From d30dfc89bf1b673b2fdc0638766b930adaec228c Mon Sep 17 00:00:00 2001 From: nsfisis Date: Sat, 29 Mar 2025 00:47:55 +0900 Subject: feat(blog/nuldoc): migrate syntax highlighter from highlight.js to shiki.js --- ...e-between-autocmd-bufwrite-and-bufwritepre.ndoc | 8 +- vhosts/blog/deno.jsonc | 2 +- vhosts/blog/deno.lock | 273 +++++++- vhosts/blog/nuldoc | 2 + vhosts/blog/nuldoc-src/components/PageLayout.tsx | 4 +- vhosts/blog/nuldoc-src/dom.ts | 22 + vhosts/blog/nuldoc-src/ndoc/parse.ts | 2 +- vhosts/blog/nuldoc-src/ndoc/to_html.ts | 71 +- vhosts/blog/nuldoc-src/types/highlight-js.d.ts | 15 - vhosts/blog/public/404.html | 2 +- vhosts/blog/public/about/index.html | 2 +- vhosts/blog/public/hl.css | 10 - vhosts/blog/public/index.html | 2 +- .../posts/2021-03-05/my-first-post/index.html | 3 +- .../posts/2021-03-30/phperkaigi-2021/index.html | 3 +- .../index.html | 49 +- .../python-unbound-local-error/index.html | 55 +- .../ruby-detect-running-implementation/index.html | 27 +- .../ruby-then-keyword-and-case-in/index.html | 217 +++--- .../rust-where-are-primitive-types-from/index.html | 199 +++--- .../index.html | 47 +- .../vim-swap-order-of-selected-lines/index.html | 47 +- .../2022-04-09/phperkaigi-2022-tokens/index.html | 435 ++++++------ .../index.html | 7 +- .../posts/2022-05-01/phperkaigi-2022/index.html | 3 +- .../php-conference-okinawa-code-golf/index.html | 27 +- .../index.html | 3 +- .../index.html | 771 +++++++++++---------- .../phperkaigi-2023-unused-token-quiz-1/index.html | 115 +-- .../setup-server-for-this-site/index.html | 151 ++-- .../phperkaigi-2023-unused-token-quiz-2/index.html | 103 +-- .../phperkaigi-2023-unused-token-quiz-3/index.html | 341 ++++----- .../rewrite-this-blog-generator/index.html | 3 +- .../index.html | 707 ++++++++++--------- .../2023-04-04/phperkaigi-2023-report/index.html | 3 +- .../2023-06-25/phpconfuk-2023-report/index.html | 3 +- .../compile-php-runtime-to-wasm/index.html | 243 ++++--- .../index.html | 3 +- .../public/posts/2023-12-03/isucon-13/index.html | 3 +- .../posts/2023-12-31/2023-reflections/index.html | 3 +- .../index.html | 265 +++---- .../index.html | 115 +-- .../2024-02-10/yapcjapan-2024-report/index.html | 3 +- .../2024-02-22/phpkansai-2024-report/index.html | 3 +- .../2024-03-17/phperkaigi-2024-report/index.html | 3 +- .../posts/2024-03-20/my-bucket-list/index.html | 3 +- .../phpcon-odawara-2024-report/index.html | 3 +- .../pipefail-option-in-gitlab-ci-cd/index.html | 127 ++-- .../index.html | 27 +- .../2024-05-11/phpconkagawa-2024-report/index.html | 3 +- .../2024-06-19/scalamatsuri-2024-report/index.html | 3 +- .../reparojson-fix-only-json-formatter/index.html | 113 +-- .../index.html | 93 +-- .../posts/2024-09-28/mncore-challenge-1/index.html | 3 +- .../posts/2024-12-04/cohackpp-report/index.html | 251 +++---- .../posts/2024-12-33/2024-reflections/index.html | 3 +- .../phperkaigi-2023-tokens-q1/index.html | 265 +++---- .../index.html | 21 +- .../phpcon-nagoya-2025-report/index.html | 3 +- .../index.html | 65 +- .../http-1-1-send-multiple-same-headers/index.html | 3 +- vhosts/blog/public/posts/index.html | 2 +- .../2023-01-18/phpstudy-tokyo-148/index.html | 3 +- .../2023-02-15/phpstudy-tokyo-149/index.html | 3 +- .../2023-03-15/phpstudy-tokyo-150/index.html | 3 +- .../slides/2023-03-24/phperkaigi-2023/index.html | 3 +- .../2023-03-25/phperkaigi-2023-tokens/index.html | 3 +- .../2023-04-12/phpstudy-tokyo-151/index.html | 3 +- .../2023-06-21/phpstudy-tokyo-153/index.html | 3 +- .../2023-06-23/phpconfuk-2023-eve/index.html | 3 +- .../2023-07-26/phpstudy-tokyo-154/index.html | 3 +- .../2023-08-24/phpstudy-tokyo-155/index.html | 3 +- .../2023-10-25/phpstudy-tokyo-157/index.html | 3 +- .../2024-01-24/phpstudy-tokyo-160/index.html | 3 +- .../slides/2024-03-08/phperkaigi-2024/index.html | 3 +- .../public/slides/2024-03-15/ya8-2024/index.html | 3 +- .../2024-04-13/phpcon-odawara-2024/index.html | 3 +- .../2024-04-25/phpstudy-tokyo-163/index.html | 3 +- .../2024-07-18/phpstudy-tokyo-166/index.html | 3 +- .../2024-10-30/phpstudy-tokyo-169/index.html | 3 +- .../public/slides/2024-11-30/cohackpp/index.html | 3 +- .../2025-02-22/phpcon-nagoya-2025/index.html | 3 +- .../slides/2025-03-23/phperkaigi-2025/index.html | 3 +- vhosts/blog/public/slides/index.html | 2 +- vhosts/blog/public/style.css | 7 +- vhosts/blog/public/tags/ci-cd/index.html | 2 +- vhosts/blog/public/tags/cohackpp/index.html | 2 +- vhosts/blog/public/tags/composer/index.html | 2 +- vhosts/blog/public/tags/conference/index.html | 2 +- vhosts/blog/public/tags/cpp/index.html | 2 +- vhosts/blog/public/tags/cpp17/index.html | 2 +- vhosts/blog/public/tags/gitlab/index.html | 2 +- vhosts/blog/public/tags/go/index.html | 2 +- vhosts/blog/public/tags/http/index.html | 2 +- vhosts/blog/public/tags/index.html | 2 +- vhosts/blog/public/tags/isucon/index.html | 2 +- .../blog/public/tags/mncore-challenge/index.html | 2 +- vhosts/blog/public/tags/neovim/index.html | 2 +- vhosts/blog/public/tags/note-to-self/index.html | 2 +- vhosts/blog/public/tags/ouj/index.html | 2 +- vhosts/blog/public/tags/perl/index.html | 2 +- vhosts/blog/public/tags/php/index.html | 2 +- vhosts/blog/public/tags/phpcon-nagoya/index.html | 2 +- vhosts/blog/public/tags/phpcon-odawara/index.html | 2 +- vhosts/blog/public/tags/phpconfuk/index.html | 2 +- vhosts/blog/public/tags/phpconkagawa/index.html | 2 +- vhosts/blog/public/tags/phpconokinawa/index.html | 2 +- vhosts/blog/public/tags/phperkaigi/index.html | 2 +- vhosts/blog/public/tags/phpkansai/index.html | 2 +- vhosts/blog/public/tags/phpstudy-tokyo/index.html | 2 +- vhosts/blog/public/tags/piet/index.html | 2 +- vhosts/blog/public/tags/python/index.html | 2 +- vhosts/blog/public/tags/python3/index.html | 2 +- vhosts/blog/public/tags/ruby/index.html | 2 +- vhosts/blog/public/tags/ruby3/index.html | 2 +- vhosts/blog/public/tags/rust/index.html | 2 +- vhosts/blog/public/tags/scala/index.html | 2 +- vhosts/blog/public/tags/scalamatsuri/index.html | 2 +- vhosts/blog/public/tags/vim/index.html | 2 +- vhosts/blog/public/tags/wasm/index.html | 2 +- vhosts/blog/public/tags/wireguard/index.html | 2 +- vhosts/blog/public/tags/ya8/index.html | 2 +- vhosts/blog/public/tags/yaml/index.html | 2 +- vhosts/blog/public/tags/yapc/index.html | 2 +- vhosts/blog/public/tags/zsh/index.html | 2 +- vhosts/blog/static/hl.css | 10 - vhosts/blog/static/style.css | 7 +- 127 files changed, 3027 insertions(+), 2505 deletions(-) delete mode 100644 vhosts/blog/nuldoc-src/types/highlight-js.d.ts delete mode 100644 vhosts/blog/public/hl.css delete mode 100644 vhosts/blog/static/hl.css diff --git a/vhosts/blog/content/posts/2021-10-02/vim-difference-between-autocmd-bufwrite-and-bufwritepre.ndoc b/vhosts/blog/content/posts/2021-10-02/vim-difference-between-autocmd-bufwrite-and-bufwritepre.ndoc index ddee7686..44713acd 100644 --- a/vhosts/blog/content/posts/2021-10-02/vim-difference-between-autocmd-bufwrite-and-bufwritepre.ndoc +++ b/vhosts/blog/content/posts/2021-10-02/vim-difference-between-autocmd-bufwrite-and-bufwritepre.ndoc @@ -102,10 +102,10 @@ remark = "Qiita から移植" diff --git a/vhosts/blog/deno.jsonc b/vhosts/blog/deno.jsonc index e0df1d1b..b0550a5b 100644 --- a/vhosts/blog/deno.jsonc +++ b/vhosts/blog/deno.jsonc @@ -7,7 +7,7 @@ "@std/path": "jsr:@std/path@^1.0.8", "@std/toml": "jsr:@std/toml@^1.0.3", "checksum/": "https://deno.land/x/checksum@1.4.0/", - "highlight.js": "npm:highlight.js@11.11.1", + "shiki": "npm:shiki@^3.2.1", "zod/": "https://deno.land/x/zod@v3.24.2/", "myjsx/jsx-runtime": "./nuldoc-src/jsx/jsx-runtime.ts", "myjsx-types/jsx-runtime": "./nuldoc-src/jsx/types.d.ts", diff --git a/vhosts/blog/deno.lock b/vhosts/blog/deno.lock index f5840e3d..300594ba 100644 --- a/vhosts/blog/deno.lock +++ b/vhosts/blog/deno.lock @@ -1,6 +1,7 @@ { "version": "4", "specifiers": { + "jsr:@std/assert@^1.0.12": "1.0.12", "jsr:@std/cli@^1.0.12": "1.0.15", "jsr:@std/cli@^1.0.15": "1.0.15", "jsr:@std/collections@^1.0.10": "1.0.10", @@ -9,14 +10,21 @@ "jsr:@std/fs@^1.0.15": "1.0.15", "jsr:@std/html@^1.0.3": "1.0.3", "jsr:@std/http@^1.0.13": "1.0.13", + "jsr:@std/internal@^1.0.6": "1.0.6", "jsr:@std/media-types@^1.1.0": "1.1.0", "jsr:@std/net@^1.0.4": "1.0.4", "jsr:@std/path@^1.0.8": "1.0.8", "jsr:@std/streams@^1.0.9": "1.0.9", "jsr:@std/toml@^1.0.3": "1.0.3", - "npm:highlight.js@11.11.1": "11.11.1" + "npm:shiki@^3.2.1": "3.2.1" }, "jsr": { + "@std/assert@1.0.12": { + "integrity": "08009f0926dda9cbd8bef3a35d3b6a4b964b0ab5c3e140a4e0351fbf34af5b9a", + "dependencies": [ + "jsr:@std/internal" + ] + }, "@std/cli@1.0.13": { "integrity": "5db2d95ab2dca3bca9fb6ad3c19908c314e93d6391c8b026725e4892d4615a69" }, @@ -54,6 +62,9 @@ "jsr:@std/streams" ] }, + "@std/internal@1.0.6": { + "integrity": "9533b128f230f73bd209408bb07a4b12f8d4255ab2a4d22a1fd6d87304aca9a4" + }, "@std/media-types@1.1.0": { "integrity": "c9d093f0c05c3512932b330e3cc1fe1d627b301db33a4c2c2185c02471d6eaa4" }, @@ -74,8 +85,262 @@ } }, "npm": { - "highlight.js@11.11.1": { - "integrity": "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==" + "@shikijs/core@3.2.1": { + "integrity": "sha512-FhsdxMWYu/C11sFisEp7FMGBtX/OSSbnXZDMBhGuUDBNTdsoZlMSgQv5f90rwvzWAdWIW6VobD+G3IrazxA6dQ==", + "dependencies": [ + "@shikijs/types", + "@shikijs/vscode-textmate", + "@types/hast", + "hast-util-to-html" + ] + }, + "@shikijs/engine-javascript@3.2.1": { + "integrity": "sha512-eMdcUzN3FMQYxOmRf2rmU8frikzoSHbQDFH2hIuXsrMO+IBOCI9BeeRkCiBkcLDHeRKbOCtYMJK3D6U32ooU9Q==", + "dependencies": [ + "@shikijs/types", + "@shikijs/vscode-textmate", + "oniguruma-to-es" + ] + }, + "@shikijs/engine-oniguruma@3.2.1": { + "integrity": "sha512-wZZAkayEn6qu2+YjenEoFqj0OyQI64EWsNR6/71d1EkG4sxEOFooowKivsWPpaWNBu3sxAG+zPz5kzBL/SsreQ==", + "dependencies": [ + "@shikijs/types", + "@shikijs/vscode-textmate" + ] + }, + "@shikijs/langs@3.2.1": { + "integrity": "sha512-If0iDHYRSGbihiA8+7uRsgb1er1Yj11pwpX1c6HLYnizDsKAw5iaT3JXj5ZpaimXSWky/IhxTm7C6nkiYVym+A==", + "dependencies": [ + "@shikijs/types" + ] + }, + "@shikijs/themes@3.2.1": { + "integrity": "sha512-k5DKJUT8IldBvAm8WcrDT5+7GA7se6lLksR+2E3SvyqGTyFMzU2F9Gb7rmD+t+Pga1MKrYFxDIeyWjMZWM6uBQ==", + "dependencies": [ + "@shikijs/types" + ] + }, + "@shikijs/types@3.2.1": { + "integrity": "sha512-/NTWAk4KE2M8uac0RhOsIhYQf4pdU0OywQuYDGIGAJ6Mjunxl2cGiuLkvu4HLCMn+OTTLRWkjZITp+aYJv60yA==", + "dependencies": [ + "@shikijs/vscode-textmate", + "@types/hast" + ] + }, + "@shikijs/vscode-textmate@10.0.2": { + "integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==" + }, + "@types/hast@3.0.4": { + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "dependencies": [ + "@types/unist" + ] + }, + "@types/mdast@4.0.4": { + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "dependencies": [ + "@types/unist" + ] + }, + "@types/unist@3.0.3": { + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==" + }, + "@ungap/structured-clone@1.3.0": { + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==" + }, + "ccount@2.0.1": { + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==" + }, + "character-entities-html4@2.1.0": { + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==" + }, + "character-entities-legacy@3.0.0": { + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==" + }, + "comma-separated-tokens@2.0.3": { + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==" + }, + "dequal@2.0.3": { + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==" + }, + "devlop@1.1.0": { + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "dependencies": [ + "dequal" + ] + }, + "emoji-regex-xs@1.0.0": { + "integrity": "sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==" + }, + "hast-util-to-html@9.0.5": { + "integrity": "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==", + "dependencies": [ + "@types/hast", + "@types/unist", + "ccount", + "comma-separated-tokens", + "hast-util-whitespace", + "html-void-elements", + "mdast-util-to-hast", + "property-information", + "space-separated-tokens", + "stringify-entities", + "zwitch" + ] + }, + "hast-util-whitespace@3.0.0": { + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "dependencies": [ + "@types/hast" + ] + }, + "html-void-elements@3.0.0": { + "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==" + }, + "mdast-util-to-hast@13.2.0": { + "integrity": "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==", + "dependencies": [ + "@types/hast", + "@types/mdast", + "@ungap/structured-clone", + "devlop", + "micromark-util-sanitize-uri", + "trim-lines", + "unist-util-position", + "unist-util-visit", + "vfile" + ] + }, + "micromark-util-character@2.1.1": { + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "dependencies": [ + "micromark-util-symbol", + "micromark-util-types" + ] + }, + "micromark-util-encode@2.0.1": { + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==" + }, + "micromark-util-sanitize-uri@2.0.1": { + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "dependencies": [ + "micromark-util-character", + "micromark-util-encode", + "micromark-util-symbol" + ] + }, + "micromark-util-symbol@2.0.1": { + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==" + }, + "micromark-util-types@2.0.2": { + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==" + }, + "oniguruma-parser@0.5.4": { + "integrity": "sha512-yNxcQ8sKvURiTwP0mV6bLQCYE7NKfKRRWunhbZnXgxSmB1OXa1lHrN3o4DZd+0Si0kU5blidK7BcROO8qv5TZA==" + }, + "oniguruma-to-es@4.1.0": { + "integrity": "sha512-SNwG909cSLo4vPyyPbU/VJkEc9WOXqu2ycBlfd1UCXLqk1IijcQktSBb2yRQ2UFPsDhpkaf+C1dtT3PkLK/yWA==", + "dependencies": [ + "emoji-regex-xs", + "oniguruma-parser", + "regex", + "regex-recursion" + ] + }, + "property-information@7.0.0": { + "integrity": "sha512-7D/qOz/+Y4X/rzSB6jKxKUsQnphO046ei8qxG59mtM3RG3DHgTK81HrxrmoDVINJb8NKT5ZsRbwHvQ6B68Iyhg==" + }, + "regex-recursion@6.0.2": { + "integrity": "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==", + "dependencies": [ + "regex-utilities" + ] + }, + "regex-utilities@2.3.0": { + "integrity": "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==" + }, + "regex@6.0.1": { + "integrity": "sha512-uorlqlzAKjKQZ5P+kTJr3eeJGSVroLKoHmquUj4zHWuR+hEyNqlXsSKlYYF5F4NI6nl7tWCs0apKJ0lmfsXAPA==", + "dependencies": [ + "regex-utilities" + ] + }, + "shiki@3.2.1": { + "integrity": "sha512-VML/2o1/KGYkEf/stJJ+s9Ypn7jUKQPomGLGYso4JJFMFxVDyPNsjsI3MB3KLjlMOeH44gyaPdXC6rik2WXvUQ==", + "dependencies": [ + "@shikijs/core", + "@shikijs/engine-javascript", + "@shikijs/engine-oniguruma", + "@shikijs/langs", + "@shikijs/themes", + "@shikijs/types", + "@shikijs/vscode-textmate", + "@types/hast" + ] + }, + "space-separated-tokens@2.0.2": { + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==" + }, + "stringify-entities@4.0.4": { + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "dependencies": [ + "character-entities-html4", + "character-entities-legacy" + ] + }, + "trim-lines@3.0.1": { + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==" + }, + "unist-util-is@6.0.0": { + "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==", + "dependencies": [ + "@types/unist" + ] + }, + "unist-util-position@5.0.0": { + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "dependencies": [ + "@types/unist" + ] + }, + "unist-util-stringify-position@4.0.0": { + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "dependencies": [ + "@types/unist" + ] + }, + "unist-util-visit-parents@6.0.1": { + "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==", + "dependencies": [ + "@types/unist", + "unist-util-is" + ] + }, + "unist-util-visit@5.0.0": { + "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", + "dependencies": [ + "@types/unist", + "unist-util-is", + "unist-util-visit-parents" + ] + }, + "vfile-message@4.0.2": { + "integrity": "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==", + "dependencies": [ + "@types/unist", + "unist-util-stringify-position" + ] + }, + "vfile@6.0.3": { + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "dependencies": [ + "@types/unist", + "vfile-message" + ] + }, + "zwitch@2.0.4": { + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==" } }, "remote": { @@ -284,7 +549,7 @@ "jsr:@std/http@^1.0.13", "jsr:@std/path@^1.0.8", "jsr:@std/toml@^1.0.3", - "npm:highlight.js@11.11.1" + "npm:shiki@^3.2.1" ] } } diff --git a/vhosts/blog/nuldoc b/vhosts/blog/nuldoc index 8ab893d3..3ce9f0e2 100755 --- a/vhosts/blog/nuldoc +++ b/vhosts/blog/nuldoc @@ -2,9 +2,11 @@ base_dir="$(dirname "$BASH_SOURCE")" +# VSCODE_TEXTMATE_DEBUG is read by shiki, the syntax highlighter library deno run \ --allow-read="$base_dir" \ --allow-write="$base_dir" \ --allow-net="0.0.0.0:8000" \ + --allow-env=VSCODE_TEXTMATE_DEBUG \ "$base_dir/nuldoc-src/main.ts" \ "$@" diff --git a/vhosts/blog/nuldoc-src/components/PageLayout.tsx b/vhosts/blog/nuldoc-src/components/PageLayout.tsx index c7145e0a..1cd0aebf 100644 --- a/vhosts/blog/nuldoc-src/components/PageLayout.tsx +++ b/vhosts/blog/nuldoc-src/components/PageLayout.tsx @@ -20,7 +20,7 @@ export default function PageLayout( metaKeywords, metaTitle, metaAtomFeedHref, - requiresSyntaxHighlight, + requiresSyntaxHighlight: _, config, children, }: Props, @@ -54,8 +54,6 @@ export default function PageLayout( {metaTitle} - {requiresSyntaxHighlight && - } {children} diff --git a/vhosts/blog/nuldoc-src/dom.ts b/vhosts/blog/nuldoc-src/dom.ts index 478cbc6c..ed7ffd31 100644 --- a/vhosts/blog/nuldoc-src/dom.ts +++ b/vhosts/blog/nuldoc-src/dom.ts @@ -69,6 +69,15 @@ export function forEachChild(e: Element, f: (n: Node) => void) { } } +export async function forEachChildAsync( + e: Element, + f: (n: Node) => Promise, +): Promise { + for (const c of e.children) { + await f(c); + } +} + export function forEachChildRecursively(e: Element, f: (n: Node) => void) { const g = (c: Node) => { f(c); @@ -78,3 +87,16 @@ export function forEachChildRecursively(e: Element, f: (n: Node) => void) { }; forEachChild(e, g); } + +export async function forEachChildRecursivelyAsync( + e: Element, + f: (n: Node) => Promise, +): Promise { + const g = async (c: Node) => { + await f(c); + if (c.kind === "element") { + await forEachChildAsync(c, g); + } + }; + await forEachChildAsync(e, g); +} diff --git a/vhosts/blog/nuldoc-src/ndoc/parse.ts b/vhosts/blog/nuldoc-src/ndoc/parse.ts index 10b3309b..4bb96f4d 100644 --- a/vhosts/blog/nuldoc-src/ndoc/parse.ts +++ b/vhosts/blog/nuldoc-src/ndoc/parse.ts @@ -19,7 +19,7 @@ export async function parseNulDocFile( const meta = parseMetadata(parts[1]); const root = parseXmlString("" + parts[2]); const doc = createNewDocumentFromRootElement(root, meta, filePath, config); - return toHtml(doc); + return await toHtml(doc); } catch (e) { if (e instanceof Error) { e.message = `${e.message} in ${filePath}`; diff --git a/vhosts/blog/nuldoc-src/ndoc/to_html.ts b/vhosts/blog/nuldoc-src/ndoc/to_html.ts index bf556655..d4630d59 100644 --- a/vhosts/blog/nuldoc-src/ndoc/to_html.ts +++ b/vhosts/blog/nuldoc-src/ndoc/to_html.ts @@ -1,30 +1,28 @@ -// @deno-types="../types/highlight-js.d.ts" -import hljs from "highlight.js"; +import { BundledLanguage, bundledLanguages, codeToHtml } from "shiki"; import { Document } from "./document.ts"; import { NuldocError } from "../errors.ts"; import { addClass, Element, - findFirstChildElement, forEachChild, forEachChildRecursively, + forEachChildRecursivelyAsync, Node, RawHTML, Text, } from "../dom.ts"; -export default function toHtml(doc: Document): Document { +export default async function toHtml(doc: Document): Promise { removeUnnecessaryTextNode(doc); transformLinkLikeToAnchorElement(doc); transformSectionIdAttribute(doc); setSectionTitleAnchor(doc); transformSectionTitleElement(doc); - transformCodeBlockElement(doc); transformNoteElement(doc); addAttributesToExternalLinkElement(doc); setDefaultLangAttribute(doc); traverseFootnotes(doc); - highlightPrograms(doc); + await transformAndHighlightCodeBlockElement(doc); return doc; } @@ -163,24 +161,6 @@ function transformSectionTitleElement(doc: Document) { forEachChild(doc.root, g); } -function transformCodeBlockElement(doc: Document) { - forEachChildRecursively(doc.root, (n) => { - if (n.kind !== "element" || n.name !== "codeblock") { - return; - } - - n.name = "pre"; - addClass(n, "highlight"); - const codeElement: Element = { - kind: "element", - name: "code", - attributes: new Map(), - children: n.children, - }; - n.children = [codeElement]; - }); -} - function transformNoteElement(doc: Document) { forEachChildRecursively(doc.root, (n) => { if (n.kind !== "element" || n.name !== "note") { @@ -254,40 +234,27 @@ function traverseFootnotes(doc: Document) { }); } -function highlightPrograms(doc: Document) { - forEachChildRecursively(doc.root, (n) => { - if (n.kind !== "element" || n.name !== "pre") { - return; - } - const preClass = n.attributes.get("class") || ""; - if (!preClass.includes("highlight")) { - return; - } - const codeElement = findFirstChildElement(n, "code"); - if (!codeElement) { - return; - } - const language = n.attributes.get("language"); - if (!language) { +async function transformAndHighlightCodeBlockElement(doc: Document) { + await forEachChildRecursivelyAsync(doc.root, async (n) => { + if (n.kind !== "element" || n.name !== "codeblock") { return; } - const sourceCodeNode = codeElement.children[0] as Text | RawHTML; - const sourceCode = sourceCodeNode.content; - if (!hljs.getLanguage(language)) { - if (language === "zsh") { - // highlight.js does not have a language definition for zsh. - hljs.registerAliases("zsh", { languageName: "bash" }); - } else { - return; - } - } + const language = n.attributes.get("language") || "text"; + const sourceCodeNode = n.children[0] as Text | RawHTML; + const sourceCode = sourceCodeNode.content; - const highlighted = - hljs.highlight(sourceCode, { language: language }).value; + const highlighted = await codeToHtml(sourceCode, { + lang: language in bundledLanguages ? language as BundledLanguage : "text", + theme: "github-light", + colorReplacements: { + "#fff": "#f5f5f5", + }, + }); sourceCodeNode.content = highlighted; sourceCodeNode.raw = true; - codeElement.attributes.set("class", "highlight"); + n.name = "div"; + n.attributes.set("class", "codeblock"); }); } diff --git a/vhosts/blog/nuldoc-src/types/highlight-js.d.ts b/vhosts/blog/nuldoc-src/types/highlight-js.d.ts deleted file mode 100644 index 67007b53..00000000 --- a/vhosts/blog/nuldoc-src/types/highlight-js.d.ts +++ /dev/null @@ -1,15 +0,0 @@ -declare module "highlight.js" { - function registerAliases( - aliases: string | string[], - language: { languageName: string }, - ): void; - - function getLanguage( - name: string, - ): string | undefined; - - function highlight( - code: string, - options: { language: string }, - ): { value: string }; -} diff --git a/vhosts/blog/public/404.html b/vhosts/blog/public/404.html index 9f1e94fe..1d5d5029 100644 --- a/vhosts/blog/public/404.html +++ b/vhosts/blog/public/404.html @@ -13,7 +13,7 @@ Page Not Found|REPL: Rest-Eat-Program Loop - +
diff --git a/vhosts/blog/public/about/index.html b/vhosts/blog/public/about/index.html index e14f427e..9c46426b 100644 --- a/vhosts/blog/public/about/index.html +++ b/vhosts/blog/public/about/index.html @@ -13,7 +13,7 @@ About|REPL: Rest-Eat-Program Loop - +
diff --git a/vhosts/blog/public/hl.css b/vhosts/blog/public/hl.css deleted file mode 100644 index 275239a7..00000000 --- a/vhosts/blog/public/hl.css +++ /dev/null @@ -1,10 +0,0 @@ -pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}/*! - Theme: GitHub - Description: Light theme as seen on github.com - Author: github.com - Maintainer: @Hirse - Updated: 2021-05-15 - - Outdated base version: https://github.com/primer/github-syntax-light - Current colors taken from GitHub's CSS -*/.hljs{color:#24292e;background:#fff}.hljs-doctag,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-template-tag,.hljs-template-variable,.hljs-type,.hljs-variable.language_{color:#d73a49}.hljs-title,.hljs-title.class_,.hljs-title.class_.inherited__,.hljs-title.function_{color:#6f42c1}.hljs-attr,.hljs-attribute,.hljs-literal,.hljs-meta,.hljs-number,.hljs-operator,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id,.hljs-variable{color:#005cc5}.hljs-meta .hljs-string,.hljs-regexp,.hljs-string{color:#032f62}.hljs-built_in,.hljs-symbol{color:#e36209}.hljs-code,.hljs-comment,.hljs-formula{color:#6a737d}.hljs-name,.hljs-quote,.hljs-selector-pseudo,.hljs-selector-tag{color:#22863a}.hljs-subst{color:#24292e}.hljs-section{color:#005cc5;font-weight:700}.hljs-bullet{color:#735c0f}.hljs-emphasis{color:#24292e;font-style:italic}.hljs-strong{color:#24292e;font-weight:700}.hljs-addition{color:#22863a;background-color:#f0fff4}.hljs-deletion{color:#b31d28;background-color:#ffeef0} \ No newline at end of file diff --git a/vhosts/blog/public/index.html b/vhosts/blog/public/index.html index 7372aa6a..d784b67d 100644 --- a/vhosts/blog/public/index.html +++ b/vhosts/blog/public/index.html @@ -14,7 +14,7 @@ REPL: Rest-Eat-Program Loop - +
diff --git a/vhosts/blog/public/posts/2021-03-05/my-first-post/index.html b/vhosts/blog/public/posts/2021-03-05/my-first-post/index.html index 4e00a39d..9170e5f2 100644 --- a/vhosts/blog/public/posts/2021-03-05/my-first-post/index.html +++ b/vhosts/blog/public/posts/2021-03-05/my-first-post/index.html @@ -13,8 +13,7 @@ My First Post|REPL: Rest-Eat-Program Loop - - +
diff --git a/vhosts/blog/public/posts/2021-03-30/phperkaigi-2021/index.html b/vhosts/blog/public/posts/2021-03-30/phperkaigi-2021/index.html index 4dfee57e..e70cf152 100644 --- a/vhosts/blog/public/posts/2021-03-30/phperkaigi-2021/index.html +++ b/vhosts/blog/public/posts/2021-03-30/phperkaigi-2021/index.html @@ -14,8 +14,7 @@ PHPerKaigi 2021|REPL: Rest-Eat-Program Loop - - +
diff --git a/vhosts/blog/public/posts/2021-10-02/cpp-you-can-use-keywords-in-attributes/index.html b/vhosts/blog/public/posts/2021-10-02/cpp-you-can-use-keywords-in-attributes/index.html index b6baad39..d99fc02c 100644 --- a/vhosts/blog/public/posts/2021-10-02/cpp-you-can-use-keywords-in-attributes/index.html +++ b/vhosts/blog/public/posts/2021-10-02/cpp-you-can-use-keywords-in-attributes/index.html @@ -14,8 +14,7 @@ 【C++】 属性構文の属性名にはキーワードが使える|REPL: Rest-Eat-Program Loop - - +
@@ -74,25 +73,27 @@ タイトル落ち。まずはこのコードを見て欲しい。

-
#include <iostream>
-
-[[alignas]] [[alignof]] [[and]] [[and_eq]] [[asm]] [[auto]] [[bitand]]
-[[bitor]] [[bool]] [[break]] [[case]] [[catch]] [[char]] [[char16_t]]
-[[char32_t]] [[class]] [[compl]] [[const]] [[const_cast]] [[constexpr]]
-[[continue]] [[decltype]] [[default]] [[delete]] [[do]] [[double]]
-[[dynamic_cast]] [[else]] [[enum]] [[explicit]] [[export]] [[extern]] [[false]]
-[[final]] [[float]] [[for]] [[friend]] [[goto]] [[if]] [[inline]] [[int]]
-[[long]] [[mutable]] [[namespace]] [[new]] [[noexcept]] [[not]] [[not_eq]]
-[[nullptr]] [[operator]] [[or]] [[or_eq]] [[override]] [[private]]
-[[protected]] [[public]] [[register]] [[reinterpret_cast]] [[return]] [[short]]
-[[signed]] [[sizeof]] [[static]] [[static_assert]] [[static_cast]] [[struct]]
-[[switch]] [[template]] [[this]] [[thread_local]] [[throw]] [[true]] [[try]]
-[[typedef]] [[typeid]] [[typename]] [[union]] [[unsigned]]
-[[virtual]] [[void]] [[volatile]] [[wchar_t]] [[while]] [[xor]] [[xor_eq]]
-// [[using]]
-int main() {
-    std::cout << "Hello, World!" << std::endl;
-}
+
+
#include <iostream>
+
+[[alignas]] [[alignof]] [[and]] [[and_eq]] [[asm]] [[auto]] [[bitand]]
+[[bitor]] [[bool]] [[break]] [[case]] [[catch]] [[char]] [[char16_t]]
+[[char32_t]] [[class]] [[compl]] [[const]] [[const_cast]] [[constexpr]]
+[[continue]] [[decltype]] [[default]] [[delete]] [[do]] [[double]]
+[[dynamic_cast]] [[else]] [[enum]] [[explicit]] [[export]] [[extern]] [[false]]
+[[final]] [[float]] [[for]] [[friend]] [[goto]] [[if]] [[inline]] [[int]]
+[[long]] [[mutable]] [[namespace]] [[new]] [[noexcept]] [[not]] [[not_eq]]
+[[nullptr]] [[operator]] [[or]] [[or_eq]] [[override]] [[private]]
+[[protected]] [[public]] [[register]] [[reinterpret_cast]] [[return]] [[short]]
+[[signed]] [[sizeof]] [[static]] [[static_assert]] [[static_cast]] [[struct]]
+[[switch]] [[template]] [[this]] [[thread_local]] [[throw]] [[true]] [[try]]
+[[typedef]] [[typeid]] [[typename]] [[union]] [[unsigned]]
+[[virtual]] [[void]] [[volatile]] [[wchar_t]] [[while]] [[xor]] [[xor_eq]]
+// [[using]]
+int main() {
+    std::cout << "Hello, World!" << std::endl;
+}
+

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

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

C++17 の仕様も見てみる (正確には標準化前のドラフト)。 diff --git a/vhosts/blog/public/posts/2021-10-02/python-unbound-local-error/index.html b/vhosts/blog/public/posts/2021-10-02/python-unbound-local-error/index.html index 91ec5d0a..f3a11356 100644 --- a/vhosts/blog/public/posts/2021-10-02/python-unbound-local-error/index.html +++ b/vhosts/blog/public/posts/2021-10-02/python-unbound-local-error/index.html @@ -14,8 +14,7 @@ 【Python】 クロージャとUnboundLocalError: local variable 'x' referenced before assignment|REPL: Rest-Eat-Program Loop - - +

@@ -78,13 +77,15 @@ Python でクロージャを作ろうと、次のようなコードを書いた。

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

関数 g から 関数 f のスコープ内で定義された変数 x を参照し、それに 1 を足そうとしている。 これを実行すると x += 1 の箇所でエラーが発生する。 @@ -100,27 +101,31 @@ f() local変数 x が代入前に参照された、とある。これは、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/vhosts/blog/public/posts/2021-10-02/ruby-detect-running-implementation/index.html b/vhosts/blog/public/posts/2021-10-02/ruby-detect-running-implementation/index.html index 0e9ee932..cf2eb729 100644 --- a/vhosts/blog/public/posts/2021-10-02/ruby-detect-running-implementation/index.html +++ b/vhosts/blog/public/posts/2021-10-02/ruby-detect-running-implementation/index.html @@ -14,8 +14,7 @@ 【Ruby】 自身を実行している処理系の種類を判定する|REPL: Rest-Eat-Program Loop - - +

@@ -83,12 +82,14 @@ 上記ページの例から引用する:

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

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

-
/*
-* Ruby engine.
-*/
-#define MRUBY_RUBY_ENGINE  "mruby"
+
+
/*
+* Ruby engine.
+*/
+#define MRUBY_RUBY_ENGINE  "mruby"
+
diff --git a/vhosts/blog/public/posts/2021-10-02/ruby-then-keyword-and-case-in/index.html b/vhosts/blog/public/posts/2021-10-02/ruby-then-keyword-and-case-in/index.html index a11a2f15..76f7058c 100644 --- a/vhosts/blog/public/posts/2021-10-02/ruby-then-keyword-and-case-in/index.html +++ b/vhosts/blog/public/posts/2021-10-02/ruby-then-keyword-and-case-in/index.html @@ -14,8 +14,7 @@ 【Ruby】 then キーワードと case in|REPL: Rest-Eat-Program Loop - - +
@@ -83,36 +82,40 @@ 使われることは稀だが、Ruby では then がキーワードになっている。次のように使う:

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

このキーワードが現れうる場所はいくつかあり、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
+
@@ -121,17 +124,21 @@ 普通 Ruby のコードで then を書くことはない。なぜか。次のコードを実行してみるとわかる。

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

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

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

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

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

無事 Hello, World! と出力されるようになった。 @@ -155,21 +164,27 @@ puts 'Hello, World!' if a b end +

+
if a b end
+

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

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

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

-
p_case_body : keyword_in
-{
-  SET_LEX_STATE(EXPR_BEG|EXPR_LABEL);
-  p->command_start = FALSE;
-  $<ctxt>1 = p->ctxt;
-  p->ctxt.in_kwarg = 1;
-  $<tbl>$ = push_pvtbl(p);
-}
-{
-  $<tbl>$ = push_pktbl(p);
-}
-p_top_expr then
-{
-  pop_pktbl(p, $<tbl>3);
-  pop_pvtbl(p, $<tbl>2);
-  p->ctxt.in_kwarg = $<ctxt>1.in_kwarg;
-}
-compstmt
-p_cases
-{
-  /*%%%*/
-  $$ = NEW_IN($4, $7, $8, &@$);
-  /*% %*/
-  /*% ripper: in!($4, $7, escape_Qundef($8)) %*/
-}
-;
+
+
p_case_body : keyword_in
+{
+  SET_LEX_STATE(EXPR_BEG|EXPR_LABEL);
+  p->command_start = FALSE;
+  $<ctxt>1 = p->ctxt;
+  p->ctxt.in_kwarg = 1;
+  $<tbl>$ = push_pvtbl(p);
+}
+{
+  $<tbl>$ = push_pktbl(p);
+}
+p_top_expr then
+{
+  pop_pktbl(p, $<tbl>3);
+  pop_pvtbl(p, $<tbl>2);
+  p->ctxt.in_kwarg = $<ctxt>1.in_kwarg;
+}
+compstmt
+p_cases
+{
+  /*%%%*/
+  $$ = NEW_IN($4, $7, $8, &@$);
+  /*% %*/
+  /*% ripper: in!($4, $7, escape_Qundef($8)) %*/
+}
+;
+

簡略版:

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

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

-
case x
-in 1 then a
-in 2 then b
-in 3 then c
-end
-
-case x
-in 1
-  a
-in 2
-  b
-in 3
-  c
-end
-
-case x
-in 1; a
-in 2; b
-in 3; c
-end
+
+
case x
+in 1 then a
+in 2 then b
+in 3 then c
+end
+
+case x
+in 1
+  a
+in 2
+  b
+in 3
+  c
+end
+
+case x
+in 1; a
+in 2; b
+in 3; c
+end
+

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

-
case x
-in 0 then a
-in n if n < 0 then b
-in n then c
-end
+
+
case x
+in 0 then a
+in n if n < 0 then b
+in n then c
+end
+
diff --git a/vhosts/blog/public/posts/2021-10-02/rust-where-are-primitive-types-from/index.html b/vhosts/blog/public/posts/2021-10-02/rust-where-are-primitive-types-from/index.html index 69cfb8a4..4ae7f235 100644 --- a/vhosts/blog/public/posts/2021-10-02/rust-where-are-primitive-types-from/index.html +++ b/vhosts/blog/public/posts/2021-10-02/rust-where-are-primitive-types-from/index.html @@ -14,8 +14,7 @@ Rust のプリミティブ型はどこからやって来るか|REPL: Rest-Eat-Program Loop - - +
@@ -73,26 +72,28 @@ Rust において、プリミティブ型の名前は予約語でない。したがって、次のコードは合法である。

-
#![allow(non_camel_case_types)]
-#![allow(dead_code)]
-
-struct bool;
-struct char;
-struct i8;
-struct i16;
-struct i32;
-struct i64;
-struct i128;
-struct isize;
-struct u8;
-struct u16;
-struct u32;
-struct u64;
-struct u128;
-struct usize;
-struct f32;
-struct f64;
-struct str;
+
+
#![allow(non_camel_case_types)]
+#![allow(dead_code)]
+
+struct bool;
+struct char;
+struct i8;
+struct i16;
+struct i32;
+struct i64;
+struct i128;
+struct isize;
+struct u8;
+struct u16;
+struct u32;
+struct u64;
+struct u128;
+struct usize;
+struct f32;
+struct f64;
+struct str;
+

では、普段単に bool と書いたとき、この bool は一体どこから来ているのか。rustc のソースを追ってみた。 @@ -127,60 +128,66 @@ rustc はセルフホストされている (= rustc 自身が Rust で書かれている) ので、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 というのはいかにも名前解決を担いそうなクレート名である。該当箇所を見てみる。

-
/// Interns the names of the primitive types.
-///
-/// All other types are defined somewhere and possibly imported, but the primitive ones need
-/// special handling, since they have no place of origin.
-struct PrimitiveTypeTable {
-    primitive_types: FxHashMap<Symbol, PrimTy>,
-}
-
-impl PrimitiveTypeTable {
-    fn new() -> PrimitiveTypeTable {
-        let mut table = FxHashMap::default();
-
-        table.insert(sym::bool, Bool);
-        table.insert(sym::char, Char);
-        table.insert(sym::f32, Float(FloatTy::F32));
-        table.insert(sym::f64, Float(FloatTy::F64));
-        table.insert(sym::isize, Int(IntTy::Isize));
-        table.insert(sym::i8, Int(IntTy::I8));
-        table.insert(sym::i16, Int(IntTy::I16));
-        table.insert(sym::i32, Int(IntTy::I32));
-        table.insert(sym::i64, Int(IntTy::I64));
-        table.insert(sym::i128, Int(IntTy::I128));
-        table.insert(sym::str, Str);
-        table.insert(sym::usize, Uint(UintTy::Usize));
-        table.insert(sym::u8, Uint(UintTy::U8));
-        table.insert(sym::u16, Uint(UintTy::U16));
-        table.insert(sym::u32, Uint(UintTy::U32));
-        table.insert(sym::u64, Uint(UintTy::U64));
-        table.insert(sym::u128, Uint(UintTy::U128));
-        Self { primitive_types: table }
-    }
-}
+
+
/// Interns the names of the primitive types.
+///
+/// All other types are defined somewhere and possibly imported, but the primitive ones need
+/// special handling, since they have no place of origin.
+struct PrimitiveTypeTable {
+    primitive_types: FxHashMap<Symbol, PrimTy>,
+}
+
+impl PrimitiveTypeTable {
+    fn new() -> PrimitiveTypeTable {
+        let mut table = FxHashMap::default();
+
+        table.insert(sym::bool, Bool);
+        table.insert(sym::char, Char);
+        table.insert(sym::f32, Float(FloatTy::F32));
+        table.insert(sym::f64, Float(FloatTy::F64));
+        table.insert(sym::isize, Int(IntTy::Isize));
+        table.insert(sym::i8, Int(IntTy::I8));
+        table.insert(sym::i16, Int(IntTy::I16));
+        table.insert(sym::i32, Int(IntTy::I32));
+        table.insert(sym::i64, Int(IntTy::I64));
+        table.insert(sym::i128, Int(IntTy::I128));
+        table.insert(sym::str, Str);
+        table.insert(sym::usize, Uint(UintTy::Usize));
+        table.insert(sym::u8, Uint(UintTy::U8));
+        table.insert(sym::u16, Uint(UintTy::U16));
+        table.insert(sym::u32, Uint(UintTy::U32));
+        table.insert(sym::u64, Uint(UintTy::U64));
+        table.insert(sym::u128, Uint(UintTy::U128));
+        Self { primitive_types: table }
+    }
+}
+

これは初めに列挙したプリミティブ型の一覧と一致している。doc comment にも、 @@ -196,27 +203,29 @@ rustc_resolve/src/lib.rs: table.insert(sym::i128, Int(IntTy::I128)); とある。次はこの struct の使用箇所を追う。追うと言っても使われている箇所は次の一箇所しかない。なお説明に不要な箇所は大きく削っている。

-
/// This resolves the identifier `ident` in the namespace `ns` in the current lexical scope.
-/// (略)
-fn resolve_ident_in_lexical_scope(
-    &mut self,
-    mut ident: Ident,
-    ns: Namespace,
-    // (略)
-) -> Option<LexicalScopeBinding<'a>> {
-    // (略)
-
-    if ns == TypeNS {
-        if let Some(prim_ty) = self.primitive_type_table.primitive_types.get(&ident.name) {
-            let binding =
-                (Res::PrimTy(*prim_ty), ty::Visibility::Public, DUMMY_SP, ExpnId::root())
-                  .to_name_binding(self.arenas);
-            return Some(LexicalScopeBinding::Item(binding));
-        }
-    }
-
-    None
-}
+
+
/// This resolves the identifier `ident` in the namespace `ns` in the current lexical scope.
+/// (略)
+fn resolve_ident_in_lexical_scope(
+    &mut self,
+    mut ident: Ident,
+    ns: Namespace,
+    // (略)
+) -> Option<LexicalScopeBinding<'a>> {
+    // (略)
+
+    if ns == TypeNS {
+        if let Some(prim_ty) = self.primitive_type_table.primitive_types.get(&ident.name) {
+            let binding =
+                (Res::PrimTy(*prim_ty), ty::Visibility::Public, DUMMY_SP, ExpnId::root())
+                  .to_name_binding(self.arenas);
+            return Some(LexicalScopeBinding::Item(binding));
+        }
+    }
+
+    None
+}
+

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

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

ここで main()boolstruct bool として解決される。なぜなら、プリミティブ型の判定をする前に bool という名前の別の型が見つかるからだ。 diff --git a/vhosts/blog/public/posts/2021-10-02/vim-difference-between-autocmd-bufwrite-and-bufwritepre/index.html b/vhosts/blog/public/posts/2021-10-02/vim-difference-between-autocmd-bufwrite-and-bufwritepre/index.html index 72c04ee1..ddf70cae 100644 --- a/vhosts/blog/public/posts/2021-10-02/vim-difference-between-autocmd-bufwrite-and-bufwritepre/index.html +++ b/vhosts/blog/public/posts/2021-10-02/vim-difference-between-autocmd-bufwrite-and-bufwritepre/index.html @@ -14,8 +14,7 @@ 【Vim】 autocmd events の BufWrite/BufWritePre の違い|REPL: Rest-Eat-Program Loop - - +

@@ -124,24 +123,30 @@ https://github.com/vim/vim/blob/8e6be34338f13a6a625f19bcef82019c9adc65f2/src/autocmd.c#L85-L86

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

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

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

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

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

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

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

-
                                                              *FileEncoding*
-FileEncoding                    Obsolete.  It still works and is equivalent
-                                to |EncodingChanged|.
+
+
                                                              *FileEncoding*
+FileEncoding                    Obsolete.  It still works and is equivalent
+                                to |EncodingChanged|.
+
diff --git a/vhosts/blog/public/posts/2021-10-02/vim-swap-order-of-selected-lines/index.html b/vhosts/blog/public/posts/2021-10-02/vim-swap-order-of-selected-lines/index.html index 00a6265b..afee803f 100644 --- a/vhosts/blog/public/posts/2021-10-02/vim-swap-order-of-selected-lines/index.html +++ b/vhosts/blog/public/posts/2021-10-02/vim-swap-order-of-selected-lines/index.html @@ -14,8 +14,7 @@ Vimで選択した行の順番を入れ替える|REPL: Rest-Eat-Program Loop - - +
@@ -69,11 +68,13 @@

TL; DR

-
" License: Public Domain
-
-command! -bar -range=%
-    \ Reverse
-    \ keeppatterns <line1>,<line2>g/^/m<line1>-1
+
+
" License: Public Domain
+
+command! -bar -range=%
+    \ Reverse
+    \ keeppatterns <line1>,<line2>g/^/m<line1>-1
+
@@ -142,9 +143,11 @@ command! -bar -range=% なお、:g/^/m0 は全ての行を入れ替えるが、:N,Mg/^/mN-1 とすることで N行目から M行目を処理範囲とするよう拡張できる。手でこれを入力するわけにはいかないので、次のようなコマンドを用意する。

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

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

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

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

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

まさにこのための Exコマンド、:keeppatterns が存在する。:keeppatterns {command} のように使い、読んで字の如く、後ろに続く Exコマンドを「現在の検索パターンを保ったまま」実行する。はるかに分かりやすく意図を表現できる。 diff --git a/vhosts/blog/public/posts/2022-04-09/phperkaigi-2022-tokens/index.html b/vhosts/blog/public/posts/2022-04-09/phperkaigi-2022-tokens/index.html index b4875558..3469e8b1 100644 --- a/vhosts/blog/public/posts/2022-04-09/phperkaigi-2022-tokens/index.html +++ b/vhosts/blog/public/posts/2022-04-09/phperkaigi-2022-tokens/index.html @@ -14,8 +14,7 @@ PHPerKaigi 2022 トークン問題の解説|REPL: Rest-Eat-Program Loop - - +

@@ -84,75 +83,77 @@ ソースコードはこちら。実行には PHP 8.1 以上が必要なので注意。

-
<?php
-
-declare(strict_types=0O1);
-
-namespace Dgcircus\PHPerKaigi\Y2022;
-
-/**
- * @todo
- * Run this program to acquire a PHPer token.
- */
-
-https://creativecommons.org/publicdomain/zero/1.0/
-
-\error_reporting(~+!'We are hiring!');
-
-$z = fn($f) => (fn($x) => $f(fn(...$xs) => $x($x)(...$xs)))(fn($x) => $f(fn(...$xs) => $x($x)(...$xs)));
-$id = \spl_object_id(...);
-$put = fn($c) => \printf('%c', $c);
-$mm = fn($p, $n) => new \ArrayObject(\array_fill(+!![], $n, $p));
-
-$👉 = fn($m, $p, $b, $e, $mp, $pc) => [++$mp, ++$pc];
-$👈 = fn($m, $p, $b, $e, $mp, $pc) => [--$mp, ++$pc];
-$👍 = fn($m, $p, $b, $e, $mp, $pc) => [$mp, ++$pc, ++$m[$mp]];
-$👎 = fn($m, $p, $b, $e, $mp, $pc) => [$mp, ++$pc, --$m[$mp]];
-$📝 = fn($m, $p, $b, $e, $mp, $pc) => [$mp, ++$pc, $put($m[$mp])];
-$🤡 = fn($m, $p, $b, $e, $mp, $pc) => match ($m[$mp]) {
-  +!![] => [$mp, $z(fn($loop) => fn($pc, $n) => match ($id($p[$pc])) {
-    $b => $loop(++$pc, ++$n),
-    $e => $n === +!![] ? ++$pc : $loop(++$pc, --$n),
-    default => $loop(++$pc, $n),
-  })($pc, -![])],
-  default => [$mp, ++$pc],
-};
-$🎪 = fn($m, $p, $b, $e, $mp, $pc) => match ($m[$mp]) {
-  +!![] => [$mp, ++$pc],
-  default => [$mp, $z(fn($loop) => fn($pc, $n) => match ($id($p[$pc])) {
-    $e => $loop(--$pc, ++$n),
-    $b => $n === +!![] ? $pc+![] : $loop(--$pc, --$n),
-    default => $loop(--$pc, $n),
-  })($pc, -![])],
-};
-$🐘 = fn($p) => $z(fn($loop) => fn($m, $p, $b, $e, $mp, $pc) =>
-  isset($p[$pc]) && $loop($m, $p, $b, $e, ...($p[$pc]($m, $p, $b, $e, $mp, $pc)))
-)($mm(+!![], +(![].![])), $p, $id($🤡), $id($🎪), +!![], +!![]);
-
-$🐘([
-  $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍,
-  $🤡,
-  $👉, $👍, $👍, $👍,
-  $👉, $👍, $👍, $👍, $👍, $👍,
-  $👉, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍,
-  $👉, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍,
-  $👈, $👈, $👈, $👈, $👎,
-  $🎪,
-  $👉, $👍, $👍, $👍, $👍, $👍, $📝,
-  $👎, $👎, $📝,
-  $👉, $👎, $👎, $👎, $📝,
-  $👉, $👎, $👎, $👎, $📝,
-  $👎, $👎, $📝,
-  $👎, $📝,
-  $👈, $📝,
-  $👉, $👉, $👎, $👎, $📝,
-  $👍, $👍, $👍, $👍, $👍, $👍, $👍, $📝,
-  $👈, $👎, $👎, $👎, $👎, $📝,
-  $👈, $📝,
-  $👉, $👍, $👍, $📝,
-  $👉, $👎, $📝,
-  $👈, $📝,
-]);
+
+
<?php
+
+declare(strict_types=0O1);
+
+namespace Dgcircus\PHPerKaigi\Y2022;
+
+/**
+ * @todo
+ * Run this program to acquire a PHPer token.
+ */
+
+https://creativecommons.org/publicdomain/zero/1.0/
+
+\error_reporting(~+!'We are hiring!');
+
+$z = fn($f) => (fn($x) => $f(fn(...$xs) => $x($x)(...$xs)))(fn($x) => $f(fn(...$xs) => $x($x)(...$xs)));
+$id = \spl_object_id(...);
+$put = fn($c) => \printf('%c', $c);
+$mm = fn($p, $n) => new \ArrayObject(\array_fill(+!![], $n, $p));
+
+$👉 = fn($m, $p, $b, $e, $mp, $pc) => [++$mp, ++$pc];
+$👈 = fn($m, $p, $b, $e, $mp, $pc) => [--$mp, ++$pc];
+$👍 = fn($m, $p, $b, $e, $mp, $pc) => [$mp, ++$pc, ++$m[$mp]];
+$👎 = fn($m, $p, $b, $e, $mp, $pc) => [$mp, ++$pc, --$m[$mp]];
+$📝 = fn($m, $p, $b, $e, $mp, $pc) => [$mp, ++$pc, $put($m[$mp])];
+$🤡 = fn($m, $p, $b, $e, $mp, $pc) => match ($m[$mp]) {
+  +!![] => [$mp, $z(fn($loop) => fn($pc, $n) => match ($id($p[$pc])) {
+    $b => $loop(++$pc, ++$n),
+    $e => $n === +!![] ? ++$pc : $loop(++$pc, --$n),
+    default => $loop(++$pc, $n),
+  })($pc, -![])],
+  default => [$mp, ++$pc],
+};
+$🎪 = fn($m, $p, $b, $e, $mp, $pc) => match ($m[$mp]) {
+  +!![] => [$mp, ++$pc],
+  default => [$mp, $z(fn($loop) => fn($pc, $n) => match ($id($p[$pc])) {
+    $e => $loop(--$pc, ++$n),
+    $b => $n === +!![] ? $pc+![] : $loop(--$pc, --$n),
+    default => $loop(--$pc, $n),
+  })($pc, -![])],
+};
+$🐘 = fn($p) => $z(fn($loop) => fn($m, $p, $b, $e, $mp, $pc) =>
+  isset($p[$pc]) && $loop($m, $p, $b, $e, ...($p[$pc]($m, $p, $b, $e, $mp, $pc)))
+)($mm(+!![], +(![].![])), $p, $id($🤡), $id($🎪), +!![], +!![]);
+
+$🐘([
+  $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍,
+  $🤡,
+  $👉, $👍, $👍, $👍,
+  $👉, $👍, $👍, $👍, $👍, $👍,
+  $👉, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍,
+  $👉, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍,
+  $👈, $👈, $👈, $👈, $👎,
+  $🎪,
+  $👉, $👍, $👍, $👍, $👍, $👍, $📝,
+  $👎, $👎, $📝,
+  $👉, $👎, $👎, $👎, $📝,
+  $👉, $👎, $👎, $👎, $📝,
+  $👎, $👎, $📝,
+  $👎, $📝,
+  $👈, $📝,
+  $👉, $👉, $👎, $👎, $📝,
+  $👍, $👍, $👍, $👍, $👍, $👍, $👍, $📝,
+  $👈, $👎, $👎, $👎, $👎, $📝,
+  $👈, $📝,
+  $👉, $👍, $👍, $📝,
+  $👉, $👎, $📝,
+  $👈, $📝,
+]);
+

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

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

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

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

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

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

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

-
<?php
-
-/*********************************************************
- * This program displays a PHPer token.                  *
- * Guess 'N'.                                            *
- *                                                       *
- * Hints:                                                *
- * - N itself has no special meaning, e.g., 42, 8128,    *
- *   it is selected at random.                           *
- * - Each element of $token represents a single letter.  *
- * - One letter consists of 5x5 cells.                   *
- * - Remember, the output is a complete PHPer token.     *
- *                                                       *
- * License:                                              *
- *   https://creativecommons.org/publicdomain/zero/1.0/  *
- *********************************************************/
-const N = 0 /* Change it to your answer. */;
-assert(0 <= N && N <= 0b11111_11111_11111_11111_11111);
-
-$token = [
-  0x14B499C,
-  0x0BE34CC, 0x01C9C69,
-  0x0ECA069, 0x01C2449, 0x0FDB166, 0x01C9C69,
-  0x01C1C66, 0x0FC1C47, 0x01C1C66,
-  0x10C5858, 0x1E4E3B8, 0x1A2F2F8,
-];
-foreach ($token as $x) {
-  $x = $x ^ N;
-
-  $x = sprintf('%025b', $x);
-  $x = str_replace(search: ['0', '1'], replace: [' ', '#'], subject: $x);
-  $x = implode("\n", str_split($x, length: 5));
-  echo "{$x}\n\n";
-}
+
+
<?php
+
+/*********************************************************
+ * This program displays a PHPer token.                  *
+ * Guess 'N'.                                            *
+ *                                                       *
+ * Hints:                                                *
+ * - N itself has no special meaning, e.g., 42, 8128,    *
+ *   it is selected at random.                           *
+ * - Each element of $token represents a single letter.  *
+ * - One letter consists of 5x5 cells.                   *
+ * - Remember, the output is a complete PHPer token.     *
+ *                                                       *
+ * License:                                              *
+ *   https://creativecommons.org/publicdomain/zero/1.0/  *
+ *********************************************************/
+const N = 0 /* Change it to your answer. */;
+assert(0 <= N && N <= 0b11111_11111_11111_11111_11111);
+
+$token = [
+  0x14B499C,
+  0x0BE34CC, 0x01C9C69,
+  0x0ECA069, 0x01C2449, 0x0FDB166, 0x01C9C69,
+  0x01C1C66, 0x0FC1C47, 0x01C1C66,
+  0x10C5858, 0x1E4E3B8, 0x1A2F2F8,
+];
+foreach ($token as $x) {
+  $x = $x ^ N;
+
+  $x = sprintf('%025b', $x);
+  $x = str_replace(search: ['0', '1'], replace: [' ', '#'], subject: $x);
+  $x = implode("\n", str_split($x, length: 5));
+  echo "{$x}\n\n";
+}
+

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

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

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

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

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

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

二進数に変換して、

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

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

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

5文字ごとに区切ったあと、改行で結合している。 @@ -450,49 +469,55 @@ $🐘([ N は高々

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

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

-
<?php
-
-$x = 0x14B499C;
-
-$x = $x ^ N;
-
-$x = sprintf('%025b', $x);
-$x = str_replace(search: ['0', '1'], replace: [' ', '#'], subject: $x);
-$x = implode("\n", str_split($x, length: 5));
-
-assert($x ===
-" # # \n" .
-"#####\n" .
-" # # \n" .
-"#####\n" .
-" # # ");
+
+
<?php
+
+$x = 0x14B499C;
+
+$x = $x ^ N;
+
+$x = sprintf('%025b', $x);
+$x = str_replace(search: ['0', '1'], replace: [' ', '#'], subject: $x);
+$x = implode("\n", str_split($x, length: 5));
+
+assert($x ===
+" # # \n" .
+"#####\n" .
+" # # \n" .
+"#####\n" .
+" # # ");
+

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

-
<?php
-
-$x =
-" # # \n" .
-"#####\n" .
-" # # \n" .
-"#####\n" .
-" # # ";
-
-$x = implode('', explode("\n", $x));
-$x = str_replace(search: [' ', '#'], replace: ['0', '1'], subject: $x);
-$x = bindec($x);
-
-$n = $x ^ 0x14B499C;
-
-echo "N = $n\n";
+
+
<?php
+
+$x =
+" # # \n" .
+"#####\n" .
+" # # \n" .
+"#####\n" .
+" # # ";
+
+$x = implode('', explode("\n", $x));
+$x = str_replace(search: [' ', '#'], replace: ['0', '1'], subject: $x);
+$x = bindec($x);
+
+$n = $x ^ 0x14B499C;
+
+echo "N = $n\n";
+

これを実行すると、N が得られる。 @@ -506,41 +531,45 @@ $🐘([ ソースコードはこちら。

-
<?php
-
-// License: https://creativecommons.org/publicdomain/zero/1.0/
-// This is a quine-like program to generate a PHPer token.
-// Execute it like this: php toquine.php | php | php | php | ...
-
-$s = <<<'Q'
-<?cuc
-// Yvprafr: uggcf://perngvirpbzzbaf.bet/choyvpqbznva/mreb/1.0/
-// Guvf vf n dhvar-yvxr cebtenz gb trarengr n CUCre gbxra.
-// Rkrphgr vg yvxr guvf: cuc gbdhvar.cuc | cuc | cuc | cuc | ...
-%f$f = %f;
-$f = fge_ebg13($f); $kf = [
-%f,
-];
-$g = ahyy.snyfr; sbe ($v = 0; $v <= vagqvi(__YVAR__-035,6); ++$v) vs (!vffrg($kf[$v])) oernx; ryfr
-$g .= vzcybqr("\a", fge_fcyvg(fge_ercynpr(['0','1'], ['  ','##'], fcevags(pue(37) . '025o', $kf[$v])), 012)) . "\a\a";
-$jf = neenl_znc(sa($j) => vzcybqr(', ', $j), neenl_puhax(neenl_znc(sa($k) => fcevags('0k' . pue(37) . '07K', $k), $kf), 10));
-cevags($f, $g, fge_ebg13("<<<'Q'\a{$f}\aQ"), vzcybqr(",\a", $jf));
-Q;
-$s = str_rot13($s); $xs = [
-0x0AFABEA, 0x1F294A7, 0x1F2109F, 0x1F294A7, 0x0002800, 0x1F2109F, 0x0117041, 0x1F294A7, 0x1FAD6B5, 0x1F295B7,
-0x010FC21, 0x1FAD6B5, 0x1151151, 0x010FC21, 0x1F294A7, 0x1F295B7, 0x1FAD6B5, 0x1F294A7, 0x1F295B7, 0x1F8C63F,
-0x1F8C631, 0x1FAD6B5, 0x17AD6BD, 0x17AD6BD, 0x1F8C63F, 0x1F295B7,
-];
-$t = null.false; for ($i = 0; $i <= intdiv(__LINE__-035,6); ++$i) if (!isset($xs[$i])) break; else
-$t .= implode("\n", str_split(str_replace(['0','1'], ['  ','##'], sprintf(chr(37) . '025b', $xs[$i])), 012)) . "\n\n";
-$ws = array_map(fn($w) => implode(', ', $w), array_chunk(array_map(fn($x) => sprintf('0x' . chr(37) . '07X', $x), $xs), 10));
-printf($s, $t, str_rot13("<<<'D'\n{$s}\nD"), implode(",\n", $ws));
+
+
<?php
+
+// License: https://creativecommons.org/publicdomain/zero/1.0/
+// This is a quine-like program to generate a PHPer token.
+// Execute it like this: php toquine.php | php | php | php | ...
+
+$s = <<<'Q'
+<?cuc
+// Yvprafr: uggcf://perngvirpbzzbaf.bet/choyvpqbznva/mreb/1.0/
+// Guvf vf n dhvar-yvxr cebtenz gb trarengr n CUCre gbxra.
+// Rkrphgr vg yvxr guvf: cuc gbdhvar.cuc | cuc | cuc | cuc | ...
+%f$f = %f;
+$f = fge_ebg13($f); $kf = [
+%f,
+];
+$g = ahyy.snyfr; sbe ($v = 0; $v <= vagqvi(__YVAR__-035,6); ++$v) vs (!vffrg($kf[$v])) oernx; ryfr
+$g .= vzcybqr("\a", fge_fcyvg(fge_ercynpr(['0','1'], ['  ','##'], fcevags(pue(37) . '025o', $kf[$v])), 012)) . "\a\a";
+$jf = neenl_znc(sa($j) => vzcybqr(', ', $j), neenl_puhax(neenl_znc(sa($k) => fcevags('0k' . pue(37) . '07K', $k), $kf), 10));
+cevags($f, $g, fge_ebg13("<<<'Q'\a{$f}\aQ"), vzcybqr(",\a", $jf));
+Q;
+$s = str_rot13($s); $xs = [
+0x0AFABEA, 0x1F294A7, 0x1F2109F, 0x1F294A7, 0x0002800, 0x1F2109F, 0x0117041, 0x1F294A7, 0x1FAD6B5, 0x1F295B7,
+0x010FC21, 0x1FAD6B5, 0x1151151, 0x010FC21, 0x1F294A7, 0x1F295B7, 0x1FAD6B5, 0x1F294A7, 0x1F295B7, 0x1F8C63F,
+0x1F8C631, 0x1FAD6B5, 0x17AD6BD, 0x17AD6BD, 0x1F8C63F, 0x1F295B7,
+];
+$t = null.false; for ($i = 0; $i <= intdiv(__LINE__-035,6); ++$i) if (!isset($xs[$i])) break; else
+$t .= implode("\n", str_split(str_replace(['0','1'], ['  ','##'], sprintf(chr(37) . '025b', $xs[$i])), 012)) . "\n\n";
+$ws = array_map(fn($w) => implode(', ', $w), array_chunk(array_map(fn($x) => sprintf('0x' . chr(37) . '07X', $x), $xs), 10));
+printf($s, $t, str_rot13("<<<'D'\n{$s}\nD"), implode(",\n", $ws));
+

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

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

実際にはもう少しパイプで繋げなければならない。 diff --git a/vhosts/blog/public/posts/2022-04-24/term-banner-write-tool-showing-banner-in-terminal/index.html b/vhosts/blog/public/posts/2022-04-24/term-banner-write-tool-showing-banner-in-terminal/index.html index d3d21364..ff722cda 100644 --- a/vhosts/blog/public/posts/2022-04-24/term-banner-write-tool-showing-banner-in-terminal/index.html +++ b/vhosts/blog/public/posts/2022-04-24/term-banner-write-tool-showing-banner-in-terminal/index.html @@ -13,8 +13,7 @@ term-banner: ターミナルにバナーを表示するツールを書いた|REPL: Rest-Eat-Program Loop - - +

@@ -61,7 +60,9 @@ こんなものを作った。

-
$ term-banner 'Hello, World!' 'こんにちは、' '世界!'
+
+
$ term-banner 'Hello, World!' 'こんにちは、' '世界!'
+
term-banner が動作している様子のスクリーンショット diff --git a/vhosts/blog/public/posts/2022-05-01/phperkaigi-2022/index.html b/vhosts/blog/public/posts/2022-05-01/phperkaigi-2022/index.html index 37c10d45..9ba40f11 100644 --- a/vhosts/blog/public/posts/2022-05-01/phperkaigi-2022/index.html +++ b/vhosts/blog/public/posts/2022-05-01/phperkaigi-2022/index.html @@ -14,8 +14,7 @@ PHPerKaigi 2022|REPL: Rest-Eat-Program Loop - - +
diff --git a/vhosts/blog/public/posts/2022-08-27/php-conference-okinawa-code-golf/index.html b/vhosts/blog/public/posts/2022-08-27/php-conference-okinawa-code-golf/index.html index 1f7ad6ba..ed87f02e 100644 --- a/vhosts/blog/public/posts/2022-08-27/php-conference-okinawa-code-golf/index.html +++ b/vhosts/blog/public/posts/2022-08-27/php-conference-okinawa-code-golf/index.html @@ -14,8 +14,7 @@ PHP カンファレンス沖縄で出題されたコードゴルフの問題を解いてみた|REPL: Rest-Eat-Program Loop - - +
@@ -121,7 +120,9 @@ 書いたものがこちら:

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

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

-
[<?php
-
-$n = $argv[1];
-foreach ([1e4, 5e3, 2e3, 1e3, 500, 100, 50, 10, 5, 1] as $x)
-  for (; $n >= $x; $n -= $x)
-    $r[] = $x;
-echo implode(', ', $r ?? []);
-
-?>]
+
+
[<?php
+
+$n = $argv[1];
+foreach ([1e4, 5e3, 2e3, 1e3, 500, 100, 50, 10, 5, 1] as $x)
+  for (; $n >= $x; $n -= $x)
+    $r[] = $x;
+echo implode(', ', $r ?? []);
+
+?>]
+
diff --git a/vhosts/blog/public/posts/2022-08-31/support-for-communty-is-employee-benefits/index.html b/vhosts/blog/public/posts/2022-08-31/support-for-communty-is-employee-benefits/index.html index fb44e927..9409eb8d 100644 --- a/vhosts/blog/public/posts/2022-08-31/support-for-communty-is-employee-benefits/index.html +++ b/vhosts/blog/public/posts/2022-08-31/support-for-communty-is-employee-benefits/index.html @@ -13,8 +13,7 @@ 弊社の PHP Foundation への寄付に寄せて|REPL: Rest-Eat-Program Loop - - +
diff --git a/vhosts/blog/public/posts/2022-09-29/write-fizzbuzz-in-php-2-letters-per-line/index.html b/vhosts/blog/public/posts/2022-09-29/write-fizzbuzz-in-php-2-letters-per-line/index.html index ab8e1136..44e3dc2e 100644 --- a/vhosts/blog/public/posts/2022-09-29/write-fizzbuzz-in-php-2-letters-per-line/index.html +++ b/vhosts/blog/public/posts/2022-09-29/write-fizzbuzz-in-php-2-letters-per-line/index.html @@ -14,8 +14,7 @@ 【PHP】 fizzbuzz を書く。1行あたり2文字で。|REPL: Rest-Eat-Program Loop - - +
@@ -125,80 +124,82 @@ 特に、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
+);
+
+/* あとは同じように普通のプログラムを変形するだけなので省略 */
+

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

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

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

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

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

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

array_walkrangeprintf といった for よりも長いトークンが現れてしまったが、これは次節で直すことにする。なお、echo は文 (statement) であり式 (expression) ではないので、式である printf に置き換えた。 @@ -309,18 +316,20 @@ fn($i) => 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 文字に収めるのか。次のテクニックへ移ろう。 @@ -345,26 +354,30 @@ fn($i) => というルールがない場合、「未定義の定数が評価された場合、その定数の名前が値になる」という PHP 7.x までの仕様が利用できる。例えば、 Fizz という文字列が欲しければ、次のようにする。

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

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

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

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

-
$a = "12345";
-$b = "world";
-
-// $a ^ $b は次のコードと同じ
-$result = '';
-for ($i = 0; $i < min(strlen($a), strlen($b)); $i++) {
-$result .= $a[$i] ^ $b[$i];
-}
-
-echo $result;
-// => F]AXQ
+
+
$a = "12345";
+$b = "world";
+
+// $a ^ $b は次のコードと同じ
+$result = '';
+for ($i = 0; $i < min(strlen($a), strlen($b)); $i++) {
+$result .= $a[$i] ^ $b[$i];
+}
+
+echo $result;
+// => F]AXQ
+

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

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

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

-
$x
-='x
-Om
-';
-$y
-='
-k!
-o'
-;
-
-$r = $x ^ $y;
-echo "$r\n";
+
+
$x
+='x
+Om
+';
+$y
+='
+k!
+o'
+;
+
+$r = $x ^ $y;
+echo "$r\n";
+

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

-
$x
-=#
-'x
-Om
-';
-$y
-='
-k!
-o'
-;#
-$r
-=#
-$x
-^#
-$y
-;#
-
-echo "$r\n";
+
+
$x
+=#
+'x
+Om
+';
+$y
+='
+k!
+o'
+;#
+$r
+=#
+$x
+^#
+$y
+;#
+
+echo "$r\n";
+

1行あたり2文字で、range という文字列を生成することに成功した。他の必要な文字列にも、同様の処理をほどこす。 @@ -458,155 +479,157 @@ o' 完成したものがこちら。

-
<?php
-
-$x
-=#
-'i
-S'
-;;
-$y
-='
-b!
-';
-$c
-=#
-$x
-^#
-$y
-;#
-$x
-=#
-'x
-Om
-';
-$y
-='
-k!
-o'
-;#
-$r
-=#
-$x
-^#
-$y
-;#
-$x
-=#
-'k
-Sk
-~}
-Ma
-';
-$y
-='
-x!
-s!
-k!
-';
-$w
-=#
-$x
-^#
-$y
-;#
-$x
-=#
-'z
-Hd
-G'
-;#
-$y
-='
-x!
-~!
-';
-$p
-=#
-$x
-^#
-$y
-;#
-$x
-=#
-'L
-[p
-';
-$y
-='
-c!
-';
-$f
-=#
-$x
-^#
-$y
-;#
-$x
-=#
-'H
-[p
-';
-$y
-='
-_!
-';
-$b
-=#
-$x
-^#
-$y
-;#
-$b
-[1
-]=
-$c
-(#
-13
-*9
-);
-$s
-=#
-$r
-(1
-,(
-10
-**
-2)
-);
-$w
-(#
-$s
-,#
-fn
-(#
-$i
-)#
-=>
-$p
-((
-(#
-$i
-%3
-?#
-''
-:#
-$f
-).
-(#
-$i
-%5
-?#
-''
-:#
-$b
-)?
-:#
-$i
-)#
-.'
-')
-);
+
+
<?php
+
+$x
+=#
+'i
+S'
+;;
+$y
+='
+b!
+';
+$c
+=#
+$x
+^#
+$y
+;#
+$x
+=#
+'x
+Om
+';
+$y
+='
+k!
+o'
+;#
+$r
+=#
+$x
+^#
+$y
+;#
+$x
+=#
+'k
+Sk
+~}
+Ma
+';
+$y
+='
+x!
+s!
+k!
+';
+$w
+=#
+$x
+^#
+$y
+;#
+$x
+=#
+'z
+Hd
+G'
+;#
+$y
+='
+x!
+~!
+';
+$p
+=#
+$x
+^#
+$y
+;#
+$x
+=#
+'L
+[p
+';
+$y
+='
+c!
+';
+$f
+=#
+$x
+^#
+$y
+;#
+$x
+=#
+'H
+[p
+';
+$y
+='
+_!
+';
+$b
+=#
+$x
+^#
+$y
+;#
+$b
+[1
+]=
+$c
+(#
+13
+*9
+);
+$s
+=#
+$r
+(1
+,(
+10
+**
+2)
+);
+$w
+(#
+$s
+,#
+fn
+(#
+$i
+)#
+=>
+$p
+((
+(#
+$i
+%3
+?#
+''
+:#
+$f
+).
+(#
+$i
+%5
+?#
+''
+:#
+$b
+)?
+:#
+$i
+)#
+.'
+')
+);
+
@@ -626,18 +649,20 @@ _! 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 という文字列を合成して可変関数で呼び出す。 @@ -663,56 +688,62 @@ o\ もうこれ以上は不可能だと思っていたのだが、この記事の執筆中に解決する方法を思いついたので載せておく。

-
<?php
-
-$c = 'chr';
-
-${
-'_
-'}
-=#
-$c
-(#
-32
-).
-$c
-(#
-92
-);
-
-printf(`
-e\
-c\
-h\
-o\
-${
-'_
-'}
-1\
-2\
-3\
-`);
+
+
<?php
+
+$c = 'chr';
+
+${
+'_
+'}
+=#
+$c
+(#
+32
+).
+$c
+(#
+92
+);
+
+printf(`
+e\
+c\
+h\
+o\
+${
+'_
+'}
+1\
+2\
+3\
+`);
+

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

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

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

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

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

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

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

@@ -86,27 +85,29 @@ 注意: これはボツ問なので、得られたトークンを 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";
+}
+
@@ -115,15 +116,19 @@ $π = trim($π); ソースを見るとわかるとおり、$argv[1] を参照している。それを なる変数に代入しているので、円周率を渡してみる。

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

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

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

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

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

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

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

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

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

を 2 文字ごとに区切り (str_split)、数値を ASCII コードと見做して文字に変換 (chr) して結合 (implode) している。 @@ -170,13 +181,17 @@ $π = trim($π); 例えば、'656667' だったとすると、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 におけるトークンである。 @@ -186,11 +201,13 @@ $π = trim($π); なお、# を直接書いていないのは、/#.+?) / と書くと、#.+?) という意図せぬトークンが登録されてしまうからである。

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

最後にトークンのハッシュ値を見て、想定解かどうかを確認する。 diff --git a/vhosts/blog/public/posts/2022-10-28/setup-server-for-this-site/index.html b/vhosts/blog/public/posts/2022-10-28/setup-server-for-this-site/index.html index e6644a58..cce709ac 100644 --- a/vhosts/blog/public/posts/2022-10-28/setup-server-for-this-site/index.html +++ b/vhosts/blog/public/posts/2022-10-28/setup-server-for-this-site/index.html @@ -14,8 +14,7 @@ 【備忘録】 このサイト用の VPS をセットアップしたときのメモ|REPL: Rest-Eat-Program Loop - - +

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

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

teika.key はローカルからサーバへの接続用、github2teika.key は、GitHub Actions からサーバへのデプロイ用。 @@ -108,12 +109,14 @@ $ ssh-keygen -t ed25519 -b 521 -f ~/.ssh/github2teika.key .ssh/config に設定しておく。

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

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

ホスト名を変える

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

公開鍵を置く

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

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

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

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

-
$ ssh teika
+
+
$ ssh teika
+
@@ -200,11 +215,13 @@ $ sudo systemctl status sshd デフォルトの 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 の接続確認を挟む。 @@ -217,40 +234,50 @@ $ sudo ufw status GitHub に置いてある private リポジトリをサーバから clone したいので、SSH 鍵を生成して置いておく。

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

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

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

設定はこう。

-
Host github.com
-    HostName github.com
-    User git
-    Port 22
-    IdentityFile ~/.ssh/github.key
-    IdentitiesOnly yes
+
+
Host github.com
+    HostName github.com
+    User git
+    Port 22
+    IdentityFile ~/.ssh/github.key
+    IdentitiesOnly yes
+

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

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

パッケージの更新

-
$ sudo apt update
-$ sudo apt upgrade
-$ sudo apt update
-$ sudo apt upgrade
-$ sudo apt autoremove
+
+
$ sudo apt update
+$ sudo apt upgrade
+$ sudo apt update
+$ sudo apt upgrade
+$ sudo apt autoremove
+
@@ -265,12 +292,16 @@ $ sudo apt autoremove

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

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

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

-
$ sudo adduser ********** docker
+
+
$ sudo adduser ********** docker
+
@@ -279,29 +310,37 @@ $ sudo apt autoremove 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/vhosts/blog/public/posts/2022-11-19/phperkaigi-2023-unused-token-quiz-2/index.html b/vhosts/blog/public/posts/2022-11-19/phperkaigi-2023-unused-token-quiz-2/index.html index 147d7a81..e093f669 100644 --- a/vhosts/blog/public/posts/2022-11-19/phperkaigi-2023-unused-token-quiz-2/index.html +++ b/vhosts/blog/public/posts/2022-11-19/phperkaigi-2023-unused-token-quiz-2/index.html @@ -14,8 +14,7 @@ PHPerKaigi 2023: ボツになったトークン問題 その 2|REPL: Rest-Eat-Program Loop - - +
@@ -90,16 +89,18 @@ 注意: これはボツ問なので、得られたトークンを 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 (自分自身と同じソースコードを出力するプログラム) になっている。 @@ -112,46 +113,52 @@ 実行してみると、次のような出力が得られる。

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

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

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

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

-
#
-W
-E
-L
-O
-V
-E
-P
-H
-P
+
+
#
+W
+E
+L
+O
+V
+E
+P
+H
+P
+

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

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

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

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

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

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

このソースコードは UTF-8 で書かれているので、105 バイトになる。それを 3 で割ると 35 となり、これは # の ASCII コードと一致する。他の行も、同様にしてゼロ幅スペースを詰めることで文字列長を調整し、トークンをエンコードしている。 diff --git a/vhosts/blog/public/posts/2023-01-10/phperkaigi-2023-unused-token-quiz-3/index.html b/vhosts/blog/public/posts/2023-01-10/phperkaigi-2023-unused-token-quiz-3/index.html index 38e6a3f6..6cad79cb 100644 --- a/vhosts/blog/public/posts/2023-01-10/phperkaigi-2023-unused-token-quiz-3/index.html +++ b/vhosts/blog/public/posts/2023-01-10/phperkaigi-2023-unused-token-quiz-3/index.html @@ -14,8 +14,7 @@ PHPerKaigi 2023: ボツになったトークン問題 その 3|REPL: Rest-Eat-Program Loop - - +

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

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

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

-
<?php
-
-try {
-  try {
-    throw new Exception("Error 1");
-  } finally {
-    throw new Exception("Error 2");
-  }
-} catch (Exception $e) {
-  echo $e->getMessage() . PHP_EOL;
-  // => Error 2
-  echo $e->getPrevious()->getMessage() . PHP_EOL;
-  // => Error 1
-}
+
+
<?php
+
+try {
+  try {
+    throw new Exception("Error 1");
+  } finally {
+    throw new Exception("Error 2");
+  }
+} catch (Exception $e) {
+  echo $e->getMessage() . PHP_EOL;
+  // => Error 2
+  echo $e->getPrevious()->getMessage() . PHP_EOL;
+  // => Error 1
+}
+

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

-
<?php
-try {
-  f(g() / __LINE__);
-} catch (Throwable $e) {
-  while ($e = $e->getPrevious()) {
-    printf('%c', $e->getLine() + 23);
-  }
-  echo "\n";
-}
+
+
<?php
+try {
+  f(g() / __LINE__);
+} catch (Throwable $e) {
+  while ($e = $e->getPrevious()) {
+    printf('%c', $e->getLine() + 23);
+  }
+  echo "\n";
+}
+

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

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

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

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

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

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

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

diff --git a/vhosts/blog/public/posts/2023-04-01/implementation-of-minimal-png-image-encoder/index.html b/vhosts/blog/public/posts/2023-04-01/implementation-of-minimal-png-image-encoder/index.html index 4255b561..11831bdf 100644 --- a/vhosts/blog/public/posts/2023-04-01/implementation-of-minimal-png-image-encoder/index.html +++ b/vhosts/blog/public/posts/2023-04-01/implementation-of-minimal-png-image-encoder/index.html @@ -13,8 +13,7 @@ PNG 画像の最小構成エンコーダを実装する|REPL: Rest-Eat-Program Loop - - +
@@ -98,44 +97,46 @@ 以下のソースコードをベースにする。今回 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 などを実装していく。 @@ -189,21 +190,23 @@ writeSignature の実装はこちら:

-
import "encoding/binary"
-
-func writeSignature(w io.Writer) {
-	sig := [8]uint8{
-		0x89,
-		0x50, // P
-		0x4E, // N
-		0x47, // G
-		0x0D, // CR
-		0x0A, // LF
-		0x1A, // EOF (^Z)
-		0x0A, // LF
-	}
-	binary.Write(w, binary.BigEndian, sig)
-}
+
+
import "encoding/binary"
+
+func writeSignature(w io.Writer) {
+	sig := [8]uint8{
+		0x89,
+		0x50, // P
+		0x4E, // N
+		0x47, // G
+		0x0D, // CR
+		0x0A, // LF
+		0x1A, // EOF (^Z)
+		0x0A, // LF
+	}
+	binary.Write(w, binary.BigEndian, sig)
+}
+

encoding/binary パッケージの binary.Write を使い、固定の 8 バイトを書き込む。 @@ -238,55 +241,59 @@ CRC (Cyclic Redundancy Check) は誤り検出符号の一種。Go 言語では hash/crc32 パッケージにあるが、今回はこれも自前で実装する。PNG の仕様書に C 言語のサンプルコードが載っている (D. Sample CRC implementation) ので、これを Go に移植する。

-
var (
-	crcTable         [256]uint32
-	crcTableComputed bool
-)
-
-func makeCrcTable() {
-	for n := 0; n < 256; n++ {
-		c := uint32(n)
-		for k := 0; k < 8; k++ {
-			if (c & 1) != 0 {
-				c = 0xEDB88320 ^ (c >> 1)
-			} else {
-				c = c >> 1
-			}
-		}
-		crcTable[n] = c
-	}
-	crcTableComputed = true
-}
-
-func updateCrc(crc uint32, buf []byte) uint32 {
-	if !crcTableComputed {
-		makeCrcTable()
-	}
-
-	c := crc
-	for n := 0; n < len(buf); n++ {
-		c = crcTable[(c^uint32(buf[n]))&0xFF] ^ (c >> 8)
-	}
-	return c
-}
-
-func crc(buf []byte) uint32 {
-	return updateCrc(0xFFFFFFFF, buf) ^ 0xFFFFFFFF
-}
+
+
var (
+	crcTable         [256]uint32
+	crcTableComputed bool
+)
+
+func makeCrcTable() {
+	for n := 0; n < 256; n++ {
+		c := uint32(n)
+		for k := 0; k < 8; k++ {
+			if (c & 1) != 0 {
+				c = 0xEDB88320 ^ (c >> 1)
+			} else {
+				c = c >> 1
+			}
+		}
+		crcTable[n] = c
+	}
+	crcTableComputed = true
+}
+
+func updateCrc(crc uint32, buf []byte) uint32 {
+	if !crcTableComputed {
+		makeCrcTable()
+	}
+
+	c := crc
+	for n := 0; n < len(buf); n++ {
+		c = crcTable[(c^uint32(buf[n]))&0xFF] ^ (c >> 8)
+	}
+	return c
+}
+
+func crc(buf []byte) uint32 {
+	return updateCrc(0xFFFFFFFF, buf) ^ 0xFFFFFFFF
+}
+

できた crc 関数を使って、chunk 一般を書き込む関数も用意しておこう。

-
func writeChunk(w io.Writer, chunkType string, data []byte) {
-	typeAndData := make([]byte, 0, len(chunkType)+len(data))
-	typeAndData = append(typeAndData, []byte(chunkType)...)
-	typeAndData = append(typeAndData, data...)
-
-	binary.Write(w, binary.BigEndian, uint32(len(data)))
-	binary.Write(w, binary.BigEndian, typeAndData)
-	binary.Write(w, binary.BigEndian, crc(typeAndData))
-}
+
+
func writeChunk(w io.Writer, chunkType string, data []byte) {
+	typeAndData := make([]byte, 0, len(chunkType)+len(data))
+	typeAndData = append(typeAndData, []byte(chunkType)...)
+	typeAndData = append(typeAndData, data...)
+
+	binary.Write(w, binary.BigEndian, uint32(len(data)))
+	binary.Write(w, binary.BigEndian, typeAndData)
+	binary.Write(w, binary.BigEndian, crc(typeAndData))
+}
+

仕様どおり、chunkTypedata から CRC を計算し、data の長さと合わせて書き込んでいる。PNG では基本的に big endian を使うことに注意する。 @@ -372,20 +379,22 @@ 今回ほとんどのデータは決め打ちするので、データに応じて変わるのは width と height だけになる。コードは次のようになる。

-
import "bytes"
-
-func writeChunkIhdr(w io.Writer, width, height uint32) {
-	var buf bytes.Buffer
-	binary.Write(&buf, binary.BigEndian, width)
-	binary.Write(&buf, binary.BigEndian, height)
-	binary.Write(&buf, binary.BigEndian, uint8(8))
-	binary.Write(&buf, binary.BigEndian, uint8(2))
-	binary.Write(&buf, binary.BigEndian, uint8(0))
-	binary.Write(&buf, binary.BigEndian, uint8(0))
-	binary.Write(&buf, binary.BigEndian, uint8(0))
-
-	writeChunk(w, "IHDR", buf.Bytes())
-}
+
+
import "bytes"
+
+func writeChunkIhdr(w io.Writer, width, height uint32) {
+	var buf bytes.Buffer
+	binary.Write(&buf, binary.BigEndian, width)
+	binary.Write(&buf, binary.BigEndian, height)
+	binary.Write(&buf, binary.BigEndian, uint8(8))
+	binary.Write(&buf, binary.BigEndian, uint8(2))
+	binary.Write(&buf, binary.BigEndian, uint8(0))
+	binary.Write(&buf, binary.BigEndian, uint8(0))
+	binary.Write(&buf, binary.BigEndian, uint8(0))
+
+	writeChunk(w, "IHDR", buf.Bytes())
+}
+
@@ -426,22 +435,24 @@ 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 の実装をサボるために使う。 @@ -473,30 +484,32 @@ 実際にこの手抜き zlib を実装したものがこちら:

-
func encodeZlib(data []byte) []byte {
-	var buf bytes.Buffer
-
-	binary.Write(&buf, binary.BigEndian, uint8(0x78))
-	binary.Write(&buf, binary.BigEndian, uint8(0x01))
-	blockSize := 65535
-	isFinalBlock := false
-	for i := 0; !isFinalBlock; i++ {
-		var block []byte
-		if len(data) <= (i+1)*blockSize {
-			block = data[i*blockSize:]
-			isFinalBlock = true
-		} else {
-			block = data[i*blockSize : (i+1)*blockSize]
-		}
-		binary.Write(&buf, binary.BigEndian, isFinalBlock)
-		binary.Write(&buf, binary.LittleEndian, uint16(len(block)))
-		binary.Write(&buf, binary.LittleEndian, uint16(^len(block)))
-		binary.Write(&buf, binary.LittleEndian, block)
-	}
-	binary.Write(&buf, binary.BigEndian, adler32(data))
-
-	return buf.Bytes()
-}
+
+
func encodeZlib(data []byte) []byte {
+	var buf bytes.Buffer
+
+	binary.Write(&buf, binary.BigEndian, uint8(0x78))
+	binary.Write(&buf, binary.BigEndian, uint8(0x01))
+	blockSize := 65535
+	isFinalBlock := false
+	for i := 0; !isFinalBlock; i++ {
+		var block []byte
+		if len(data) <= (i+1)*blockSize {
+			block = data[i*blockSize:]
+			isFinalBlock = true
+		} else {
+			block = data[i*blockSize : (i+1)*blockSize]
+		}
+		binary.Write(&buf, binary.BigEndian, isFinalBlock)
+		binary.Write(&buf, binary.LittleEndian, uint16(len(block)))
+		binary.Write(&buf, binary.LittleEndian, uint16(^len(block)))
+		binary.Write(&buf, binary.LittleEndian, block)
+	}
+	binary.Write(&buf, binary.BigEndian, adler32(data))
+
+	return buf.Bytes()
+}
+
@@ -513,20 +526,22 @@ 先ほどの 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()))
+}
+
@@ -540,9 +555,11 @@ 特に追加のデータはなく、必要なのは chunk type の IEND くらいなので実装は簡単:

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

-
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/vhosts/blog/public/posts/2023-04-04/phperkaigi-2023-report/index.html b/vhosts/blog/public/posts/2023-04-04/phperkaigi-2023-report/index.html index de619ab8..5f6c6bec 100644 --- a/vhosts/blog/public/posts/2023-04-04/phperkaigi-2023-report/index.html +++ b/vhosts/blog/public/posts/2023-04-04/phperkaigi-2023-report/index.html @@ -14,8 +14,7 @@ PHPerKaigi 2023 参加レポ|REPL: Rest-Eat-Program Loop - - +
diff --git a/vhosts/blog/public/posts/2023-06-25/phpconfuk-2023-report/index.html b/vhosts/blog/public/posts/2023-06-25/phpconfuk-2023-report/index.html index 93fe3e09..8e2d179f 100644 --- a/vhosts/blog/public/posts/2023-06-25/phpconfuk-2023-report/index.html +++ b/vhosts/blog/public/posts/2023-06-25/phpconfuk-2023-report/index.html @@ -14,8 +14,7 @@ PHP カンファレンス福岡 2023 参加レポ|REPL: Rest-Eat-Program Loop - - +
diff --git a/vhosts/blog/public/posts/2023-10-02/compile-php-runtime-to-wasm/index.html b/vhosts/blog/public/posts/2023-10-02/compile-php-runtime-to-wasm/index.html index 0e00dead..35252f62 100644 --- a/vhosts/blog/public/posts/2023-10-02/compile-php-runtime-to-wasm/index.html +++ b/vhosts/blog/public/posts/2023-10-02/compile-php-runtime-to-wasm/index.html @@ -14,8 +14,7 @@ PHP の処理系を Emscripten で WebAssembly にコンパイルする|REPL: Rest-Eat-Program Loop - - +
@@ -111,18 +110,20 @@ 先にこの記事のゴールを示しておく。これから示す手順のとおりに進めると、次のようなコードが動くようになる。このコードはこのあと使うので、index.mjs の名前で保存しておくこと。

-
import { readFile } from 'node:fs/promises';
-import PHPWasm from './php-wasm.mjs'
-
-const code = await readFile('/dev/stdin', { encoding: 'utf-8' });
-
-const { ccall } = await PHPWasm();
-const result = ccall(
-  'php_wasm_run',
-  'number', ['string'],
-  [code],
-);
-console.log(`exit code: ${result}`);
+
+
import { readFile } from 'node:fs/promises';
+import PHPWasm from './php-wasm.mjs'
+
+const code = await readFile('/dev/stdin', { encoding: 'utf-8' });
+
+const { ccall } = await PHPWasm();
+const result = ccall(
+  'php_wasm_run',
+  'number', ['string'],
+  [code],
+);
+console.log(`exit code: ${result}`);
+

標準入力から与えたコードを WebAssembly にコンパイルされた PHP 処理系の上で実行している。このような php-wasm.mjs (とそこから呼び出される php-wasm.wasm) を作成する。 @@ -137,30 +138,32 @@ 先ほどのコードでも使っていたエントリポイントである php_wasm_run を用意する。

-
#include <stdio.h>
-#include <emscripten.h>
-#include <Zend/zend_execute.h>
-#include <sapi/embed/php_embed.h>
-
-int EMSCRIPTEN_KEEPALIVE php_wasm_run(const char* code) {
-    zend_result result;
-
-    int argc = 1;
-    char* argv[] = { "php.wasm", NULL };
-
-    PHP_EMBED_START_BLOCK(argc, argv);
-
-    result = zend_eval_string_ex(code, NULL, "php.wasm code", 1);
-
-    PHP_EMBED_END_BLOCK();
-
-    fprintf(stdout, "\n");
-    fflush(stdout);
-    fprintf(stderr, "\n");
-    fflush(stderr);
-
-    return result == SUCCESS ? 0 : 1;
-}
+
+
#include <stdio.h>
+#include <emscripten.h>
+#include <Zend/zend_execute.h>
+#include <sapi/embed/php_embed.h>
+
+int EMSCRIPTEN_KEEPALIVE php_wasm_run(const char* code) {
+    zend_result result;
+
+    int argc = 1;
+    char* argv[] = { "php.wasm", NULL };
+
+    PHP_EMBED_START_BLOCK(argc, argv);
+
+    result = zend_eval_string_ex(code, NULL, "php.wasm code", 1);
+
+    PHP_EMBED_END_BLOCK();
+
+    fprintf(stdout, "\n");
+    fflush(stdout);
+    fprintf(stderr, "\n");
+    fflush(stderr);
+
+    return result == SUCCESS ? 0 : 1;
+}
+

ほとんどはただの PHP の公開 API を使ったコードだが、Emscripten 向けの注意点が 2点ある。 @@ -185,49 +188,55 @@ まずは Emscripten 公式が提供している Docker イメージを使って、PHP 処理系と先ほど示した C 言語のソースコードを WebAssembly にコンパイルする。

-
FROM emscripten/emsdk:3.1.46 AS wasm-builder
+
+
FROM emscripten/emsdk:3.1.46 AS wasm-builder
+

次に、php/php-src から PHP 処理系のソースコードを取得し、ビルドに必要な apt パッケージを取ってくる。有効にする拡張を増やしたいなら、ここでインストールするパッケージも増やすことになるだろう。

-
RUN git clone --depth=1 --branch=php-8.2.10 https://github.com/php/php-src
-
-RUN apt-get update && \
-    apt-get install -y --no-install-recommends \
-        autoconf \
-        bison \
-        pkg-config \
-        re2c \
-        && \
-    :
+
+
RUN git clone --depth=1 --branch=php-8.2.10 https://github.com/php/php-src
+
+RUN apt-get update && \
+    apt-get install -y --no-install-recommends \
+        autoconf \
+        bison \
+        pkg-config \
+        re2c \
+        && \
+    :
+

続けて、Emscripten のツールチェインを用いて PHP 処理系をビルドする。

-
RUN cd php-src && \
-    ./buildconf --force && \
-    emconfigure ./configure \
-        --disable-all \
-        --disable-mbregex \
-        --disable-fiber-asm \
-        --disable-cli \
-        --disable-cgi \
-        --disable-phpdbg \
-        --enable-embed=static \
-        --enable-mbstring \
-        --without-iconv \
-        --without-libxml \
-        --without-pcre-jit \
-        --without-pdo-sqlite \
-        --without-sqlite3 \
-        && \
-    EMCC_CFLAGS='-s ERROR_ON_UNDEFINED_SYMBOLS=0' emmake make -j$(nproc) && \
-    mv libs/libphp.a .. && \
-    make clean && \
-    git clean -fd && \
-    :
+
+
RUN cd php-src && \
+    ./buildconf --force && \
+    emconfigure ./configure \
+        --disable-all \
+        --disable-mbregex \
+        --disable-fiber-asm \
+        --disable-cli \
+        --disable-cgi \
+        --disable-phpdbg \
+        --enable-embed=static \
+        --enable-mbstring \
+        --without-iconv \
+        --without-libxml \
+        --without-pcre-jit \
+        --without-pdo-sqlite \
+        --without-sqlite3 \
+        && \
+    EMCC_CFLAGS='-s ERROR_ON_UNDEFINED_SYMBOLS=0' emmake make -j$(nproc) && \
+    mv libs/libphp.a .. && \
+    make clean && \
+    git clean -fd && \
+    :
+

ここまでと比べると少し複雑なので、それぞれ詳しく見ていこう。 @@ -257,22 +266,24 @@ さて、PHP 処理系をライブラリ化できたので、次に先ほど載せた C のソースコードをビルドしていこう。Dockerfile と同じ場所に php-wasm.c という名前で保存し、次のようにする。

-
COPY php-wasm.c /src/
-
-RUN cd php-src && \
-    emcc \
-        -c \
-        -o php-wasm.o \
-        -I . \
-        -I TSRM \
-        -I Zend \
-        -I main \
-        ../php-wasm.c \
-        && \
-    mv php-wasm.o .. && \
-    make clean && \
-    git clean -fd && \
-    :
+
+
COPY php-wasm.c /src/
+
+RUN cd php-src && \
+    emcc \
+        -c \
+        -o php-wasm.o \
+        -I . \
+        -I TSRM \
+        -I Zend \
+        -I main \
+        ../php-wasm.c \
+        && \
+    mv php-wasm.o .. && \
+    make clean && \
+    git clean -fd && \
+    :
+

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

それぞれのフラグについて解説する。 @@ -335,14 +348,16 @@ といっても、Node.js はビルトインで WebAssembly をサポートしているので、ほとんどやることはない。先ほど掲載した JavaScript のコードは、Dockerfile と同じディレクトリに index.mjs で配置すること。

-
FROM node:20.7
-
-WORKDIR /app
-COPY --from=wasm-builder /src/php-wasm.js /app/php-wasm.mjs
-COPY --from=wasm-builder /src/php-wasm.wasm /app/php-wasm.wasm
-COPY index.mjs /app/
-
-ENTRYPOINT ["node", "index.mjs"]
+
+
FROM node:20.7
+
+WORKDIR /app
+COPY --from=wasm-builder /src/php-wasm.js /app/php-wasm.mjs
+COPY --from=wasm-builder /src/php-wasm.wasm /app/php-wasm.wasm
+COPY index.mjs /app/
+
+ENTRYPOINT ["node", "index.mjs"]
+
@@ -352,12 +367,14 @@ 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/vhosts/blog/public/posts/2023-10-13/i-entered-the-open-university-of-japan/index.html b/vhosts/blog/public/posts/2023-10-13/i-entered-the-open-university-of-japan/index.html index d6c63557..ac13669c 100644 --- a/vhosts/blog/public/posts/2023-10-13/i-entered-the-open-university-of-japan/index.html +++ b/vhosts/blog/public/posts/2023-10-13/i-entered-the-open-university-of-japan/index.html @@ -14,8 +14,7 @@ 放送大学に入学しました|REPL: Rest-Eat-Program Loop - - +
diff --git a/vhosts/blog/public/posts/2023-12-03/isucon-13/index.html b/vhosts/blog/public/posts/2023-12-03/isucon-13/index.html index fdc94f3a..63f6e8f4 100644 --- a/vhosts/blog/public/posts/2023-12-03/isucon-13/index.html +++ b/vhosts/blog/public/posts/2023-12-03/isucon-13/index.html @@ -14,8 +14,7 @@ ISUCON 13 に参加した|REPL: Rest-Eat-Program Loop - - +
diff --git a/vhosts/blog/public/posts/2023-12-31/2023-reflections/index.html b/vhosts/blog/public/posts/2023-12-31/2023-reflections/index.html index 1e4f364b..355f11bd 100644 --- a/vhosts/blog/public/posts/2023-12-31/2023-reflections/index.html +++ b/vhosts/blog/public/posts/2023-12-31/2023-reflections/index.html @@ -13,8 +13,7 @@ 2023年の振り返り|REPL: Rest-Eat-Program Loop - - +
diff --git a/vhosts/blog/public/posts/2024-01-10/neovim-insert-namespace-declaration-to-empty-php-file/index.html b/vhosts/blog/public/posts/2024-01-10/neovim-insert-namespace-declaration-to-empty-php-file/index.html index 1e7cd5ee..07043627 100644 --- a/vhosts/blog/public/posts/2024-01-10/neovim-insert-namespace-declaration-to-empty-php-file/index.html +++ b/vhosts/blog/public/posts/2024-01-10/neovim-insert-namespace-declaration-to-empty-php-file/index.html @@ -14,8 +14,7 @@ 【Neovim】 空の PHP ファイルに namespace 宣言を挿入する|REPL: Rest-Eat-Program Loop - - +
@@ -76,17 +75,21 @@ 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 には対応していない。 @@ -114,13 +117,15 @@ LuaJIT 2.1.1693350652 ファイルタイプは読み込んだあとに変更されることもあるので、ftplugin は複数回実行されうる。二重読み込みを防ぐために、did_ftplugin_<FILE_TYPE>_after というバッファローカル変数を定義しておくのが慣習となっている。

-
if vim.b.did_ftplugin_php_after then
-   return
-end
-
--- ここに実際の処理を書く
-
-vim.b.did_ftplugin_php_after = true
+
+
if vim.b.did_ftplugin_php_after then
+   return
+end
+
+-- ここに実際の処理を書く
+
+vim.b.did_ftplugin_php_after = true
+
@@ -159,119 +164,121 @@ vim.b.did_ftplugin_php_after = true -
if vim.b.did_ftplugin_php_after then
-   return
-end
-
--- base_dir を起点としてディレクトリを上向きに辿っていき、composer.json を探す
--- :help vim.fs.find()
-local function find_composer_json(base_dir)
-   return vim.fs.find('composer.json', {
-      path = base_dir,
-      upward = true,
-      -- ホームディレクトリまで到達したら探索を打ち切る
-      stop = vim.loop.os_homedir(),
-      type = 'file',
-   })[1]
-end
-
--- JSON ファイルを読み込み、デコードして返す
--- :help readblob()
--- :help vim.json.decode
--- :help luaref-pcall()
-local function load_json(file_path)
-   -- readblob() は Vim script では Blob オブジェクトを返すが、Lua から呼ぶと string に変換される
-   local ok_read, content = pcall(vim.fn.readblob, file_path)
-   if not ok_read then
-      return nil
-   end
-   local ok_decode, obj = pcall(vim.json.decode, content)
-   if not ok_decode then
-      return nil
-   end
-   return obj
-end
-
--- 対象ファイルの置かれたディレクトリを基に namespace 宣言を生成する
--- :help nvim_buf_get_name()
--- :help vim.fs.dirname()
-local function generate_namespace_declaration()
-   -- composer.json を探し、トップレベルの名前空間とディレクトリを特定する
-   local current_dir = vim.fs.dirname(vim.api.nvim_buf_get_name(0))
-   local path_to_composer_json = find_composer_json(current_dir)
-   if not path_to_composer_json then
-      return nil -- failed to locate composer.json
-   end
-   local composer_json = load_json(path_to_composer_json)
-   if not composer_json then
-      return nil -- failed to load composer.json
-   end
-   -- autoload.psr-4 を探し、型が期待される型と一致するかどうか調べる
-   local psr4 = vim.tbl_get(composer_json, 'autoload', 'psr-4')
-   if not psr4 then
-      return nil -- autoload.psr-4 section is absent
-   end
-   if vim.tbl_count(psr4) ~= 1 then
-      return nil -- psr-4 section is ambiguous
-   end
-   local psr4_namespace, psr4_dir
-   for k, v in pairs(psr4) do
-      psr4_namespace = k
-      psr4_dir = v
-   end
-   if type(psr4_dir) == 'table' then
-      if #psr4_dir == 1 then
-         psr4_dir = psr4_dir[1]
-      else
-         return nil -- psr-4 section is ambiguous
-      end
-   end
-   if type(psr4_namespace) ~= 'string' or type(psr4_dir) ~= 'string' then
-      return nil -- psr-4 section is invalid
-   end
-   -- 末尾のスラッシュとバックスラッシュを取り除いておく
-   if psr4_namespace:sub(-1, -1) == '\\' then
-      psr4_namespace = psr4_namespace:sub(0, -2)
-   end
-   if psr4_dir:sub(-1, -1) == '/' then
-      psr4_dir = psr4_dir:sub(0, -2)
-   end
-
-   -- 対象ファイルが置かれたディレクトリとトップレベルのディレクトリを比較し、その差分を名前空間とする
-   local namespace_root_dir = vim.fs.dirname(path_to_composer_json) .. '/' .. psr4_dir
-   if not vim.startswith(current_dir, namespace_root_dir) then
-      return nil
-   end
-   local current_path_suffix = current_dir:sub(#namespace_root_dir + 1)
-   local namespace = psr4_namespace .. current_path_suffix:gsub('/', '\\')
-   return ("namespace %s;"):format(namespace)
-end
-
-local function generate_template()
-   local lines = {
-      '<?php',
-      '',
-      'declare(strict_types=1);',
-      '',
-   }
-   local namespace_decl = generate_namespace_declaration()
-   if namespace_decl then
-      lines[#lines + 1] = namespace_decl
-      lines[#lines + 1] = ''
-   end
-   lines[#lines + 1] = ''
-   return lines
-end
-
-if vim.fn.line('$') == 1 and vim.fn.getline(1) == '' then
-   -- 対象ファイルが空なら、テンプレートを挿入してカーソルを末尾に移動させる
-   -- :help setline()
-   -- :help cursor()
-   vim.fn.setline(1, generate_template())
-   vim.fn.cursor('$', 0)
-end
-
-vim.b.did_ftplugin_php_after = true
+
+
if vim.b.did_ftplugin_php_after then
+   return
+end
+
+-- base_dir を起点としてディレクトリを上向きに辿っていき、composer.json を探す
+-- :help vim.fs.find()
+local function find_composer_json(base_dir)
+   return vim.fs.find('composer.json', {
+      path = base_dir,
+      upward = true,
+      -- ホームディレクトリまで到達したら探索を打ち切る
+      stop = vim.loop.os_homedir(),
+      type = 'file',
+   })[1]
+end
+
+-- JSON ファイルを読み込み、デコードして返す
+-- :help readblob()
+-- :help vim.json.decode
+-- :help luaref-pcall()
+local function load_json(file_path)
+   -- readblob() は Vim script では Blob オブジェクトを返すが、Lua から呼ぶと string に変換される
+   local ok_read, content = pcall(vim.fn.readblob, file_path)
+   if not ok_read then
+      return nil
+   end
+   local ok_decode, obj = pcall(vim.json.decode, content)
+   if not ok_decode then
+      return nil
+   end
+   return obj
+end
+
+-- 対象ファイルの置かれたディレクトリを基に namespace 宣言を生成する
+-- :help nvim_buf_get_name()
+-- :help vim.fs.dirname()
+local function generate_namespace_declaration()
+   -- composer.json を探し、トップレベルの名前空間とディレクトリを特定する
+   local current_dir = vim.fs.dirname(vim.api.nvim_buf_get_name(0))
+   local path_to_composer_json = find_composer_json(current_dir)
+   if not path_to_composer_json then
+      return nil -- failed to locate composer.json
+   end
+   local composer_json = load_json(path_to_composer_json)
+   if not composer_json then
+      return nil -- failed to load composer.json
+   end
+   -- autoload.psr-4 を探し、型が期待される型と一致するかどうか調べる
+   local psr4 = vim.tbl_get(composer_json, 'autoload', 'psr-4')
+   if not psr4 then
+      return nil -- autoload.psr-4 section is absent
+   end
+   if vim.tbl_count(psr4) ~= 1 then
+      return nil -- psr-4 section is ambiguous
+   end
+   local psr4_namespace, psr4_dir
+   for k, v in pairs(psr4) do
+      psr4_namespace = k
+      psr4_dir = v
+   end
+   if type(psr4_dir) == 'table' then
+      if #psr4_dir == 1 then
+         psr4_dir = psr4_dir[1]
+      else
+         return nil -- psr-4 section is ambiguous
+      end
+   end
+   if type(psr4_namespace) ~= 'string' or type(psr4_dir) ~= 'string' then
+      return nil -- psr-4 section is invalid
+   end
+   -- 末尾のスラッシュとバックスラッシュを取り除いておく
+   if psr4_namespace:sub(-1, -1) == '\\' then
+      psr4_namespace = psr4_namespace:sub(0, -2)
+   end
+   if psr4_dir:sub(-1, -1) == '/' then
+      psr4_dir = psr4_dir:sub(0, -2)
+   end
+
+   -- 対象ファイルが置かれたディレクトリとトップレベルのディレクトリを比較し、その差分を名前空間とする
+   local namespace_root_dir = vim.fs.dirname(path_to_composer_json) .. '/' .. psr4_dir
+   if not vim.startswith(current_dir, namespace_root_dir) then
+      return nil
+   end
+   local current_path_suffix = current_dir:sub(#namespace_root_dir + 1)
+   local namespace = psr4_namespace .. current_path_suffix:gsub('/', '\\')
+   return ("namespace %s;"):format(namespace)
+end
+
+local function generate_template()
+   local lines = {
+      '<?php',
+      '',
+      'declare(strict_types=1);',
+      '',
+   }
+   local namespace_decl = generate_namespace_declaration()
+   if namespace_decl then
+      lines[#lines + 1] = namespace_decl
+      lines[#lines + 1] = ''
+   end
+   lines[#lines + 1] = ''
+   return lines
+end
+
+if vim.fn.line('$') == 1 and vim.fn.getline(1) == '' then
+   -- 対象ファイルが空なら、テンプレートを挿入してカーソルを末尾に移動させる
+   -- :help setline()
+   -- :help cursor()
+   vim.fn.setline(1, generate_template())
+   vim.fn.cursor('$', 0)
+end
+
+vim.b.did_ftplugin_php_after = true
+
diff --git a/vhosts/blog/public/posts/2024-02-03/install-wireguard-on-personal-server/index.html b/vhosts/blog/public/posts/2024-02-03/install-wireguard-on-personal-server/index.html index 9b8c1815..f7a6f601 100644 --- a/vhosts/blog/public/posts/2024-02-03/install-wireguard-on-personal-server/index.html +++ b/vhosts/blog/public/posts/2024-02-03/install-wireguard-on-personal-server/index.html @@ -14,8 +14,7 @@ 【備忘録】 個人用サーバに WireGuard を導入する|REPL: Rest-Eat-Program Loop - - +
@@ -99,14 +98,18 @@ まずは個人用サービスをホストしている 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}
+
@@ -115,25 +118,29 @@ $ 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
- -
# クライアント 2 の場合
-[Interface]
-Address = 10.10.1.3/32
-PrivateKey = <クライアント 2 の秘密鍵>
-
-[Peer]
-PublicKey = <サーバの公開鍵>
-AllowedIPs = <サーバの外部 IP アドレス>/32
-Endpoint = <サーバの外部 IP アドレス>:51820
+
+
# クライアント 1 の場合
+[Interface]
+Address = 10.10.1.2/32
+PrivateKey = <クライアント 1 の秘密鍵>
+
+[Peer]
+PublicKey = <サーバの公開鍵>
+AllowedIPs = <サーバの外部 IP アドレス>/32
+Endpoint = <サーバの外部 IP アドレス>:51820
+
+ +
+
# クライアント 2 の場合
+[Interface]
+Address = 10.10.1.3/32
+PrivateKey = <クライアント 2 の秘密鍵>
+
+[Peer]
+PublicKey = <サーバの公開鍵>
+AllowedIPs = <サーバの外部 IP アドレス>/32
+Endpoint = <サーバの外部 IP アドレス>:51820
+

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

-
$ sudo vim /etc/wireguard/wg0.conf
- -
[Interface]
-Address = 10.10.1.1/32
-SaveConfig = true
-PrivateKey = <サーバの秘密鍵>
-ListenPort = 51820
-
-[Peer]
-PublicKey = <クライアント 1 の公開鍵>
-AllowedIPs = 10.10.1.2/32
-
-[Peer]
-PublicKey = <クライアント 2 の公開鍵>
-AllowedIPs = 10.10.1.3/32
+
+
$ sudo vim /etc/wireguard/wg0.conf
+
+ +
+
[Interface]
+Address = 10.10.1.1/32
+SaveConfig = true
+PrivateKey = <サーバの秘密鍵>
+ListenPort = 51820
+
+[Peer]
+PublicKey = <クライアント 1 の公開鍵>
+AllowedIPs = 10.10.1.2/32
+
+[Peer]
+PublicKey = <クライアント 2 の公開鍵>
+AllowedIPs = 10.10.1.3/32
+

次に、WireGuard のサービスを起動する。

-
$ sudo systemctl enable wg-quick@wg0
-$ sudo systemctl start wg-quick@wg0
+
+
$ sudo systemctl enable wg-quick@wg0
+$ sudo systemctl start wg-quick@wg0
+
@@ -176,23 +189,29 @@ $ 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/vhosts/blog/public/posts/2024-02-10/yapcjapan-2024-report/index.html b/vhosts/blog/public/posts/2024-02-10/yapcjapan-2024-report/index.html index 27f11038..62debe04 100644 --- a/vhosts/blog/public/posts/2024-02-10/yapcjapan-2024-report/index.html +++ b/vhosts/blog/public/posts/2024-02-10/yapcjapan-2024-report/index.html @@ -14,8 +14,7 @@ YAPC::Hiroshima 2024 参加レポ|REPL: Rest-Eat-Program Loop - - +
diff --git a/vhosts/blog/public/posts/2024-02-22/phpkansai-2024-report/index.html b/vhosts/blog/public/posts/2024-02-22/phpkansai-2024-report/index.html index 4cb0c230..e47eef88 100644 --- a/vhosts/blog/public/posts/2024-02-22/phpkansai-2024-report/index.html +++ b/vhosts/blog/public/posts/2024-02-22/phpkansai-2024-report/index.html @@ -14,8 +14,7 @@ PHPカンファレンス関西 2024 参加レポ|REPL: Rest-Eat-Program Loop - - +
diff --git a/vhosts/blog/public/posts/2024-03-17/phperkaigi-2024-report/index.html b/vhosts/blog/public/posts/2024-03-17/phperkaigi-2024-report/index.html index a906374b..80ec045e 100644 --- a/vhosts/blog/public/posts/2024-03-17/phperkaigi-2024-report/index.html +++ b/vhosts/blog/public/posts/2024-03-17/phperkaigi-2024-report/index.html @@ -14,8 +14,7 @@ PHPerKaigi 2024 参加レポ|REPL: Rest-Eat-Program Loop - - +
diff --git a/vhosts/blog/public/posts/2024-03-20/my-bucket-list/index.html b/vhosts/blog/public/posts/2024-03-20/my-bucket-list/index.html index e1449afa..2b874bf3 100644 --- a/vhosts/blog/public/posts/2024-03-20/my-bucket-list/index.html +++ b/vhosts/blog/public/posts/2024-03-20/my-bucket-list/index.html @@ -13,8 +13,7 @@ 死ぬまでに作る自作○○一覧あるいは人生の TODO リスト|REPL: Rest-Eat-Program Loop - - +
diff --git a/vhosts/blog/public/posts/2024-04-14/phpcon-odawara-2024-report/index.html b/vhosts/blog/public/posts/2024-04-14/phpcon-odawara-2024-report/index.html index 4f7abef8..92983841 100644 --- a/vhosts/blog/public/posts/2024-04-14/phpcon-odawara-2024-report/index.html +++ b/vhosts/blog/public/posts/2024-04-14/phpcon-odawara-2024-report/index.html @@ -14,8 +14,7 @@ PHP カンファレンス小田原 2024 参加レポ|REPL: Rest-Eat-Program Loop - - +
diff --git a/vhosts/blog/public/posts/2024-04-21/pipefail-option-in-gitlab-ci-cd/index.html b/vhosts/blog/public/posts/2024-04-21/pipefail-option-in-gitlab-ci-cd/index.html index bf281982..3e41bbf7 100644 --- a/vhosts/blog/public/posts/2024-04-21/pipefail-option-in-gitlab-ci-cd/index.html +++ b/vhosts/blog/public/posts/2024-04-21/pipefail-option-in-gitlab-ci-cd/index.html @@ -14,8 +14,7 @@ 【GitLab】 GitLab CI/CD 上での bash/sh は pipefail が有効になっている|REPL: Rest-Eat-Program Loop - - +
@@ -89,14 +88,16 @@ 例:

-
hello-world:
-  stage: test
-  image: alpine:latest
-  script:
-    - 'echo "Hello, World!"'
-  rules:
-    - if: '$CI_MERGE_REQUEST_IID'
-  when: always
+
+
hello-world:
+  stage: test
+  image: alpine:latest
+  script:
+    - 'echo "Hello, World!"'
+  rules:
+    - if: '$CI_MERGE_REQUEST_IID'
+  when: always
+

ここで、script に指定したコマンドが失敗する (exit status が 0 以外になる) と、即座に実行が停止され、ジョブは失敗する。 @@ -106,14 +107,16 @@ では、次のようなケースだとどうなるか。

-
hello-world:
-  stage: test
-  image: alpine:latest
-  script:
-    - 'exit 1 | exit 0'
-  rules:
-    - if: '$CI_MERGE_REQUEST_IID'
-  when: always
+
+
hello-world:
+  stage: test
+  image: alpine:latest
+  script:
+    - 'exit 1 | exit 0'
+  rules:
+    - if: '$CI_MERGE_REQUEST_IID'
+  when: always
+

失敗するコマンドをパイプに接続した。通常 Bash では、パイプの最後のコマンドの exit code が全体の exit code になる。 @@ -126,10 +129,12 @@ 前述したようなケースにおいて、途中で失敗したときに全体を失敗させるには、pipefail オプションを有効にする。

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

こうすると、パイプ全体が失敗するようになる。この設定は、デフォルトだと off になっている。 @@ -143,14 +148,16 @@ 次のような GitLab CI/CD ジョブが失敗してしまった。

-
hoge:
-  stage: test
-  image: alpine:latest
-  script:
-    - 'cat hoge.txt | grep piyo | sed -e "s/foo/bar/g"'
-  rules:
-    - if: '$CI_MERGE_REQUEST_IID'
-  when: always
+
+
hoge:
+  stage: test
+  image: alpine:latest
+  script:
+    - 'cat hoge.txt | grep piyo | sed -e "s/foo/bar/g"'
+  rules:
+    - if: '$CI_MERGE_REQUEST_IID'
+  when: always
+

grep コマンドは、パターンにマッチする行が一行もなかったとき、exit code 1 を返す。よって、pipefail が on になっていると、このジョブは失敗する。現在の pipefail がどうなっているか確かめるため set +o で全オプションを出力させたところ、pipefail が on になっていた。 @@ -160,20 +167,22 @@ しかし、先述したように Bash における pipefail のデフォルト値は off のはずだ。実際に、ローカルで alpine:latest を動かしてみたところ、

-
$ docker run --rm alpine:latest sh -c "set +o"
-set +o errexit
-set +o noglob
-set +o ignoreeof
-set +o monitor
-set +o noexec
-set +o xtrace
-set +o verbose
-set +o noclobber
-set +o allexport
-set +o notify
-set +o nounset
-set +o vi
-set +o pipefail
+
+
$ docker run --rm alpine:latest sh -c "set +o"
+set +o errexit
+set +o noglob
+set +o ignoreeof
+set +o monitor
+set +o noexec
+set +o xtrace
+set +o verbose
+set +o noclobber
+set +o allexport
+set +o notify
+set +o nounset
+set +o vi
+set +o pipefail
+

確かに pipefail は無効になっている。 @@ -190,9 +199,11 @@ set +o pipefail .gitlab-ci.yml で明示的には書いていないので、GitLab Runner (GitLab CI/CD のスクリプトを実行するプログラム) が勝手に追加しているに違いない。そう仮説を立てて GitLab Runner のリポジトリ を調査したところ、ソースコード中の以下の箇所set -o pipefail していることが判明した (コメントは筆者による)。

-
// pipefail オプションが存在しない環境にも対応するため、
-// 先に set -o でオプション一覧を表示させたあと、set -o pipefail している
-buf.WriteString("if set -o | grep pipefail > /dev/null; then set -o pipefail; fi; set -o errexit\n")
+
+
// pipefail オプションが存在しない環境にも対応するため、
+// 先に set -o でオプション一覧を表示させたあと、set -o pipefail している
+buf.WriteString("if set -o | grep pipefail > /dev/null; then set -o pipefail; fi; set -o errexit\n")
+
@@ -201,16 +212,18 @@ buf.WriteString("if set -o | grep 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/vhosts/blog/public/posts/2024-04-29/zsh-file-completion-for-composer-custom-commands/index.html b/vhosts/blog/public/posts/2024-04-29/zsh-file-completion-for-composer-custom-commands/index.html index 99216975..c1390ced 100644 --- a/vhosts/blog/public/posts/2024-04-29/zsh-file-completion-for-composer-custom-commands/index.html +++ b/vhosts/blog/public/posts/2024-04-29/zsh-file-completion-for-composer-custom-commands/index.html @@ -14,8 +14,7 @@ 【Zsh】 Composer のカスタムコマンドに対する Zsh 補完で引数にファイルを補完させる|REPL: Rest-Eat-Program Loop - - +
@@ -98,11 +97,13 @@ このことは、先ほどリンクを載せた _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
+
@@ -122,7 +123,9 @@ まずは、Zsh で補完関数を提供する場合のボイラープレートコードを書く。以下は ~/.zshrc にすべて書く前提だが、autoload を設定するなどすれば別ファイルに分離できる (詳細な手順は割愛)。

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

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

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

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

diff --git a/vhosts/blog/public/posts/2024-06-19/scalamatsuri-2024-report/index.html b/vhosts/blog/public/posts/2024-06-19/scalamatsuri-2024-report/index.html index d5444b32..5e3263bf 100644 --- a/vhosts/blog/public/posts/2024-06-19/scalamatsuri-2024-report/index.html +++ b/vhosts/blog/public/posts/2024-06-19/scalamatsuri-2024-report/index.html @@ -14,8 +14,7 @@ ScalaMatsuri 2024 参加レポ|REPL: Rest-Eat-Program Loop - - +
diff --git a/vhosts/blog/public/posts/2024-07-19/reparojson-fix-only-json-formatter/index.html b/vhosts/blog/public/posts/2024-07-19/reparojson-fix-only-json-formatter/index.html index c5724274..6fb4f0d0 100644 --- a/vhosts/blog/public/posts/2024-07-19/reparojson-fix-only-json-formatter/index.html +++ b/vhosts/blog/public/posts/2024-07-19/reparojson-fix-only-json-formatter/index.html @@ -14,8 +14,7 @@ reparojson: 文法エラーを直すだけの JSON フォーマッタを作った|REPL: Rest-Eat-Program Loop - - +
@@ -91,17 +90,19 @@ 次のように動作する。

-
$ echo '[ 1 2 ]' | reparojson
-[ 1, 2 ]
-
-$ echo '[ 1, 2, ]' | reparojson
-[ 1, 2 ]
-
-$ echo '{ "foo": 1 "bar": 2 }' | reparojson
-{ "foo": 1, "bar": 2 }
-
-$ echo '{ "foo": 1, "bar": 2, }' | reparojson
-{ "foo": 1, "bar": 2 }
+
+
$ echo '[ 1 2 ]' | reparojson
+[ 1, 2 ]
+
+$ echo '[ 1, 2, ]' | reparojson
+[ 1, 2 ]
+
+$ echo '{ "foo": 1 "bar": 2 }' | reparojson
+{ "foo": 1, "bar": 2 }
+
+$ echo '{ "foo": 1, "bar": 2, }' | reparojson
+{ "foo": 1, "bar": 2 }
+

バージョン 0.1.1 時点で修正対象の文法エラーは次のとおり: @@ -140,33 +141,35 @@ $ echo '{ "foo": 1, "bar": 2, }' | reparojson ここでは、nvim-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 で終了するようにしている。 @@ -179,28 +182,34 @@ vim.api.nvim_create_autocmd('LspAttach' -

{
-   "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/vhosts/blog/public/posts/2024-08-19/go-template-access-outer-scope-pipeline-within-with-or-range/index.html b/vhosts/blog/public/posts/2024-08-19/go-template-access-outer-scope-pipeline-within-with-or-range/index.html index 2d2c3761..aa6d2afb 100644 --- a/vhosts/blog/public/posts/2024-08-19/go-template-access-outer-scope-pipeline-within-with-or-range/index.html +++ b/vhosts/blog/public/posts/2024-08-19/go-template-access-outer-scope-pipeline-within-with-or-range/index.html @@ -14,8 +14,7 @@ 【Go】 text/template の with や range の内側から外側の "." にアクセスする|REPL: Rest-Eat-Program Loop - - +

@@ -71,19 +70,21 @@ 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. は、現在の操作対象を表す特殊なオブジェクトである。 @@ -97,18 +98,20 @@ つまりこのテンプレートは、次のような構造をレンダリングしている (Execute() の第2引数)。

-
tmpl.Execute(out, Params{
-    Title: "foo",
-    User: User{
-        ID:   123,
-        Name: "john",
-    },
-    Items: []string{
-        "hoge",
-        "piyo",
-        "fuga",
-    },
-})
+
+
tmpl.Execute(out, Params{
+    Title: "foo",
+    User: User{
+        ID:   123,
+        Name: "john",
+    },
+    Items: []string{
+        "hoge",
+        "piyo",
+        "fuga",
+    },
+})
+
@@ -117,13 +120,15 @@ 今回おこないたいのは、withrange の中で、その外側で使われていたトップレベルのオブジェクトを参照することだ。

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

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

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

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

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

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

diff --git a/vhosts/blog/public/posts/2024-12-04/cohackpp-report/index.html b/vhosts/blog/public/posts/2024-12-04/cohackpp-report/index.html index f98a752d..97f8e6b3 100644 --- a/vhosts/blog/public/posts/2024-12-04/cohackpp-report/index.html +++ b/vhosts/blog/public/posts/2024-12-04/cohackpp-report/index.html @@ -14,8 +14,7 @@ 紅白ぺぱ合戦に参加&LTしました|REPL: Rest-Eat-Program Loop - - +
@@ -146,129 +145,131 @@ 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/vhosts/blog/public/posts/2024-12-33/2024-reflections/index.html b/vhosts/blog/public/posts/2024-12-33/2024-reflections/index.html index f2169b2c..a6e0f3f6 100644 --- a/vhosts/blog/public/posts/2024-12-33/2024-reflections/index.html +++ b/vhosts/blog/public/posts/2024-12-33/2024-reflections/index.html @@ -13,8 +13,7 @@ 2024年の振り返り|REPL: Rest-Eat-Program Loop - - +
diff --git a/vhosts/blog/public/posts/2025-01-08/phperkaigi-2023-tokens-q1/index.html b/vhosts/blog/public/posts/2025-01-08/phperkaigi-2023-tokens-q1/index.html index 16bf6730..892c7f34 100644 --- a/vhosts/blog/public/posts/2025-01-08/phperkaigi-2023-tokens-q1/index.html +++ b/vhosts/blog/public/posts/2025-01-08/phperkaigi-2023-tokens-q1/index.html @@ -14,8 +14,7 @@ PHPerKaigi 2023 トークン問題解説 (1/5)|REPL: Rest-Eat-Program Loop - - +
@@ -136,7 +135,9 @@ まずはトークンを得る方法を解説抜きで説明する。次のように実行する。

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

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

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

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

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

すでに「解き方」の節で示したように、パスワードである PHPer トークンは「#iwillblog」である。これを与えて実行すると正解のトークンが得られる。 @@ -258,23 +263,27 @@ strings コマンドを使うと、隠されたデータを簡単に閲覧できる。

-
IHDR
--HHc
-<PLTE
-IDATx
-IEND
-<?php
-error_reporting(-1);
-$b = unpack('C*', file_get_contents(__FILE__));
-$w = $b[20]+2;
-$h = $b[24]+2;
-// (以下略)
+
+
IHDR
+-HHc
+<PLTE
+IDATx
+IEND
+<?php
+error_reporting(-1);
+$b = unpack('C*', file_get_contents(__FILE__));
+$w = $b[20]+2;
+$h = $b[24]+2;
+// (以下略)
+

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 と指定されている。 @@ -291,107 +300,109 @@ $h = $b[24]+2; 画像の正体がわかったところで、画像に隠されていた PHP プログラムについて見ていこう。先ほどは一部しか記載しなかったので、全体を載せる。なお、ある程度ゴルフしながら書いたので、空白こそ残しているものの可読性は非常に低いことと思う。

-
<?php
-error_reporting(-1);
-$b = unpack('C*', file_get_contents(__FILE__));
-$w = $b[20]+2;
-$h = $b[24]+2;
-$cs = [];
-for ($y = 0; $y < $h; $y++)
-  for ($x = 0; $x < $w; $x++)
-    $cs[$y*$w + $x] = ($x*$y === 0 || $x === $w-1 || $y === $h-1)
-                        ? 0
-                        : $b[122+($y-1)*($w-1)+$x-1];
-$i = stream_isatty(STDIN)
-    ? []
-    : array_map(ord(...), str_split(trim((string) fgets(STDIN))));
-$m = [];
-$pc = 1*$w+1;
-$dp = 0;
-$cc = 1;
-$c0 = 1;
-$b = 0;
-$ns = 0;
-$o = '';
-while (true) {
-  $ns++;
-  if ($ns > 1e5) {
-    echo "infinite loop detected\n";
-    break;
-  $c1 = $cs[$pc];
-  $y = (6 + intdiv($c1-2, 3) - intdiv($c0-2, 3)) % 6;
-  $x = (3 + $c1%3 - $c0%3) % 3;
-  match (($c0 !== 1) * ($c1 !== 1) * ($y*3 + $x)) {
-    1 => $m[] = $b,
-    2 => array_pop($m),
-    3 => $m[] = array_pop($m) + array_pop($m),
-    4 => $m[] = (fn($x, $y) => $y - $x)(array_pop($m), array_pop($m)),
-    5 => $m[] = array_pop($m) * array_pop($m),
-    8 => $m[] = array_pop($m) === 0 ? 1 : 0,
-    11 => $cc *= pow(-1, array_pop($m)),
-    12 => $m[] = $m[count($m)-1],
-    13 => $m = (fn($n, $d, $m, $l) => [
-            ...array_slice($m, 0, $l-$d),
-            ...array_reverse([
-              ...array_reverse(array_slice($m, $l-$d, $d-$n)),
-              ...array_reverse(array_slice($m, $l-$n)),
-            ]),
-          ])(array_pop($m), array_pop($m), $m, count($m)),
-    15 => !empty($i) and $m[] = array_shift($i),
-    16 => $o .= sprintf('%d', array_pop($m)),
-    17 => $o .= sprintf('%c', array_pop($m)),
-    default => 'nop',
-  };
-  $c0 = $c1;
-  for ($j = 0; $j < 8; $j++) {
-    $v = [];
-    if ($c1 === 1) {
-      $x = $pc % $w;
-      $y = intdiv($pc, $w);
-      $e = [($y+1)*$w-1, ($h-1)*$w+$x, $y*$w, $x][$dp];
-      $z = [1, $w, -1, -$w][$dp];
-      for ($ep = $pc; $ep !== $e; $ep += $z)
-        if ($cs[$ep] !== 1) break;
-      $ep -= $z;
-      $pc = $ep;
-    } else {
-      $q = [$pc];
-      $ep = $pc;
-      while (!empty($q)) {
-        $qq = array_pop($q);
-        $v[$qq] = true;
-        foreach ([$qq+1, $qq+$w, $qq-1, $qq-$w] as $qp) {
-          if ($cs[$qp] !== $c1) continue;
-          if (isset($v[$qp])) continue;
-          $q[] = $qp;
-          $qx = $qp % $w;
-          $qy = intdiv($qp, $w);
-          $x = $ep % $w;
-          $y = intdiv($ep, $w);
-          if (
-            ($dp === 0 && ($x < $qx || ($x === $qx && ($y<=>$qy) === $cc)))
-            || ($dp === 1 && ($y < $qy || ($y === $qy && ($qx<=>$x) === $cc)))
-            || ($dp === 2 && ($qx < $x || ($qx === $x && ($qy<=>$y) === $cc)))
-            || ($dp === 3 && ($qy < $y || ($qy === $y && ($x<=>$qx) === $cc)))
-          )
-            $ep = $qp;
-        }
-      }
-    }
-    $np = $ep + [1, $w, -1, -$w][$dp];
-    if ($cs[$np] !== 0) {
-      $b = count(array_keys($v));
-      $pc = $np;
-      break;
-    }
-    if ($j === 7) break 2;
-    if ($j % 2 === 0) $cc = -$cc;
-    if ($j % 2 === 1) $dp = ($dp+1) % 4;
-// The original Piet image is wrong: it outputs 403 error for invalid passwords.
-// Failure of authentication should be notified by 401, not 403.
-// I noticed that one month before PHPerKaigi, but I could not read or write (paint)
-// Piet any longer at that time.
-fwrite(STDERR, str_replace('403 Forbidden', '401 Unauthorized', $o));
+
+
<?php
+error_reporting(-1);
+$b = unpack('C*', file_get_contents(__FILE__));
+$w = $b[20]+2;
+$h = $b[24]+2;
+$cs = [];
+for ($y = 0; $y < $h; $y++)
+  for ($x = 0; $x < $w; $x++)
+    $cs[$y*$w + $x] = ($x*$y === 0 || $x === $w-1 || $y === $h-1)
+                        ? 0
+                        : $b[122+($y-1)*($w-1)+$x-1];
+$i = stream_isatty(STDIN)
+    ? []
+    : array_map(ord(...), str_split(trim((string) fgets(STDIN))));
+$m = [];
+$pc = 1*$w+1;
+$dp = 0;
+$cc = 1;
+$c0 = 1;
+$b = 0;
+$ns = 0;
+$o = '';
+while (true) {
+  $ns++;
+  if ($ns > 1e5) {
+    echo "infinite loop detected\n";
+    break;
+  $c1 = $cs[$pc];
+  $y = (6 + intdiv($c1-2, 3) - intdiv($c0-2, 3)) % 6;
+  $x = (3 + $c1%3 - $c0%3) % 3;
+  match (($c0 !== 1) * ($c1 !== 1) * ($y*3 + $x)) {
+    1 => $m[] = $b,
+    2 => array_pop($m),
+    3 => $m[] = array_pop($m) + array_pop($m),
+    4 => $m[] = (fn($x, $y) => $y - $x)(array_pop($m), array_pop($m)),
+    5 => $m[] = array_pop($m) * array_pop($m),
+    8 => $m[] = array_pop($m) === 0 ? 1 : 0,
+    11 => $cc *= pow(-1, array_pop($m)),
+    12 => $m[] = $m[count($m)-1],
+    13 => $m = (fn($n, $d, $m, $l) => [
+            ...array_slice($m, 0, $l-$d),
+            ...array_reverse([
+              ...array_reverse(array_slice($m, $l-$d, $d-$n)),
+              ...array_reverse(array_slice($m, $l-$n)),
+            ]),
+          ])(array_pop($m), array_pop($m), $m, count($m)),
+    15 => !empty($i) and $m[] = array_shift($i),
+    16 => $o .= sprintf('%d', array_pop($m)),
+    17 => $o .= sprintf('%c', array_pop($m)),
+    default => 'nop',
+  };
+  $c0 = $c1;
+  for ($j = 0; $j < 8; $j++) {
+    $v = [];
+    if ($c1 === 1) {
+      $x = $pc % $w;
+      $y = intdiv($pc, $w);
+      $e = [($y+1)*$w-1, ($h-1)*$w+$x, $y*$w, $x][$dp];
+      $z = [1, $w, -1, -$w][$dp];
+      for ($ep = $pc; $ep !== $e; $ep += $z)
+        if ($cs[$ep] !== 1) break;
+      $ep -= $z;
+      $pc = $ep;
+    } else {
+      $q = [$pc];
+      $ep = $pc;
+      while (!empty($q)) {
+        $qq = array_pop($q);
+        $v[$qq] = true;
+        foreach ([$qq+1, $qq+$w, $qq-1, $qq-$w] as $qp) {
+          if ($cs[$qp] !== $c1) continue;
+          if (isset($v[$qp])) continue;
+          $q[] = $qp;
+          $qx = $qp % $w;
+          $qy = intdiv($qp, $w);
+          $x = $ep % $w;
+          $y = intdiv($ep, $w);
+          if (
+            ($dp === 0 && ($x < $qx || ($x === $qx && ($y<=>$qy) === $cc)))
+            || ($dp === 1 && ($y < $qy || ($y === $qy && ($qx<=>$x) === $cc)))
+            || ($dp === 2 && ($qx < $x || ($qx === $x && ($qy<=>$y) === $cc)))
+            || ($dp === 3 && ($qy < $y || ($qy === $y && ($x<=>$qx) === $cc)))
+          )
+            $ep = $qp;
+        }
+      }
+    }
+    $np = $ep + [1, $w, -1, -$w][$dp];
+    if ($cs[$np] !== 0) {
+      $b = count(array_keys($v));
+      $pc = $np;
+      break;
+    }
+    if ($j === 7) break 2;
+    if ($j % 2 === 0) $cc = -$cc;
+    if ($j % 2 === 1) $dp = ($dp+1) % 4;
+// The original Piet image is wrong: it outputs 403 error for invalid passwords.
+// Failure of authentication should be notified by 401, not 403.
+// I noticed that one month before PHPerKaigi, but I could not read or write (paint)
+// Piet any longer at that time.
+fwrite(STDERR, str_replace('403 Forbidden', '401 Unauthorized', $o));
+

これは一体なんなのか。ずばり、難解プログラミング言語の一つ Piet のインタプリタである。Piet はピエト・モンドリアン (『赤・青・黄のコンポジション』などで知られる抽象画家) の作品にインスピレーションを受けて作られた、画像をソースコードとするプログラミング言語である。インタプリタは画像の各ピクセルの上を進みながら、色等に応じて特定の処理をおこなっていく。ここでは詳しい言語仕様については解説しないので、気になる方は Wikipedia の記事「Piet」 などを参照してほしい。 @@ -401,7 +412,9 @@ $h = $b[24]+2; プログラムの冒頭にあるこの箇所

-
$b = unpack('C*', file_get_contents(__FILE__));
+
+
$b = unpack('C*', file_get_contents(__FILE__));
+

__FILE__ つまりこの画像ファイルを読み込んでいる。先ほど Piet は画像をソースコードにしていると説明した。そう、今回の問題の画像ファイル Q1.png は、PHP 製 Piet インタプリタであると同時に、Piet のソースコード画像でもあるのだ。QR コード中央のカラフルな部分が Piet の命令になっている。 @@ -460,11 +473,13 @@ $h = $b[24]+2; ところで、先ほど掲載した Piet のインタプリタのソースコード末尾には次のような箇所がある。

-
// The original Piet image is wrong: it outputs 403 error for invalid passwords.
-// Failure of authentication should be notified by 401, not 403.
-// I noticed that one month before PHPerKaigi, but I could not read or write (paint)
-// Piet any longer at that time.
-fwrite(STDERR, str_replace('403 Forbidden', '401 Unauthorized', $o));
+
+
// The original Piet image is wrong: it outputs 403 error for invalid passwords.
+// Failure of authentication should be notified by 401, not 403.
+// I noticed that one month before PHPerKaigi, but I could not read or write (paint)
+// Piet any longer at that time.
+fwrite(STDERR, str_replace('403 Forbidden', '401 Unauthorized', $o));
+

コメントにも書かれているが、この Piet のソースコード画像には誤りがあった。本来 HTTP のステータスコードを真似るのなら、認証の失敗には 401 を返さなければならない。しかし、Piet のソースは 403 を返すように書いてしまっていた。そのことに私が気付いたのは PHPerKaigi 2023 が開催されるひと月前で、その時点で私はこの Piet のソースコードを (ちょうどこの記事でそうなっているのと同じように) 読解できなくなっていた。さらに悪いことに、正しいメッセージ「401 Unauthorized」は元の「403 Forbidden」よりも3文字長い。3文字出力が長くなるということは、それだけ Piet で塗るべきピクセルが増えることを意味する。もはや3文字追加で出力するだけの余白はこの画像に残されていなかった (と思う。腕ききの Piet プログラマならできるかもしれないので挑戦してみてほしい)。 diff --git a/vhosts/blog/public/posts/2025-01-26/yaml-breaking-changes-between-v1-1-and-v1-2/index.html b/vhosts/blog/public/posts/2025-01-26/yaml-breaking-changes-between-v1-1-and-v1-2/index.html index a8f72a21..87efea12 100644 --- a/vhosts/blog/public/posts/2025-01-26/yaml-breaking-changes-between-v1-1-and-v1-2/index.html +++ b/vhosts/blog/public/posts/2025-01-26/yaml-breaking-changes-between-v1-1-and-v1-2/index.html @@ -14,8 +14,7 @@ 【YAML】YAML 1.1 と YAML 1.2 の主な破壊的変更|REPL: Rest-Eat-Program Loop - - +

@@ -103,14 +102,16 @@ 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/vhosts/blog/public/posts/2025-02-24/phpcon-nagoya-2025-report/index.html b/vhosts/blog/public/posts/2025-02-24/phpcon-nagoya-2025-report/index.html index e929808e..ee924cdc 100644 --- a/vhosts/blog/public/posts/2025-02-24/phpcon-nagoya-2025-report/index.html +++ b/vhosts/blog/public/posts/2025-02-24/phpcon-nagoya-2025-report/index.html @@ -14,8 +14,7 @@ PHP カンファレンス名古屋 2025 参加レポ|REPL: Rest-Eat-Program Loop - - +

diff --git a/vhosts/blog/public/posts/2025-03-27/zip-function-like-command-paste-command/index.html b/vhosts/blog/public/posts/2025-03-27/zip-function-like-command-paste-command/index.html index 33d871d5..c1d25c00 100644 --- a/vhosts/blog/public/posts/2025-03-27/zip-function-like-command-paste-command/index.html +++ b/vhosts/blog/public/posts/2025-03-27/zip-function-like-command-paste-command/index.html @@ -14,8 +14,7 @@ zip 関数のようなコマンド paste|REPL: Rest-Eat-Program Loop - - +
@@ -80,28 +79,34 @@ a.txt

-
a1
-a2
-a3
+
+
a1
+a2
+a3
+

b.txt

-
b1
-b2
-b3
+
+
b1
+b2
+b3
+

ab.txt

-
a1
-b1
-a2
-b2
-a3
-b3
+
+
a1
+b1
+a2
+b2
+a3
+b3
+

ちょうど Python や Haskell などにある zip 関数のような動きをさせたい。 @@ -114,8 +119,10 @@ b3 記事タイトルに書いたように、paste コマンドを使うと実現できる。

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

paste コマンドは複数のファイルを引数に取り、それらを1行ずつ消費しながら -d で指定した文字で区切って出力する。-d は区切り文字の指定で、デフォルトだとタブ区切りになる。 @@ -125,22 +132,26 @@ b3 ファイル名には - を指定でき、その場合は標準入力から読み込んで出力する。このとき paste - - のように複数回 - を指定すると、指定した回数の行ごとに連結することができる。例えば ab.txt だとこうなる。

-
$ paste - - < ab.txt
-a1	b1
-a2	b2
-a3	b3
+
+
$ paste - - < ab.txt
+a1	b1
+a2	b2
+a3	b3
+

これは標準入力を使うとき特有の挙動で、単に同じファイル名を指定してもこうはならない。

-
$ paste ab.txt ab.txt
-a1	a1
-b1	b1
-a2	a2
-b2	b2
-a3	a3
-b3	b3
+
+
$ paste ab.txt ab.txt
+a1	a1
+b1	b1
+a2	a2
+b2	b2
+a3	a3
+b3	b3
+

ときどき便利。 diff --git a/vhosts/blog/public/posts/2025-03-28/http-1-1-send-multiple-same-headers/index.html b/vhosts/blog/public/posts/2025-03-28/http-1-1-send-multiple-same-headers/index.html index 5b645245..ef12bbfe 100644 --- a/vhosts/blog/public/posts/2025-03-28/http-1-1-send-multiple-same-headers/index.html +++ b/vhosts/blog/public/posts/2025-03-28/http-1-1-send-multiple-same-headers/index.html @@ -14,8 +14,7 @@ 【HTTP】HTTP/1.1 で同じヘッダを2回送るとどうなるか|REPL: Rest-Eat-Program Loop - - +

diff --git a/vhosts/blog/public/posts/index.html b/vhosts/blog/public/posts/index.html index b6887c5f..6d11a4bf 100644 --- a/vhosts/blog/public/posts/index.html +++ b/vhosts/blog/public/posts/index.html @@ -14,7 +14,7 @@ 投稿一覧|REPL: Rest-Eat-Program Loop - +
diff --git a/vhosts/blog/public/slides/2023-01-18/phpstudy-tokyo-148/index.html b/vhosts/blog/public/slides/2023-01-18/phpstudy-tokyo-148/index.html index 98c41adc..20cb4fa1 100644 --- a/vhosts/blog/public/slides/2023-01-18/phpstudy-tokyo-148/index.html +++ b/vhosts/blog/public/slides/2023-01-18/phpstudy-tokyo-148/index.html @@ -14,8 +14,7 @@ PHP 勉強会@東京 第148 回 (LT)|REPL: Rest-Eat-Program Loop - - +
diff --git a/vhosts/blog/public/slides/2023-02-15/phpstudy-tokyo-149/index.html b/vhosts/blog/public/slides/2023-02-15/phpstudy-tokyo-149/index.html index 39ec8df1..f26fadf6 100644 --- a/vhosts/blog/public/slides/2023-02-15/phpstudy-tokyo-149/index.html +++ b/vhosts/blog/public/slides/2023-02-15/phpstudy-tokyo-149/index.html @@ -14,8 +14,7 @@ PHP 勉強会@東京 第149 回 (LT)|REPL: Rest-Eat-Program Loop - - +
diff --git a/vhosts/blog/public/slides/2023-03-15/phpstudy-tokyo-150/index.html b/vhosts/blog/public/slides/2023-03-15/phpstudy-tokyo-150/index.html index 1e8d34c5..6bb93bf8 100644 --- a/vhosts/blog/public/slides/2023-03-15/phpstudy-tokyo-150/index.html +++ b/vhosts/blog/public/slides/2023-03-15/phpstudy-tokyo-150/index.html @@ -14,8 +14,7 @@ PHP 勉強会@東京 第150 回 (LT)|REPL: Rest-Eat-Program Loop - - +
diff --git a/vhosts/blog/public/slides/2023-03-24/phperkaigi-2023/index.html b/vhosts/blog/public/slides/2023-03-24/phperkaigi-2023/index.html index c1d57749..9d0afa9a 100644 --- a/vhosts/blog/public/slides/2023-03-24/phperkaigi-2023/index.html +++ b/vhosts/blog/public/slides/2023-03-24/phperkaigi-2023/index.html @@ -14,8 +14,7 @@ PHPerKaigi 2023 (レギュラートーク)|REPL: Rest-Eat-Program Loop - - +
diff --git a/vhosts/blog/public/slides/2023-03-25/phperkaigi-2023-tokens/index.html b/vhosts/blog/public/slides/2023-03-25/phperkaigi-2023-tokens/index.html index 017ee5c0..a42c3a20 100644 --- a/vhosts/blog/public/slides/2023-03-25/phperkaigi-2023-tokens/index.html +++ b/vhosts/blog/public/slides/2023-03-25/phperkaigi-2023-tokens/index.html @@ -14,8 +14,7 @@ PHPerKaigi 2023 (トークン解説セッション)|REPL: Rest-Eat-Program Loop - - +
diff --git a/vhosts/blog/public/slides/2023-04-12/phpstudy-tokyo-151/index.html b/vhosts/blog/public/slides/2023-04-12/phpstudy-tokyo-151/index.html index 5dde3a9a..d0f77ca1 100644 --- a/vhosts/blog/public/slides/2023-04-12/phpstudy-tokyo-151/index.html +++ b/vhosts/blog/public/slides/2023-04-12/phpstudy-tokyo-151/index.html @@ -14,8 +14,7 @@ PHP 勉強会@東京 第151 回 (LT)|REPL: Rest-Eat-Program Loop - - +
diff --git a/vhosts/blog/public/slides/2023-06-21/phpstudy-tokyo-153/index.html b/vhosts/blog/public/slides/2023-06-21/phpstudy-tokyo-153/index.html index 47be5af5..01157ee9 100644 --- a/vhosts/blog/public/slides/2023-06-21/phpstudy-tokyo-153/index.html +++ b/vhosts/blog/public/slides/2023-06-21/phpstudy-tokyo-153/index.html @@ -14,8 +14,7 @@ PHP 勉強会@東京 第153 回 (LT)|REPL: Rest-Eat-Program Loop - - +
diff --git a/vhosts/blog/public/slides/2023-06-23/phpconfuk-2023-eve/index.html b/vhosts/blog/public/slides/2023-06-23/phpconfuk-2023-eve/index.html index 33d5c800..c9be7532 100644 --- a/vhosts/blog/public/slides/2023-06-23/phpconfuk-2023-eve/index.html +++ b/vhosts/blog/public/slides/2023-06-23/phpconfuk-2023-eve/index.html @@ -14,8 +14,7 @@ PHP カンファレンス福岡 2023 前夜祭 (非公式) (レギュラートーク)|REPL: Rest-Eat-Program Loop - - +
diff --git a/vhosts/blog/public/slides/2023-07-26/phpstudy-tokyo-154/index.html b/vhosts/blog/public/slides/2023-07-26/phpstudy-tokyo-154/index.html index 4eb42c73..4b127692 100644 --- a/vhosts/blog/public/slides/2023-07-26/phpstudy-tokyo-154/index.html +++ b/vhosts/blog/public/slides/2023-07-26/phpstudy-tokyo-154/index.html @@ -14,8 +14,7 @@ PHP 勉強会@東京 第154 回 (レギュラートーク)|REPL: Rest-Eat-Program Loop - - +
diff --git a/vhosts/blog/public/slides/2023-08-24/phpstudy-tokyo-155/index.html b/vhosts/blog/public/slides/2023-08-24/phpstudy-tokyo-155/index.html index 62b8e8ed..5a1f8cf0 100644 --- a/vhosts/blog/public/slides/2023-08-24/phpstudy-tokyo-155/index.html +++ b/vhosts/blog/public/slides/2023-08-24/phpstudy-tokyo-155/index.html @@ -14,8 +14,7 @@ PHP 勉強会@東京 第155 回 (LT)|REPL: Rest-Eat-Program Loop - - +
diff --git a/vhosts/blog/public/slides/2023-10-25/phpstudy-tokyo-157/index.html b/vhosts/blog/public/slides/2023-10-25/phpstudy-tokyo-157/index.html index a0144716..8f39dbdb 100644 --- a/vhosts/blog/public/slides/2023-10-25/phpstudy-tokyo-157/index.html +++ b/vhosts/blog/public/slides/2023-10-25/phpstudy-tokyo-157/index.html @@ -14,8 +14,7 @@ PHP 勉強会@東京 第157 回 (LT)|REPL: Rest-Eat-Program Loop - - +
diff --git a/vhosts/blog/public/slides/2024-01-24/phpstudy-tokyo-160/index.html b/vhosts/blog/public/slides/2024-01-24/phpstudy-tokyo-160/index.html index 59d478f1..e0d99041 100644 --- a/vhosts/blog/public/slides/2024-01-24/phpstudy-tokyo-160/index.html +++ b/vhosts/blog/public/slides/2024-01-24/phpstudy-tokyo-160/index.html @@ -14,8 +14,7 @@ PHP 勉強会@東京 第160 回 (レギュラートーク)|REPL: Rest-Eat-Program Loop - - +
diff --git a/vhosts/blog/public/slides/2024-03-08/phperkaigi-2024/index.html b/vhosts/blog/public/slides/2024-03-08/phperkaigi-2024/index.html index af067bc4..e1623d7e 100644 --- a/vhosts/blog/public/slides/2024-03-08/phperkaigi-2024/index.html +++ b/vhosts/blog/public/slides/2024-03-08/phperkaigi-2024/index.html @@ -14,8 +14,7 @@ PHPerKaigi 2024 (レギュラートーク (40分))|REPL: Rest-Eat-Program Loop - - +
diff --git a/vhosts/blog/public/slides/2024-03-15/ya8-2024/index.html b/vhosts/blog/public/slides/2024-03-15/ya8-2024/index.html index 9823d837..6a01d8d4 100644 --- a/vhosts/blog/public/slides/2024-03-15/ya8-2024/index.html +++ b/vhosts/blog/public/slides/2024-03-15/ya8-2024/index.html @@ -14,8 +14,7 @@ Ya8 2024 (レギュラートーク (60分))|REPL: Rest-Eat-Program Loop - - +
diff --git a/vhosts/blog/public/slides/2024-04-13/phpcon-odawara-2024/index.html b/vhosts/blog/public/slides/2024-04-13/phpcon-odawara-2024/index.html index 0c444c0f..1d20ff7c 100644 --- a/vhosts/blog/public/slides/2024-04-13/phpcon-odawara-2024/index.html +++ b/vhosts/blog/public/slides/2024-04-13/phpcon-odawara-2024/index.html @@ -14,8 +14,7 @@ PHP カンファレンス小田原 (レギュラートーク (15分))|REPL: Rest-Eat-Program Loop - - +
diff --git a/vhosts/blog/public/slides/2024-04-25/phpstudy-tokyo-163/index.html b/vhosts/blog/public/slides/2024-04-25/phpstudy-tokyo-163/index.html index 716444f8..0bcbdca1 100644 --- a/vhosts/blog/public/slides/2024-04-25/phpstudy-tokyo-163/index.html +++ b/vhosts/blog/public/slides/2024-04-25/phpstudy-tokyo-163/index.html @@ -14,8 +14,7 @@ PHP 勉強会@東京 第163回 (LT)|REPL: Rest-Eat-Program Loop - - +
diff --git a/vhosts/blog/public/slides/2024-07-18/phpstudy-tokyo-166/index.html b/vhosts/blog/public/slides/2024-07-18/phpstudy-tokyo-166/index.html index 3dd8c652..48572ffc 100644 --- a/vhosts/blog/public/slides/2024-07-18/phpstudy-tokyo-166/index.html +++ b/vhosts/blog/public/slides/2024-07-18/phpstudy-tokyo-166/index.html @@ -14,8 +14,7 @@ PHP 勉強会@東京 第166回 (レギュラートーク (20分))|REPL: Rest-Eat-Program Loop - - +
diff --git a/vhosts/blog/public/slides/2024-10-30/phpstudy-tokyo-169/index.html b/vhosts/blog/public/slides/2024-10-30/phpstudy-tokyo-169/index.html index 8784ab0d..5e630b1b 100644 --- a/vhosts/blog/public/slides/2024-10-30/phpstudy-tokyo-169/index.html +++ b/vhosts/blog/public/slides/2024-10-30/phpstudy-tokyo-169/index.html @@ -14,8 +14,7 @@ PHP 勉強会@東京 第169回 (レギュラートーク (20分))|REPL: Rest-Eat-Program Loop - - +
diff --git a/vhosts/blog/public/slides/2024-11-30/cohackpp/index.html b/vhosts/blog/public/slides/2024-11-30/cohackpp/index.html index 46fda600..ed64278a 100644 --- a/vhosts/blog/public/slides/2024-11-30/cohackpp/index.html +++ b/vhosts/blog/public/slides/2024-11-30/cohackpp/index.html @@ -14,8 +14,7 @@ 紅白ぺぱ合戦 (LT)|REPL: Rest-Eat-Program Loop - - +
diff --git a/vhosts/blog/public/slides/2025-02-22/phpcon-nagoya-2025/index.html b/vhosts/blog/public/slides/2025-02-22/phpcon-nagoya-2025/index.html index 323cf7b8..3ca72263 100644 --- a/vhosts/blog/public/slides/2025-02-22/phpcon-nagoya-2025/index.html +++ b/vhosts/blog/public/slides/2025-02-22/phpcon-nagoya-2025/index.html @@ -14,8 +14,7 @@ PHP カンファレンス名古屋 2025 (レギュラートーク (30分))|REPL: Rest-Eat-Program Loop - - +
diff --git a/vhosts/blog/public/slides/2025-03-23/phperkaigi-2025/index.html b/vhosts/blog/public/slides/2025-03-23/phperkaigi-2025/index.html index 95c4e4de..bb2cb9e1 100644 --- a/vhosts/blog/public/slides/2025-03-23/phperkaigi-2025/index.html +++ b/vhosts/blog/public/slides/2025-03-23/phperkaigi-2025/index.html @@ -14,8 +14,7 @@ PHPerKaigi 2025 (レギュラートーク (40分))|REPL: Rest-Eat-Program Loop - - +
diff --git a/vhosts/blog/public/slides/index.html b/vhosts/blog/public/slides/index.html index 409deac0..42565061 100644 --- a/vhosts/blog/public/slides/index.html +++ b/vhosts/blog/public/slides/index.html @@ -14,7 +14,7 @@ スライド一覧|REPL: Rest-Eat-Program Loop - +
diff --git a/vhosts/blog/public/style.css b/vhosts/blog/public/style.css index 25137117..18b6ffae 100644 --- a/vhosts/blog/public/style.css +++ b/vhosts/blog/public/style.css @@ -98,6 +98,10 @@ a:hover { color: #666; } +.codeblock { + background-color: #f5f5f5; +} + pre { background-color: #f5f5f5; padding: 1rem; @@ -133,7 +137,8 @@ code { font-size: 0.9rem; } -pre code { +.shiki code { + background-color: unset; padding: 0; } diff --git a/vhosts/blog/public/tags/ci-cd/index.html b/vhosts/blog/public/tags/ci-cd/index.html index 34b2df9b..5ee5a9cf 100644 --- a/vhosts/blog/public/tags/ci-cd/index.html +++ b/vhosts/blog/public/tags/ci-cd/index.html @@ -15,7 +15,7 @@ タグ「CI/CD」一覧|REPL: Rest-Eat-Program Loop - +
diff --git a/vhosts/blog/public/tags/cohackpp/index.html b/vhosts/blog/public/tags/cohackpp/index.html index 12cac4b7..487a9037 100644 --- a/vhosts/blog/public/tags/cohackpp/index.html +++ b/vhosts/blog/public/tags/cohackpp/index.html @@ -15,7 +15,7 @@ タグ「紅白ぺぱ合戦」一覧|REPL: Rest-Eat-Program Loop - +
diff --git a/vhosts/blog/public/tags/composer/index.html b/vhosts/blog/public/tags/composer/index.html index f2d95a2c..43e482b0 100644 --- a/vhosts/blog/public/tags/composer/index.html +++ b/vhosts/blog/public/tags/composer/index.html @@ -15,7 +15,7 @@ タグ「Composer」一覧|REPL: Rest-Eat-Program Loop - +
diff --git a/vhosts/blog/public/tags/conference/index.html b/vhosts/blog/public/tags/conference/index.html index acbb7fdd..2e6ff332 100644 --- a/vhosts/blog/public/tags/conference/index.html +++ b/vhosts/blog/public/tags/conference/index.html @@ -15,7 +15,7 @@ タグ「カンファレンス」一覧|REPL: Rest-Eat-Program Loop - +
diff --git a/vhosts/blog/public/tags/cpp/index.html b/vhosts/blog/public/tags/cpp/index.html index 558af044..988b4a70 100644 --- a/vhosts/blog/public/tags/cpp/index.html +++ b/vhosts/blog/public/tags/cpp/index.html @@ -15,7 +15,7 @@ タグ「C++」一覧|REPL: Rest-Eat-Program Loop - +
diff --git a/vhosts/blog/public/tags/cpp17/index.html b/vhosts/blog/public/tags/cpp17/index.html index ce6e1e12..fd1c4dd7 100644 --- a/vhosts/blog/public/tags/cpp17/index.html +++ b/vhosts/blog/public/tags/cpp17/index.html @@ -15,7 +15,7 @@ タグ「C++ 17」一覧|REPL: Rest-Eat-Program Loop - +
diff --git a/vhosts/blog/public/tags/gitlab/index.html b/vhosts/blog/public/tags/gitlab/index.html index 739517ff..04b9b364 100644 --- a/vhosts/blog/public/tags/gitlab/index.html +++ b/vhosts/blog/public/tags/gitlab/index.html @@ -15,7 +15,7 @@ タグ「GitLab」一覧|REPL: Rest-Eat-Program Loop - +
diff --git a/vhosts/blog/public/tags/go/index.html b/vhosts/blog/public/tags/go/index.html index 322d2c16..12491851 100644 --- a/vhosts/blog/public/tags/go/index.html +++ b/vhosts/blog/public/tags/go/index.html @@ -15,7 +15,7 @@ タグ「Go」一覧|REPL: Rest-Eat-Program Loop - +
diff --git a/vhosts/blog/public/tags/http/index.html b/vhosts/blog/public/tags/http/index.html index 53a57f48..9229f486 100644 --- a/vhosts/blog/public/tags/http/index.html +++ b/vhosts/blog/public/tags/http/index.html @@ -15,7 +15,7 @@ タグ「HTTP」一覧|REPL: Rest-Eat-Program Loop - +
diff --git a/vhosts/blog/public/tags/index.html b/vhosts/blog/public/tags/index.html index eb24c407..c0540cb0 100644 --- a/vhosts/blog/public/tags/index.html +++ b/vhosts/blog/public/tags/index.html @@ -13,7 +13,7 @@ タグ一覧|REPL: Rest-Eat-Program Loop - +
diff --git a/vhosts/blog/public/tags/isucon/index.html b/vhosts/blog/public/tags/isucon/index.html index 0112d179..f7633dd5 100644 --- a/vhosts/blog/public/tags/isucon/index.html +++ b/vhosts/blog/public/tags/isucon/index.html @@ -15,7 +15,7 @@ タグ「ISUCON」一覧|REPL: Rest-Eat-Program Loop - +
diff --git a/vhosts/blog/public/tags/mncore-challenge/index.html b/vhosts/blog/public/tags/mncore-challenge/index.html index 608e1deb..854f3283 100644 --- a/vhosts/blog/public/tags/mncore-challenge/index.html +++ b/vhosts/blog/public/tags/mncore-challenge/index.html @@ -15,7 +15,7 @@ タグ「MN-Core Challenge」一覧|REPL: Rest-Eat-Program Loop - +
diff --git a/vhosts/blog/public/tags/neovim/index.html b/vhosts/blog/public/tags/neovim/index.html index 51310be5..d7acdae8 100644 --- a/vhosts/blog/public/tags/neovim/index.html +++ b/vhosts/blog/public/tags/neovim/index.html @@ -15,7 +15,7 @@ タグ「Neovim」一覧|REPL: Rest-Eat-Program Loop - +
diff --git a/vhosts/blog/public/tags/note-to-self/index.html b/vhosts/blog/public/tags/note-to-self/index.html index 62455d19..f81b9ebd 100644 --- a/vhosts/blog/public/tags/note-to-self/index.html +++ b/vhosts/blog/public/tags/note-to-self/index.html @@ -15,7 +15,7 @@ タグ「備忘録」一覧|REPL: Rest-Eat-Program Loop - +
diff --git a/vhosts/blog/public/tags/ouj/index.html b/vhosts/blog/public/tags/ouj/index.html index 5cee2753..768c5abd 100644 --- a/vhosts/blog/public/tags/ouj/index.html +++ b/vhosts/blog/public/tags/ouj/index.html @@ -15,7 +15,7 @@ タグ「放送大学」一覧|REPL: Rest-Eat-Program Loop - +
diff --git a/vhosts/blog/public/tags/perl/index.html b/vhosts/blog/public/tags/perl/index.html index ee2506ba..a7bbe0db 100644 --- a/vhosts/blog/public/tags/perl/index.html +++ b/vhosts/blog/public/tags/perl/index.html @@ -15,7 +15,7 @@ タグ「Perl」一覧|REPL: Rest-Eat-Program Loop - +
diff --git a/vhosts/blog/public/tags/php/index.html b/vhosts/blog/public/tags/php/index.html index cb12380e..e2cc2e71 100644 --- a/vhosts/blog/public/tags/php/index.html +++ b/vhosts/blog/public/tags/php/index.html @@ -15,7 +15,7 @@ タグ「PHP」一覧|REPL: Rest-Eat-Program Loop - +
diff --git a/vhosts/blog/public/tags/phpcon-nagoya/index.html b/vhosts/blog/public/tags/phpcon-nagoya/index.html index c2878866..87adcb80 100644 --- a/vhosts/blog/public/tags/phpcon-nagoya/index.html +++ b/vhosts/blog/public/tags/phpcon-nagoya/index.html @@ -15,7 +15,7 @@ タグ「PHP カンファレンス名古屋」一覧|REPL: Rest-Eat-Program Loop - +
diff --git a/vhosts/blog/public/tags/phpcon-odawara/index.html b/vhosts/blog/public/tags/phpcon-odawara/index.html index 89229c9d..a6b0beb5 100644 --- a/vhosts/blog/public/tags/phpcon-odawara/index.html +++ b/vhosts/blog/public/tags/phpcon-odawara/index.html @@ -15,7 +15,7 @@ タグ「PHP カンファレンス小田原」一覧|REPL: Rest-Eat-Program Loop - +
diff --git a/vhosts/blog/public/tags/phpconfuk/index.html b/vhosts/blog/public/tags/phpconfuk/index.html index 9ad9931d..3076cf9d 100644 --- a/vhosts/blog/public/tags/phpconfuk/index.html +++ b/vhosts/blog/public/tags/phpconfuk/index.html @@ -15,7 +15,7 @@ タグ「PHP カンファレンス福岡」一覧|REPL: Rest-Eat-Program Loop - +
diff --git a/vhosts/blog/public/tags/phpconkagawa/index.html b/vhosts/blog/public/tags/phpconkagawa/index.html index b6ab4a45..a7173105 100644 --- a/vhosts/blog/public/tags/phpconkagawa/index.html +++ b/vhosts/blog/public/tags/phpconkagawa/index.html @@ -15,7 +15,7 @@ タグ「PHP カンファレンス香川」一覧|REPL: Rest-Eat-Program Loop - +
diff --git a/vhosts/blog/public/tags/phpconokinawa/index.html b/vhosts/blog/public/tags/phpconokinawa/index.html index ec8ba4b2..93adc600 100644 --- a/vhosts/blog/public/tags/phpconokinawa/index.html +++ b/vhosts/blog/public/tags/phpconokinawa/index.html @@ -15,7 +15,7 @@ タグ「PHP カンファレンス沖縄」一覧|REPL: Rest-Eat-Program Loop - +
diff --git a/vhosts/blog/public/tags/phperkaigi/index.html b/vhosts/blog/public/tags/phperkaigi/index.html index 4ee93aef..3193a1eb 100644 --- a/vhosts/blog/public/tags/phperkaigi/index.html +++ b/vhosts/blog/public/tags/phperkaigi/index.html @@ -15,7 +15,7 @@ タグ「PHPerKaigi」一覧|REPL: Rest-Eat-Program Loop - +
diff --git a/vhosts/blog/public/tags/phpkansai/index.html b/vhosts/blog/public/tags/phpkansai/index.html index 171d6cb7..9b3bdcce 100644 --- a/vhosts/blog/public/tags/phpkansai/index.html +++ b/vhosts/blog/public/tags/phpkansai/index.html @@ -15,7 +15,7 @@ タグ「PHP カンファレンス関西」一覧|REPL: Rest-Eat-Program Loop - +
diff --git a/vhosts/blog/public/tags/phpstudy-tokyo/index.html b/vhosts/blog/public/tags/phpstudy-tokyo/index.html index 764c975b..5f1cd716 100644 --- a/vhosts/blog/public/tags/phpstudy-tokyo/index.html +++ b/vhosts/blog/public/tags/phpstudy-tokyo/index.html @@ -15,7 +15,7 @@ タグ「PHP 勉強会@東京」一覧|REPL: Rest-Eat-Program Loop - +
diff --git a/vhosts/blog/public/tags/piet/index.html b/vhosts/blog/public/tags/piet/index.html index 0a6ea1e4..a5a14d35 100644 --- a/vhosts/blog/public/tags/piet/index.html +++ b/vhosts/blog/public/tags/piet/index.html @@ -15,7 +15,7 @@ タグ「Piet」一覧|REPL: Rest-Eat-Program Loop - +
diff --git a/vhosts/blog/public/tags/python/index.html b/vhosts/blog/public/tags/python/index.html index 1ed00d1e..5f3f87fd 100644 --- a/vhosts/blog/public/tags/python/index.html +++ b/vhosts/blog/public/tags/python/index.html @@ -15,7 +15,7 @@ タグ「Python」一覧|REPL: Rest-Eat-Program Loop - +
diff --git a/vhosts/blog/public/tags/python3/index.html b/vhosts/blog/public/tags/python3/index.html index 27bcd376..452da403 100644 --- a/vhosts/blog/public/tags/python3/index.html +++ b/vhosts/blog/public/tags/python3/index.html @@ -15,7 +15,7 @@ タグ「Python 3」一覧|REPL: Rest-Eat-Program Loop - +
diff --git a/vhosts/blog/public/tags/ruby/index.html b/vhosts/blog/public/tags/ruby/index.html index 7467ceb1..c5ac5f19 100644 --- a/vhosts/blog/public/tags/ruby/index.html +++ b/vhosts/blog/public/tags/ruby/index.html @@ -15,7 +15,7 @@ タグ「Ruby」一覧|REPL: Rest-Eat-Program Loop - +
diff --git a/vhosts/blog/public/tags/ruby3/index.html b/vhosts/blog/public/tags/ruby3/index.html index 56b5acce..11170d00 100644 --- a/vhosts/blog/public/tags/ruby3/index.html +++ b/vhosts/blog/public/tags/ruby3/index.html @@ -15,7 +15,7 @@ タグ「Ruby 3」一覧|REPL: Rest-Eat-Program Loop - +
diff --git a/vhosts/blog/public/tags/rust/index.html b/vhosts/blog/public/tags/rust/index.html index 0f24d169..346da982 100644 --- a/vhosts/blog/public/tags/rust/index.html +++ b/vhosts/blog/public/tags/rust/index.html @@ -15,7 +15,7 @@ タグ「Rust」一覧|REPL: Rest-Eat-Program Loop - +
diff --git a/vhosts/blog/public/tags/scala/index.html b/vhosts/blog/public/tags/scala/index.html index 755635e4..fcba99d3 100644 --- a/vhosts/blog/public/tags/scala/index.html +++ b/vhosts/blog/public/tags/scala/index.html @@ -15,7 +15,7 @@ タグ「Scala」一覧|REPL: Rest-Eat-Program Loop - +
diff --git a/vhosts/blog/public/tags/scalamatsuri/index.html b/vhosts/blog/public/tags/scalamatsuri/index.html index 11404c80..5167aac9 100644 --- a/vhosts/blog/public/tags/scalamatsuri/index.html +++ b/vhosts/blog/public/tags/scalamatsuri/index.html @@ -15,7 +15,7 @@ タグ「ScalaMatsuri」一覧|REPL: Rest-Eat-Program Loop - +
diff --git a/vhosts/blog/public/tags/vim/index.html b/vhosts/blog/public/tags/vim/index.html index 363e3877..335c102f 100644 --- a/vhosts/blog/public/tags/vim/index.html +++ b/vhosts/blog/public/tags/vim/index.html @@ -15,7 +15,7 @@ タグ「Vim」一覧|REPL: Rest-Eat-Program Loop - +
diff --git a/vhosts/blog/public/tags/wasm/index.html b/vhosts/blog/public/tags/wasm/index.html index 75265810..560075d2 100644 --- a/vhosts/blog/public/tags/wasm/index.html +++ b/vhosts/blog/public/tags/wasm/index.html @@ -15,7 +15,7 @@ タグ「WebAssembly」一覧|REPL: Rest-Eat-Program Loop - +
diff --git a/vhosts/blog/public/tags/wireguard/index.html b/vhosts/blog/public/tags/wireguard/index.html index 99fd522d..7541f413 100644 --- a/vhosts/blog/public/tags/wireguard/index.html +++ b/vhosts/blog/public/tags/wireguard/index.html @@ -15,7 +15,7 @@ タグ「WireGuard」一覧|REPL: Rest-Eat-Program Loop - +
diff --git a/vhosts/blog/public/tags/ya8/index.html b/vhosts/blog/public/tags/ya8/index.html index cbf17c14..766da04f 100644 --- a/vhosts/blog/public/tags/ya8/index.html +++ b/vhosts/blog/public/tags/ya8/index.html @@ -15,7 +15,7 @@ タグ「Ya8」一覧|REPL: Rest-Eat-Program Loop - +
diff --git a/vhosts/blog/public/tags/yaml/index.html b/vhosts/blog/public/tags/yaml/index.html index b3b14153..c7dbb150 100644 --- a/vhosts/blog/public/tags/yaml/index.html +++ b/vhosts/blog/public/tags/yaml/index.html @@ -15,7 +15,7 @@ タグ「YAML」一覧|REPL: Rest-Eat-Program Loop - +
diff --git a/vhosts/blog/public/tags/yapc/index.html b/vhosts/blog/public/tags/yapc/index.html index 03ab468d..32582b01 100644 --- a/vhosts/blog/public/tags/yapc/index.html +++ b/vhosts/blog/public/tags/yapc/index.html @@ -15,7 +15,7 @@ タグ「YAPC」一覧|REPL: Rest-Eat-Program Loop - +
diff --git a/vhosts/blog/public/tags/zsh/index.html b/vhosts/blog/public/tags/zsh/index.html index 35899518..f9494475 100644 --- a/vhosts/blog/public/tags/zsh/index.html +++ b/vhosts/blog/public/tags/zsh/index.html @@ -15,7 +15,7 @@ タグ「Zsh」一覧|REPL: Rest-Eat-Program Loop - +
diff --git a/vhosts/blog/static/hl.css b/vhosts/blog/static/hl.css deleted file mode 100644 index 275239a7..00000000 --- a/vhosts/blog/static/hl.css +++ /dev/null @@ -1,10 +0,0 @@ -pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}/*! - Theme: GitHub - Description: Light theme as seen on github.com - Author: github.com - Maintainer: @Hirse - Updated: 2021-05-15 - - Outdated base version: https://github.com/primer/github-syntax-light - Current colors taken from GitHub's CSS -*/.hljs{color:#24292e;background:#fff}.hljs-doctag,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-template-tag,.hljs-template-variable,.hljs-type,.hljs-variable.language_{color:#d73a49}.hljs-title,.hljs-title.class_,.hljs-title.class_.inherited__,.hljs-title.function_{color:#6f42c1}.hljs-attr,.hljs-attribute,.hljs-literal,.hljs-meta,.hljs-number,.hljs-operator,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id,.hljs-variable{color:#005cc5}.hljs-meta .hljs-string,.hljs-regexp,.hljs-string{color:#032f62}.hljs-built_in,.hljs-symbol{color:#e36209}.hljs-code,.hljs-comment,.hljs-formula{color:#6a737d}.hljs-name,.hljs-quote,.hljs-selector-pseudo,.hljs-selector-tag{color:#22863a}.hljs-subst{color:#24292e}.hljs-section{color:#005cc5;font-weight:700}.hljs-bullet{color:#735c0f}.hljs-emphasis{color:#24292e;font-style:italic}.hljs-strong{color:#24292e;font-weight:700}.hljs-addition{color:#22863a;background-color:#f0fff4}.hljs-deletion{color:#b31d28;background-color:#ffeef0} \ No newline at end of file diff --git a/vhosts/blog/static/style.css b/vhosts/blog/static/style.css index 25137117..18b6ffae 100644 --- a/vhosts/blog/static/style.css +++ b/vhosts/blog/static/style.css @@ -98,6 +98,10 @@ a:hover { color: #666; } +.codeblock { + background-color: #f5f5f5; +} + pre { background-color: #f5f5f5; padding: 1rem; @@ -133,7 +137,8 @@ code { font-size: 0.9rem; } -pre code { +.shiki code { + background-color: unset; padding: 0; } -- cgit v1.2.3-70-g09d2