diff options
| author | nsfisis <nsfisis@gmail.com> | 2022-12-23 23:27:09 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2023-03-06 01:46:04 +0900 |
| commit | 88ba6cfe220216f371f8756921059fac51a21262 (patch) | |
| tree | f272db2a0a3340f103df6618f19a101e65941b37 | |
| parent | 8f988a6e899aed678406ddfac1be4ef105439274 (diff) | |
| download | blog.nsfisis.dev-88ba6cfe220216f371f8756921059fac51a21262.tar.gz blog.nsfisis.dev-88ba6cfe220216f371f8756921059fac51a21262.tar.zst blog.nsfisis.dev-88ba6cfe220216f371f8756921059fac51a21262.zip | |
AsciiDoc to DocBook
112 files changed, 11333 insertions, 11506 deletions
diff --git a/CHEATSHEET.md b/CHEATSHEET.md new file mode 100644 index 0000000..9ba0c9c --- /dev/null +++ b/CHEATSHEET.md @@ -0,0 +1,38 @@ +article + info + title + abstract + keywordset + keyword + revhistory + revision + date + revremark + section + + + +anchor +article +blockquote +caption +date +emphasis +informaltable +itemizedlist +link +listitem +literal +literallayout +note +orderedlist +programlisting +section +simpara +table +tbody +td +tfoot +thead +title +tr diff --git a/Gemfile b/Gemfile deleted file mode 100644 index 1784943..0000000 --- a/Gemfile +++ /dev/null @@ -1,7 +0,0 @@ -# frozen_string_literal: true - -source "https://rubygems.org" - -gem "asciidoctor" -gem "concurrent-ruby" -gem "rouge" diff --git a/Gemfile.lock b/Gemfile.lock deleted file mode 100644 index af3d42c..0000000 --- a/Gemfile.lock +++ /dev/null @@ -1,17 +0,0 @@ -GEM - remote: https://rubygems.org/ - specs: - asciidoctor (2.0.18) - concurrent-ruby (1.1.10) - rouge (4.0.0) - -PLATFORMS - x86_64-darwin-19 - -DEPENDENCIES - asciidoctor - concurrent-ruby - rouge - -BUNDLED WITH - 2.3.11 @@ -22,7 +22,7 @@ clean: .PHONY: gen gen: - @ruby nuldoc.rb + @./nuldoc .PHONY: local_up local_up: @@ -12,14 +12,14 @@ Create a new post. ``` $ mkdir -p content/posts/$(date +'%Y-%m-%d') -$ touch content/posts/$(date +'%Y-%m-%d')/[TITLE].adoc +$ touch content/posts/$(date +'%Y-%m-%d')/[TITLE].xml ``` ## TODO * [x] Add /posts/ page * [x] Stylesheets -* [x] Syntax highlight +* [ ] Syntax highlight * [ ] Add /tags/ page * [ ] Add / page * [ ] Add /about/ page @@ -77,8 +77,3 @@ public ├── feed.xml └── index.html ``` - - -## References - -* https://docs.asciidoctor.org/asciidoctor/latest/ diff --git a/content/posts/2021-03-05/my-first-post.adoc b/content/posts/2021-03-05/my-first-post.adoc deleted file mode 100644 index 664dd5c..0000000 --- a/content/posts/2021-03-05/my-first-post.adoc +++ /dev/null @@ -1,13 +0,0 @@ -= My First Post -:description: これはテスト投稿です。これはテスト投稿です。これはテスト投稿です。 -:revision-1: 2021-03-05 公開 - -== Test - -Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod -tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim -veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea -commodo consequat. Duis aute irure dolor in reprehenderit in voluptate -velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint -occaecat cupidatat non proident, sunt in culpa qui officia deserunt -mollit anim id est laborum. diff --git a/content/posts/2021-03-05/my-first-post.xml b/content/posts/2021-03-05/my-first-post.xml new file mode 100644 index 0000000..9f9c48e --- /dev/null +++ b/content/posts/2021-03-05/my-first-post.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="UTF-8"?> +<article xmlns="http://docbook.org/ns/docbook" xmlns:xl="http://www.w3.org/1999/xlink" version="5.0"> + <info> + <title>My First Post</title> + <abstract> + これはテスト投稿です。これはテスト投稿です。これはテスト投稿です。 + </abstract> + <revhistory> + <revision> + <date>2021-03-05</date> + <revremark>公開</revremark> + </revision> + </revhistory> + </info> + <section xml:id="test"> + <title>Test</title> + <simpara> + Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod + tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim + veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea + commodo consequat. Duis aute irure dolor in reprehenderit in voluptate + velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint + occaecat cupidatat non proident, sunt in culpa qui officia deserunt + mollit anim id est laborum. + </simpara> + </section> +</article> diff --git a/content/posts/2021-03-30/phperkaigi-2021.adoc b/content/posts/2021-03-30/phperkaigi-2021.adoc deleted file mode 100644 index e843e83..0000000 --- a/content/posts/2021-03-30/phperkaigi-2021.adoc +++ /dev/null @@ -1,532 +0,0 @@ -= PHPerKaigi 2021 -:tags: conference, php, phperkaigi -:description: 2021-03-26 から 2021-03-28 にかけて開催された、PHPerKaigi 2021 に参加した。 -:revision-1: 2021-03-30 公開 - -== PHPerKaigi 2021 参加レポ - -2021-03-26 から 2021-03-28 -にかけて開催された、 https://phperkaigi.jp/2021/[PHPerKaigi 2021] -に一般参加者として参加した。 -弊社 https://www.dgcircus.com/[デジタルサーカス株式会社] -(今年1月から勤務) -はダイヤモンドスポンサーとなっており、スポンサー枠のチケットを使わせていただいた。 - -このようなカンファレンスには初めて参加するのでかねてより心待ちにしていたのだが、生憎2日目から体調を崩してしまい、この記事も途中までとなっている。まだ見ていないセッションも多いが、ひとまず現時点での参加レポを書いておく。 - -発表はトラック A、B に分かれていたのだが、今回はすべて A -トラックを視聴している (切り替えるのが面倒だっただけ)。 - -=== 凡例 - -____ -発表・スライドのメモ (引用ではない) -____ - -感想など - -=== Day 0 前夜祭 (2021/03/27) - -==== 17:30 [A] - -PHP で AWS Lambda - -____ -Rails のプロジェクトを PHPer のメンバのみでメンテ →他のメンバもわかる -PHP にリプレースを検討 - -* サーバレス -* サーバ・インフラの管理が不要 -* アプリケーションコードの知識だけで保守可能 - -ゼロベースで作れる案件が (Railsの件とは別に) -あるため、そちらで試験的に導入? - -AWSの学習 AWS のドキュメント DevelopersIO - -AWS Lambda のカスタムランタイムで PHP を動かす - -サーバのセットアップや維持管理を気にしなくて良い サーバーレスで PHP -を動かすツールがすでにある サーバーレス構築はすんなり - -今は Laravel がルーティングしている Laravel Livewire を Lambda -に載せられないか? デプロイ方法は? バッチ処理は? (Lambda は -15分の制限) - -Lambda でコンテナイメージがサポートされるように - -抽象化されたもの「だけ」しか知らないよりも具象の理解は助けになる -____ - -AWS Lambda のような Function as a Service -はマイクロサービス化における一つの到達点に思えるのだが、これを使って実際に -web サービスを作る具体的なイメージがまだ見えない (注: すべて for me -として書いている)。 - -PHP on AWS Lambda があれだけ簡単に動かせるのには驚いた。 - -勝手に AWS Lambda だとフットプリントの軽さが求められそう (= PHP + -Laravel などでは動かなさそう) -だという先入観を持っていたのだが、この発表のデモによればそうでもないらしい。 - -==== 18:10 [A] - -大規模サイトの SEO - -____ -大規模サイト (100万ページ以上) Google の基準 - -クロールバジェットを意識したSEO - -大規模サイトでコンテンツが中頻度 (1回/週) で更新 OR 中規模サイト -(10,000以上) でコンテンツが目まぐるしく変更される -これを満たさないなら、クロールバジェットを考えなくてもいい - -サーチコンソール 「カバレッジ」の「除外」 -多すぎるのは問題→クロールバジェットを浪費している - -* クエリの順番を決める -* 空の値のルールを決めておく -* リダイレクトすればインデックスはうまくいく -* リンクが存在する限りクロールはされる - -リニューアル前のURL - -インデックスは移行される -リンクのURLが存在する限り、別のURLとしてクロールされる -リダイレクトされるとはいえ、リニューアル前のURLは移行した方が良い -リニューアルで無視されるようになったパラメータも注意 - -robotes.txt で拒否しているのにクロールされる 一時的に拒否を外して 404 や -301 を読ませる 内部リンクを確認する JS でのリンクに書き換え - -クエリパラメータからURLのパスに `/tokyo?area=HOGE` → `/tokyo/HOGE` - -URL 設計だいじ -____ - -SEO (Search Engine Optimization) -は大して知らないので新鮮な話が多かった。その分語れることも少ない……。 - -==== 18:50 [A] - -____ -知覚可能 操作可能 理解可能 堅牢 ちゃんとしたHTMLを書く -(閉じタグ・入れ子構造など) - -* 標準の HTML を適切に使う -* WAI-ARIA -* キーボードフレンドリー -* マシンフレンドリー -* SEOフレンドリー - -button タグ →キーボード h1 タグ →スクリーンリーダー・クローラ a タグ - -WAI-ARIA HTML では表現できないセマンティクスを追加する - -* ロール -** 何をするのか? -** ユーザーアクションによって変化しない -* プロパティ -** 関連づけられたデータ -* ステート -** 現在の状態 - -まずは標準の HTML 要素で解決する 何でもかんでも WAI-ARIA -を使えばいいというものではない - -マウスホバーでツールチップが出てくるが、キーボード操作では出てこない - -VoiceOver - -全ての属性を使う必要はない -あくまでアクセシビリティを上げるための方法の一つにすぎない -____ - -つい最近 WAI-ARIA -についての記事を読んだばかりだったので個人的にタイムリーな話題だった。(あまりこの言葉を使いたくないのだが) -いわゆる「健常者」にとって、こうした問題を普段の生活の中で意識するのは難しい。だからこそ情報へのアンテナは張っておくようにしたい。 - -==== 19:30 [A] - -PHP で FUSE - -個人的に楽しみだった発表。 - -____ -VFS (virtual filesystem) vs 具体的なファイルシステム - -最適な実装方法は状況により異なる - -アプリケーションに見せるAPIは変えずに実装を隠蔽する→VFS - -カーネルのプログラムを作るのは難しい -* 権限がデカすぎる -* システム全体がクラッシュ -* セキュリティリスク -* 開発サイクルを回しづらい -* ネイティブコードにコンパイルされる言語である必要がある - -Filesystem in USEr space (FUSE) - -* 特定の C の関数を呼ぶことで filesystem が作れる -* FFI を持つ言語なら FUSE が使える - -SSHFS / s3fs / Docker Desktop - -Linux 以外でも使える - -* dokany (on Windows) -* osxfuse - -VFS: システムコールが呼ばれると、ファイルシステムによってコール FUSE: -カーネル空間からユーザ空間へ通信 - -高レベルなラッパで型をつける - -PHP 以外では Wordpress を FUSE にマウントする実装がある (C, Python など) - -* grep できる -* sed できる -* 編集できる -____ - -期待通りの興味深い発表だった。FUSE -自体も今回の発表で知ったのだが、これ本体の実装を見るのも面白そうだ。 -この発表を聞きながらファイルシステムにマウントできそうなものを考えていたのだが、およそ木構造をしているものすべてと言えそうだ -(ハンマーしか持っていないと云々)。何かできそうだがなかなか思いつかない。 - -=== Day 1 (2021/03/27) - -==== 10:50 [A] - -ATDD - -____ -* ユーザーストーリー -* ユニットテスト -* CI/CD - -ユーザストーリーの受け入れ条件が曖昧になりがち -デグレチェックがユニットレベルでは収まらない場合、手動で同じシナリオをテストしている - -Q2の強化 アジャイルテストの4象限 - -技術面/ビジネス面 -開発チーム支援(コーディング前・コーディング中)/製品批評(コーディング後) - -* Q1: 技術面 & チーム支援 -** TDD -** ユニットテストなど -* Q2: ビジネス面 & チーム支援 -** ATDD -** ビジネス面の受け入れテストで駆動する - -Agile Alliance ユーザストーリーのスキルレベルを高める - -テストピラミッド - -* UI Tests -* Service Tests -* Unit Tests -* 異なる粒度のテストを書く -* 高レベルになるほど、持つべきテストは少なくなる -** ピラミッド型になる - -高レベルテストが多すぎる→アイスクリームコーン アンチパターン - -ATDD (Acceptance TDD) API経由・UI経由での高レベルテスト E2E test - -ストーリ受け入れテスト - -入れ子のフィードバックループ ATDD(外側) と TDD(内側) - -外部品質・内部品質 - -バーティカルスライスのデリバリー - -* cucumber -* gauge -* behat - -ユビキタス言語 手動テストもspecに書く 自動化は可能だがコスパが悪い -失敗することがわかっているテスト(レッドテスト)はCIから外す -失敗時の原因究明が難しい 饒舌なエラーメッセージ 状況のスナップショット - -Continuous Testing -____ - -User Acceptance Test (UAT) -くらいの規模になると個人開発・趣味開発では触れない領域なので、大いに勉強になった。スライドに添付されている資料が相当に充実していたので、これを読むのが本番といった様相すら感じる。 -高レベルテストの自動化は現在のプロジェクトでも感じており、自動化のチャンスは伺っている。とはいえセッションでも指摘されているように自動化することにコストがかかりすぎる領域があるのも事実で、そのバランスが難しい。 - -==== 11:50 [A] - -型解析を用いたリファクタリング - -型のある世界で生きてきた身として大いに楽しみにしていた発表。 - -____ -* PHPStan -* Phan -* Psalm - -autoload も認識できる bootstrapFiles - -編集箇所と利用箇所を CI でチェック ルールレベルを徐々に引き上げていく -警告が多すぎると見落としてしまう・無視されやすくなる - -型がついていないことによるエラーが多い - -型よりも詳細な検査 `Util_Assert::min` - -SQL を静的解析 placeholder の型付け - -警告レベルを低いレベルから導入 タイプヒントを積極的に書いていく PHPStan -の拡張を追加する -____ - -昨今、動的型付き言語での型宣言・型アノテーション・型ヒントの導入が相次いでいる。長らく静的型付き言語を書いてきた私からすると、ようやく気づいたかといったところだが、ともかく型を導入する言語が増えてきた。 -今のプロジェクトでも新しく追加するコードには型をつけるよう努めているが、どうしても古いコードには型がついていない。個人的には型のないコードに対してどう型を自動的に付けるかという点に興味があり、その点で -Ruby の typeprof には注目している。 - -==== 12:30 [A] - -昼食をとっていた。事前に何か食料を買っておくべきだった。 - -==== 13:10 [A] - -Documentation as Code - -この発表も以前から非常に楽しみにしていた。 - -____ -開発開始までのオーバーヘッド 新規にチームにジョイン -担当範囲外の機能を理解 オンボーディングのコスト - -PHPerKaigi 2020 で発表あり - -継続的にシステムの理解を助けるドキュメント - -継続的ドキュメンテーション システムとドキュメントの乖離 - -書いてあることが間違っている・足りない * 徐々にずれていく * -システムの更新タイミングとドキュメントの更新タイミングに差がある - -システムとドキュメントは対応関係がある * 間違ったドキュメント * -存在しないドキュメント - -システムとドキュメントの乖離を定量化する 継続的に -システムの更新に近いタイミングで ドキュメントを更新し続ける - -Documentation as Code - -コードと同じツールでドキュメントを書く * issue tracker * vcs * plain -text markup * automation - -開発者 システム ドキュメント 構造化データ ソフトウェア - -システムから構造化データを抽出する PHPDoc OpenAPI - -ビュー 関心ごとに合わせてアーキテクチャを一つ以上の側面(断面)で説明する - -ビューの単位でドキュメントに - -スタックトレースからのドキュメント生成 -____ - -ドキュメントの管理は現プロジェクトでも課題と感じている。作られた当初は正しくても、実態と乖離していくのを止めるのは困難を極める。全体的に興味深い発表だったが、特にスタックトレースからのドキュメント生成というアイデアに惹かれるものを感じた。スタックトレースという実態と不可分な -(乖離しない) -情報を起点にするのは理にかなっている。問題はトレースをいつ、どう取るかだろうか。それを自動化しなければ、実態との乖離が避けられないだろう。 - -==== 14:10 [A] - -cookie による session 管理 - -全体的に基本的な話だったので特に触れない。Cookie -やセッションの話としては非常に分かりやすくまとめられていたので、知らない人が学ぶにはいい教材だろう。 - -==== 14:50 [A] - -PHP のエラーと例外 - -____ -エラー PHPエンジンがエラーを通知する 例外 プログラムが投げる - -PHP7-8とエラー - -PHPエンジンのエラーの一部が に変換されるようになった → try-catch -で捕捉できる - -は例外とは異なる - -PHP8 でエラーレベルの引き上げ - -* 捕捉すべきもの -** recoverable -* 捕捉すべきでないもの -** unrecoverable -** 開発時に対処できるもの - -例外 * 捕捉して事後処理 * 捕捉せず(or 捕捉した上で)さらに上に是非を問う - -開発段階で例外を把握し、ハンドリングを考えておく - -と - -はキャッチすべきでない - -* {blank} -+ -** 本番で起きてはいけない -* {blank} -+ -** 本番で起きてはいけない →生じないのだから捕捉もしない -* {blank} -+ -** 起こるかもしれないので本番環境でも考慮する - -捕捉して対応するのではなく、未然に防ぐ - -独自例外を使う を投げてしまうと、 catch ()せざるを得ない →catch -範囲が広すぎる - -SPL の例外を使う - -例外翻訳 -上位のレイヤが下位のレイヤの例外を捕捉し、上位レイヤのAPIに「翻訳」する -下位レイヤの知識に依存させない - -@throws 捕捉してほしい例外を書き連ねておく - -呼び出しもとに負わせたい責任 -____ - -PHP を学んでいる途中の私としては、今まさに聞きたい発表だった (現時点で -PHP を書き始めてから 4ヶ月ほどになる)。 - -個人的に例外やエラーを最もうまく扱っているのは Go、Swift、Rust、Haskell -などのエラーを「値として」扱う言語だと思っている。try-catch -は通常の処理フローを完全に壊してしまう上、構文としても重すぎる。値としてのエラー通知は -C言語時代への回帰ともいえるが、その頃と異なるのはエラーを暗黙のうちに握り潰すことがないということだ。これらの言語は型を持っており、静的に検証ができる -(C のそれはまともな型付けではない。念のため)。 - -PHP -のように、すでに例外が言語システムに根ざしている言語ではどうすればよいか。この場合も同じく静的検証の力を借りることになるだろう。 - -==== 15:30 [A] - -Laravel のメール認証 - -Laravel -の知識がない私にはまったくついていけなかった。また、個人的にタイトルがややミスリーディングに感じた。 - -==== 16:10 [A] - -gRPC - -____ -Unary RPCs Server streaming RPCs Client streaming RPCs Bidirectional -streaming RPCs - -Protobuf - -実装とAPIが乖離しにくい 自動生成 複数言語でも相互に使える - -マイクロサービスのサービス通信 スマホアプリ ゲームサーバ - -PHP では? - -PHP ではストリーミングが難しい リクエストごとにプロセスが使い捨て - -PHP ではgRPCのクライアントしか対応していない - -gRPC-Web ブラウザで扱うためのJSライブラリ+プロトコル - -HTTP/1.1 でも使える Unary RPC と Server streaming RPC のみ - -Envoy Nginx などで相互に gRPC と gRPC-Web で変換 - -Amp イベント駆動な並行処理のフレームワーク - -HTTP/2 対応 - -C#のgRPC-Webが楽 -____ - -(発表の中でもまさに同じことをおっしゃっていたが) PHP -以外の方が向いているだろう、というのが第一の感想である。gRPC -はそれ自体というよりも Protobuf -というエコシステムに乗れることのメリットが大きいと感じる。そのエコシステムにうまく乗れない時点で、うーんという感じ。 - -==== 16:50 [A] - -アーキテクチャテスト - -____ -Independent Core Layer Pattern - -開発初期のアーキテクチャが崩れる -アーキテクチャ観点のコードレビューができない - -どこにクラスを置けばよいか? ドキュメントがない - -アーキテクチャ設計に関する知識が属人化・暗黙知化 - -ガイドライン * 最初にルールを決めるのは簡単 * -ルール通り作り始めるのも簡単 * -→維持するのが難しい、人が決めたものゆえ壊れやすい - -PHP の特性 * クラスは public * 可視性の制御が public / protected / -private のみ * 依存関係の制御が困難 - -アーキテクチャテスト -クラスの依存関係や実装ルールをコードとして表現し、自動テスト化する - -* deptrac -* phpat - -Independent Core Layer Pattern - -アーキテクチャテストの失敗 * 実装誤り * or アーキテクチャが適切でない * -開発の過程でフィードバックしていく - -モジュラーモノリス→マイクロサービスへ -____ - -=== Day 2 (2021/03/28) - -冒頭に書いた通り、2日目から体調が悪くまともに聴けていない。途中までは頭痛を我慢しつつ見ていたのだが、まともに入ってこなかった。 - -残念ではあるが、いずれにせよ見られていない発表は他にもあるので、今週末にでもまとめて見ようと思う。 - -=== 全体の感想 - -Day 2 -にほとんど参加できなかったのは残念だが、イベント自体は大変楽しく、また興味深いものであった。自分がまったく知らない領域の話を聞けるのはこうしたイベントならではだと感じる。オンライン開催ゆえ現地に行く必要がなく、気軽に参加できたのも -(特に初参加者として) 嬉しいポイントだった。 - -今回、雑談/登壇者への質問等向けに Discord -サーバもあったのだが、こちらは参加こそしたものの ROM -のままになってしまった。発表に1ウィンドウ、メモを書くのに1ウィンドウ、Discord -表示に -1ウィンドウで私にはもう脳のリソースとディスプレイのスペースが追いつかなかった -(さらにいうと Zoom -でアンカンファレンスもやっていたようだ。こちらはまったく参加していない)。 - -1つ個人的な反省点としては、一つ一つのセッションを真剣に聞き過ぎたというものがある。もっと適当に聞いておけばよかった。これだけだと大変語弊があるのだが、言い方を変えると、Discord -しかりアンカンファレンスしかり「このイベントのこの瞬間にしかないコンテンツ」に触れずに、後から見返せる発表やスライドに注力してしまった、ということだ。発表の詳細な見直しはあとからできるのだから、今しかできないことを考えるべきだった。 -まあ初カンファレンスだし、とお茶を濁しておこう。 - -さて、カンファレンスで一つ気になったことがある。それは、Discord -という書き込み場所が増えたことでニコ生のコメントの流量が吸い取られてしまったのではないか、という点だ。ニコニコだけ見ていると過疎っているかのように見えた発表も、Discord -の方では盛り上がっている、というのを何度か見かけた。ニコニコのコメント方式は盛り上がりを如実に反映するが、逆もまたしかり。Discord -があったこと自体はプラスだったと思うが、この点はマイナスだったのではないかと感じる。 - -''''' - -最後になりましたが、毎年の PHPerKaigi -開催にご尽力されている皆様、スピーカーの皆様、楽しい3日間でした。ありがとうございました! -(ずっと常体で書いてしまったのでいきなり仏頂面から笑顔になったようで気持ち悪い) - -ではまた来年。 diff --git a/content/posts/2021-03-30/phperkaigi-2021.xml b/content/posts/2021-03-30/phperkaigi-2021.xml new file mode 100644 index 0000000..ea9fb37 --- /dev/null +++ b/content/posts/2021-03-30/phperkaigi-2021.xml @@ -0,0 +1,572 @@ +<?xml version="1.0" encoding="UTF-8"?> +<article xmlns="http://docbook.org/ns/docbook" xmlns:xl="http://www.w3.org/1999/xlink" version="5.0"> + <info> + <title>PHPerKaigi 2021</title> + <abstract> + 2021-03-26 から 2021-03-28 にかけて開催された、PHPerKaigi 2021 に参加した。 + </abstract> + <keywordset> + <keyword>conference</keyword> + <keyword>php</keyword> + <keyword>phperkaigi</keyword> + </keywordset> + <revhistory> + <revision> + <date>2021-03-30</date> + <revremark>公開</revremark> + </revision> + </revhistory> + </info> + <section xml:id="_phperkaigi_2021_参加レポ"> + <title>PHPerKaigi 2021 参加レポ</title> + <simpara>2021-03-26 から 2021-03-28 + にかけて開催された、 <link xl:href="https://phperkaigi.jp/2021/">PHPerKaigi 2021</link> + に一般参加者として参加した。 + 弊社 <link xl:href="https://www.dgcircus.com/">デジタルサーカス株式会社</link> + (今年1月から勤務) + はダイヤモンドスポンサーとなっており、スポンサー枠のチケットを使わせていただいた。</simpara> + <simpara>このようなカンファレンスには初めて参加するのでかねてより心待ちにしていたのだが、生憎2日目から体調を崩してしまい、この記事も途中までとなっている。まだ見ていないセッションも多いが、ひとまず現時点での参加レポを書いておく。</simpara> + <simpara>発表はトラック A、B に分かれていたのだが、今回はすべて A + トラックを視聴している (切り替えるのが面倒だっただけ)。</simpara> +<section xml:id="_凡例"> + <title>凡例</title> + <blockquote> + <simpara>発表・スライドのメモ (引用ではない)</simpara> + </blockquote> + <simpara>感想など</simpara> +</section> +<section xml:id="_day_0_前夜祭_20210327"> + <title>Day 0 前夜祭 (2021/03/27)</title> + <section xml:id="_1730_a"> + <title>17:30 [A]</title> + <simpara>PHP で AWS Lambda</simpara> + <blockquote> + <simpara>Rails のプロジェクトを PHPer のメンバのみでメンテ →他のメンバもわかる + PHP にリプレースを検討</simpara> + <itemizedlist> + <listitem> + <simpara>サーバレス</simpara> + </listitem> + <listitem> + <simpara>サーバ・インフラの管理が不要</simpara> + </listitem> + <listitem> + <simpara>アプリケーションコードの知識だけで保守可能</simpara> + </listitem> + </itemizedlist> + <simpara>ゼロベースで作れる案件が (Railsの件とは別に) + あるため、そちらで試験的に導入?</simpara> + <simpara>AWSの学習 AWS のドキュメント DevelopersIO</simpara> + <simpara>AWS Lambda のカスタムランタイムで PHP を動かす</simpara> + <simpara>サーバのセットアップや維持管理を気にしなくて良い サーバーレスで PHP + を動かすツールがすでにある サーバーレス構築はすんなり</simpara> +<simpara>今は Laravel がルーティングしている Laravel Livewire を Lambda +に載せられないか? デプロイ方法は? バッチ処理は? (Lambda は +15分の制限)</simpara> +<simpara>Lambda でコンテナイメージがサポートされるように</simpara> +<simpara>抽象化されたもの「だけ」しか知らないよりも具象の理解は助けになる</simpara> +</blockquote> +<simpara>AWS Lambda のような Function as a Service +はマイクロサービス化における一つの到達点に思えるのだが、これを使って実際に +web サービスを作る具体的なイメージがまだ見えない (注: すべて for me +として書いている)。</simpara> +<simpara>PHP on AWS Lambda があれだけ簡単に動かせるのには驚いた。</simpara> +<simpara>勝手に AWS Lambda だとフットプリントの軽さが求められそう (= PHP + + Laravel などでは動かなさそう) + だという先入観を持っていたのだが、この発表のデモによればそうでもないらしい。</simpara> +</section> +<section xml:id="_1810_a"> + <title>18:10 [A]</title> + <simpara>大規模サイトの SEO</simpara> + <blockquote> + <simpara>大規模サイト (100万ページ以上) Google の基準</simpara> + <simpara>クロールバジェットを意識したSEO</simpara> + <simpara>大規模サイトでコンテンツが中頻度 (1回/週) で更新 OR 中規模サイト + (10,000以上) でコンテンツが目まぐるしく変更される + これを満たさないなら、クロールバジェットを考えなくてもいい</simpara> + <simpara>サーチコンソール 「カバレッジ」の「除外」 + 多すぎるのは問題→クロールバジェットを浪費している</simpara> +<itemizedlist> + <listitem> + <simpara>クエリの順番を決める</simpara> + </listitem> + <listitem> + <simpara>空の値のルールを決めておく</simpara> + </listitem> + <listitem> + <simpara>リダイレクトすればインデックスはうまくいく</simpara> + </listitem> + <listitem> + <simpara>リンクが存在する限りクロールはされる</simpara> + </listitem> +</itemizedlist> +<simpara>リニューアル前のURL</simpara> +<simpara>インデックスは移行される +リンクのURLが存在する限り、別のURLとしてクロールされる +リダイレクトされるとはいえ、リニューアル前のURLは移行した方が良い +リニューアルで無視されるようになったパラメータも注意</simpara> +<simpara>robotes.txt で拒否しているのにクロールされる 一時的に拒否を外して 404 や +301 を読ませる 内部リンクを確認する JS でのリンクに書き換え</simpara> +<simpara>クエリパラメータからURLのパスに <literal>/tokyo?area=HOGE</literal> → <literal>/tokyo/HOGE</literal></simpara> +<simpara>URL 設計だいじ</simpara> +</blockquote> +<simpara>SEO (Search Engine Optimization) +は大して知らないので新鮮な話が多かった。その分語れることも少ない……。</simpara> +</section> +<section xml:id="_1850_a"> + <title>18:50 [A]</title> + <blockquote> + <simpara>知覚可能 操作可能 理解可能 堅牢 ちゃんとしたHTMLを書く + (閉じタグ・入れ子構造など)</simpara> + <itemizedlist> + <listitem> + <simpara>標準の HTML を適切に使う</simpara> + </listitem> + <listitem> + <simpara>WAI-ARIA</simpara> + </listitem> + <listitem> + <simpara>キーボードフレンドリー</simpara> + </listitem> + <listitem> + <simpara>マシンフレンドリー</simpara> + </listitem> + <listitem> + <simpara>SEOフレンドリー</simpara> + </listitem> + </itemizedlist> + <simpara>button タグ →キーボード h1 タグ →スクリーンリーダー・クローラ a タグ</simpara> + <simpara>WAI-ARIA HTML では表現できないセマンティクスを追加する</simpara> + <itemizedlist> + <listitem> + <simpara>ロール</simpara> + <itemizedlist> + <listitem> + <simpara>何をするのか?</simpara> + </listitem> + <listitem> + <simpara>ユーザーアクションによって変化しない</simpara> + </listitem> + </itemizedlist> + </listitem> + <listitem> + <simpara>プロパティ</simpara> + <itemizedlist> + <listitem> + <simpara>関連づけられたデータ</simpara> + </listitem> + </itemizedlist> + </listitem> + <listitem> + <simpara>ステート</simpara> + <itemizedlist> + <listitem> + <simpara>現在の状態</simpara> + </listitem> + </itemizedlist> + </listitem> + </itemizedlist> + <simpara>まずは標準の HTML 要素で解決する 何でもかんでも WAI-ARIA + を使えばいいというものではない</simpara> +<simpara>マウスホバーでツールチップが出てくるが、キーボード操作では出てこない</simpara> +<simpara>VoiceOver</simpara> +<simpara>全ての属性を使う必要はない +あくまでアクセシビリティを上げるための方法の一つにすぎない</simpara> +</blockquote> +<simpara>つい最近 WAI-ARIA +についての記事を読んだばかりだったので個人的にタイムリーな話題だった。(あまりこの言葉を使いたくないのだが) +いわゆる「健常者」にとって、こうした問題を普段の生活の中で意識するのは難しい。だからこそ情報へのアンテナは張っておくようにしたい。</simpara> +</section> +<section xml:id="_1930_a"> + <title>19:30 [A]</title> + <simpara>PHP で FUSE</simpara> + <simpara>個人的に楽しみだった発表。</simpara> + <blockquote> + <simpara>VFS (virtual filesystem) vs 具体的なファイルシステム</simpara> + <simpara>最適な実装方法は状況により異なる</simpara> + <simpara>アプリケーションに見せるAPIは変えずに実装を隠蔽する→VFS</simpara> + <simpara>カーネルのプログラムを作るのは難しい + * 権限がデカすぎる + * システム全体がクラッシュ + * セキュリティリスク + * 開発サイクルを回しづらい + * ネイティブコードにコンパイルされる言語である必要がある</simpara> + <simpara>Filesystem in USEr space (FUSE)</simpara> + <itemizedlist> + <listitem> + <simpara>特定の C の関数を呼ぶことで filesystem が作れる</simpara> + </listitem> + <listitem> + <simpara>FFI を持つ言語なら FUSE が使える</simpara> + </listitem> + </itemizedlist> + <simpara>SSHFS / s3fs / Docker Desktop</simpara> + <simpara>Linux 以外でも使える</simpara> + <itemizedlist> + <listitem> + <simpara>dokany (on Windows)</simpara> + </listitem> + <listitem> + <simpara>osxfuse</simpara> + </listitem> + </itemizedlist> + <simpara>VFS: システムコールが呼ばれると、ファイルシステムによってコール FUSE: + カーネル空間からユーザ空間へ通信</simpara> +<simpara>高レベルなラッパで型をつける</simpara> +<simpara>PHP 以外では Wordpress を FUSE にマウントする実装がある (C, Python など)</simpara> +<itemizedlist> + <listitem> + <simpara>grep できる</simpara> + </listitem> + <listitem> + <simpara>sed できる</simpara> + </listitem> + <listitem> + <simpara>編集できる</simpara> + </listitem> +</itemizedlist> +</blockquote> +<simpara>期待通りの興味深い発表だった。FUSE +自体も今回の発表で知ったのだが、これ本体の実装を見るのも面白そうだ。 +この発表を聞きながらファイルシステムにマウントできそうなものを考えていたのだが、およそ木構造をしているものすべてと言えそうだ +(ハンマーしか持っていないと云々)。何かできそうだがなかなか思いつかない。</simpara> +</section> +</section> +<section xml:id="_day_1_20210327"> + <title>Day 1 (2021/03/27)</title> + <section xml:id="_1050_a"> + <title>10:50 [A]</title> + <simpara>ATDD</simpara> + <blockquote> + <itemizedlist> + <listitem> + <simpara>ユーザーストーリー</simpara> + </listitem> + <listitem> + <simpara>ユニットテスト</simpara> + </listitem> + <listitem> + <simpara>CI/CD</simpara> + </listitem> + </itemizedlist> + <simpara>ユーザストーリーの受け入れ条件が曖昧になりがち + デグレチェックがユニットレベルでは収まらない場合、手動で同じシナリオをテストしている</simpara> + <simpara>Q2の強化 アジャイルテストの4象限</simpara> + <simpara>技術面/ビジネス面 + 開発チーム支援(コーディング前・コーディング中)/製品批評(コーディング後)</simpara> + <itemizedlist> + <listitem> + <simpara>Q1: 技術面 & チーム支援</simpara> + <itemizedlist> + <listitem> + <simpara>TDD</simpara> + </listitem> + <listitem> + <simpara>ユニットテストなど</simpara> + </listitem> + </itemizedlist> + </listitem> + <listitem> + <simpara>Q2: ビジネス面 & チーム支援</simpara> + <itemizedlist> + <listitem> + <simpara>ATDD</simpara> + </listitem> + <listitem> + <simpara>ビジネス面の受け入れテストで駆動する</simpara> + </listitem> + </itemizedlist> + </listitem> + </itemizedlist> + <simpara>Agile Alliance ユーザストーリーのスキルレベルを高める</simpara> + <simpara>テストピラミッド</simpara> + <itemizedlist> + <listitem> + <simpara>UI Tests</simpara> + </listitem> + <listitem> + <simpara>Service Tests</simpara> + </listitem> + <listitem> + <simpara>Unit Tests</simpara> + </listitem> + <listitem> + <simpara>異なる粒度のテストを書く</simpara> + </listitem> + <listitem> + <simpara>高レベルになるほど、持つべきテストは少なくなる</simpara> + <itemizedlist> + <listitem> + <simpara>ピラミッド型になる</simpara> + </listitem> + </itemizedlist> + </listitem> + </itemizedlist> + <simpara>高レベルテストが多すぎる→アイスクリームコーン アンチパターン</simpara> + <simpara>ATDD (Acceptance TDD) API経由・UI経由での高レベルテスト E2E test</simpara> + <simpara>ストーリ受け入れテスト</simpara> + <simpara>入れ子のフィードバックループ ATDD(外側) と TDD(内側)</simpara> + <simpara>外部品質・内部品質</simpara> + <simpara>バーティカルスライスのデリバリー</simpara> + <itemizedlist> + <listitem> + <simpara>cucumber</simpara> + </listitem> + <listitem> + <simpara>gauge</simpara> + </listitem> + <listitem> + <simpara>behat</simpara> + </listitem> + </itemizedlist> + <simpara>ユビキタス言語 手動テストもspecに書く 自動化は可能だがコスパが悪い + 失敗することがわかっているテスト(レッドテスト)はCIから外す + 失敗時の原因究明が難しい 饒舌なエラーメッセージ 状況のスナップショット</simpara> +<simpara>Continuous Testing</simpara> +</blockquote> +<simpara>User Acceptance Test (UAT) +くらいの規模になると個人開発・趣味開発では触れない領域なので、大いに勉強になった。スライドに添付されている資料が相当に充実していたので、これを読むのが本番といった様相すら感じる。 +高レベルテストの自動化は現在のプロジェクトでも感じており、自動化のチャンスは伺っている。とはいえセッションでも指摘されているように自動化することにコストがかかりすぎる領域があるのも事実で、そのバランスが難しい。</simpara> +</section> +<section xml:id="_1150_a"> + <title>11:50 [A]</title> + <simpara>型解析を用いたリファクタリング</simpara> + <simpara>型のある世界で生きてきた身として大いに楽しみにしていた発表。</simpara> + <blockquote> + <itemizedlist> + <listitem> + <simpara>PHPStan</simpara> + </listitem> + <listitem> + <simpara>Phan</simpara> + </listitem> + <listitem> + <simpara>Psalm</simpara> + </listitem> + </itemizedlist> + <simpara>autoload も認識できる bootstrapFiles</simpara> + <simpara>編集箇所と利用箇所を CI でチェック ルールレベルを徐々に引き上げていく + 警告が多すぎると見落としてしまう・無視されやすくなる</simpara> + <simpara>型がついていないことによるエラーが多い</simpara> + <simpara>型よりも詳細な検査 <literal>Util_Assert::min</literal></simpara> + <simpara>SQL を静的解析 placeholder の型付け</simpara> + <simpara>警告レベルを低いレベルから導入 タイプヒントを積極的に書いていく PHPStan + の拡張を追加する</simpara> +</blockquote> +<simpara>昨今、動的型付き言語での型宣言・型アノテーション・型ヒントの導入が相次いでいる。長らく静的型付き言語を書いてきた私からすると、ようやく気づいたかといったところだが、ともかく型を導入する言語が増えてきた。 +今のプロジェクトでも新しく追加するコードには型をつけるよう努めているが、どうしても古いコードには型がついていない。個人的には型のないコードに対してどう型を自動的に付けるかという点に興味があり、その点で +Ruby の typeprof には注目している。</simpara> +</section> +<section xml:id="_1230_a"> + <title>12:30 [A]</title> + <simpara>昼食をとっていた。事前に何か食料を買っておくべきだった。</simpara> +</section> +<section xml:id="_1310_a"> + <title>13:10 [A]</title> + <simpara>Documentation as Code</simpara> + <simpara>この発表も以前から非常に楽しみにしていた。</simpara> + <blockquote> + <simpara>開発開始までのオーバーヘッド 新規にチームにジョイン + 担当範囲外の機能を理解 オンボーディングのコスト</simpara> + <simpara>PHPerKaigi 2020 で発表あり</simpara> + <simpara>継続的にシステムの理解を助けるドキュメント</simpara> + <simpara>継続的ドキュメンテーション システムとドキュメントの乖離</simpara> + <simpara>書いてあることが間違っている・足りない * 徐々にずれていく * + システムの更新タイミングとドキュメントの更新タイミングに差がある</simpara> +<simpara>システムとドキュメントは対応関係がある * 間違ったドキュメント * +存在しないドキュメント</simpara> +<simpara>システムとドキュメントの乖離を定量化する 継続的に +システムの更新に近いタイミングで ドキュメントを更新し続ける</simpara> +<simpara>Documentation as Code</simpara> +<simpara>コードと同じツールでドキュメントを書く * issue tracker * vcs * plain +text markup * automation</simpara> +<simpara>開発者 システム ドキュメント 構造化データ ソフトウェア</simpara> +<simpara>システムから構造化データを抽出する PHPDoc OpenAPI</simpara> +<simpara>ビュー 関心ごとに合わせてアーキテクチャを一つ以上の側面(断面)で説明する</simpara> +<simpara>ビューの単位でドキュメントに</simpara> +<simpara>スタックトレースからのドキュメント生成</simpara> +</blockquote> +<simpara>ドキュメントの管理は現プロジェクトでも課題と感じている。作られた当初は正しくても、実態と乖離していくのを止めるのは困難を極める。全体的に興味深い発表だったが、特にスタックトレースからのドキュメント生成というアイデアに惹かれるものを感じた。スタックトレースという実態と不可分な +(乖離しない) +情報を起点にするのは理にかなっている。問題はトレースをいつ、どう取るかだろうか。それを自動化しなければ、実態との乖離が避けられないだろう。</simpara> +</section> +<section xml:id="_1410_a"> + <title>14:10 [A]</title> + <simpara>cookie による session 管理</simpara> + <simpara>全体的に基本的な話だったので特に触れない。Cookie + やセッションの話としては非常に分かりやすくまとめられていたので、知らない人が学ぶにはいい教材だろう。</simpara> +</section> +<section xml:id="_1450_a"> + <title>14:50 [A]</title> + <simpara>PHP のエラーと例外</simpara> + <blockquote> + <simpara>エラー PHPエンジンがエラーを通知する 例外 プログラムが投げる</simpara> + <simpara>PHP7-8とエラー</simpara> + <simpara>PHPエンジンのエラーの一部が に変換されるようになった → try-catch + で捕捉できる</simpara> + <simpara>は例外とは異なる</simpara> + <simpara>PHP8 でエラーレベルの引き上げ</simpara> + <itemizedlist> + <listitem> + <simpara>捕捉すべきもの</simpara> + <itemizedlist> + <listitem> + <simpara>recoverable</simpara> + </listitem> + </itemizedlist> + </listitem> + <listitem> + <simpara>捕捉すべきでないもの</simpara> + <itemizedlist> + <listitem> + <simpara>unrecoverable</simpara> + </listitem> + <listitem> + <simpara>開発時に対処できるもの</simpara> + </listitem> + </itemizedlist> + </listitem> + </itemizedlist> + <simpara>例外 * 捕捉して事後処理 * 捕捉せず(or 捕捉した上で)さらに上に是非を問う</simpara> + <simpara>開発段階で例外を把握し、ハンドリングを考えておく</simpara> + <simpara>と</simpara> + <simpara>はキャッチすべきでない</simpara> + <itemizedlist> + <listitem> + <simpara></simpara> + <itemizedlist> + <listitem> + <simpara>本番で起きてはいけない</simpara> + </listitem> + </itemizedlist> + </listitem> + <listitem> + <simpara></simpara> + <itemizedlist> + <listitem> + <simpara>本番で起きてはいけない →生じないのだから捕捉もしない</simpara> + </listitem> + </itemizedlist> + </listitem> + <listitem> + <simpara></simpara> + <itemizedlist> + <listitem> + <simpara>起こるかもしれないので本番環境でも考慮する</simpara> + </listitem> + </itemizedlist> + </listitem> + </itemizedlist> + <simpara>捕捉して対応するのではなく、未然に防ぐ</simpara> + <simpara>独自例外を使う を投げてしまうと、 catch ()せざるを得ない →catch + 範囲が広すぎる</simpara> +<simpara>SPL の例外を使う</simpara> +<simpara>例外翻訳 +上位のレイヤが下位のレイヤの例外を捕捉し、上位レイヤのAPIに「翻訳」する +下位レイヤの知識に依存させない</simpara> +<simpara>@throws 捕捉してほしい例外を書き連ねておく</simpara> +<simpara>呼び出しもとに負わせたい責任</simpara> +</blockquote> +<simpara>PHP を学んでいる途中の私としては、今まさに聞きたい発表だった (現時点で +PHP を書き始めてから 4ヶ月ほどになる)。</simpara> +<simpara>個人的に例外やエラーを最もうまく扱っているのは Go、Swift、Rust、Haskell +などのエラーを「値として」扱う言語だと思っている。try-catch +は通常の処理フローを完全に壊してしまう上、構文としても重すぎる。値としてのエラー通知は +C言語時代への回帰ともいえるが、その頃と異なるのはエラーを暗黙のうちに握り潰すことがないということだ。これらの言語は型を持っており、静的に検証ができる +(C のそれはまともな型付けではない。念のため)。</simpara> +<simpara>PHP +のように、すでに例外が言語システムに根ざしている言語ではどうすればよいか。この場合も同じく静的検証の力を借りることになるだろう。</simpara> +</section> +<section xml:id="_1530_a"> + <title>15:30 [A]</title> + <simpara>Laravel のメール認証</simpara> + <simpara>Laravel + の知識がない私にはまったくついていけなかった。また、個人的にタイトルがややミスリーディングに感じた。</simpara> +</section> +<section xml:id="_1610_a"> + <title>16:10 [A]</title> + <simpara>gRPC</simpara> + <blockquote> + <simpara>Unary RPCs Server streaming RPCs Client streaming RPCs Bidirectional + streaming RPCs</simpara> + <simpara>Protobuf</simpara> + <simpara>実装とAPIが乖離しにくい 自動生成 複数言語でも相互に使える</simpara> + <simpara>マイクロサービスのサービス通信 スマホアプリ ゲームサーバ</simpara> + <simpara>PHP では?</simpara> + <simpara>PHP ではストリーミングが難しい リクエストごとにプロセスが使い捨て</simpara> + <simpara>PHP ではgRPCのクライアントしか対応していない</simpara> + <simpara>gRPC-Web ブラウザで扱うためのJSライブラリ+プロトコル</simpara> + <simpara>HTTP/1.1 でも使える Unary RPC と Server streaming RPC のみ</simpara> + <simpara>Envoy Nginx などで相互に gRPC と gRPC-Web で変換</simpara> + <simpara>Amp イベント駆動な並行処理のフレームワーク</simpara> + <simpara>HTTP/2 対応</simpara> + <simpara>C#のgRPC-Webが楽</simpara> +</blockquote> +<simpara>(発表の中でもまさに同じことをおっしゃっていたが) PHP +以外の方が向いているだろう、というのが第一の感想である。gRPC +はそれ自体というよりも Protobuf +というエコシステムに乗れることのメリットが大きいと感じる。そのエコシステムにうまく乗れない時点で、うーんという感じ。</simpara> +</section> +<section xml:id="_1650_a"> + <title>16:50 [A]</title> + <simpara>アーキテクチャテスト</simpara> + <blockquote> + <simpara>Independent Core Layer Pattern</simpara> + <simpara>開発初期のアーキテクチャが崩れる + アーキテクチャ観点のコードレビューができない</simpara> + <simpara>どこにクラスを置けばよいか? ドキュメントがない</simpara> + <simpara>アーキテクチャ設計に関する知識が属人化・暗黙知化</simpara> + <simpara>ガイドライン * 最初にルールを決めるのは簡単 * + ルール通り作り始めるのも簡単 * + →維持するのが難しい、人が決めたものゆえ壊れやすい</simpara> +<simpara>PHP の特性 * クラスは public * 可視性の制御が public / protected / +private のみ * 依存関係の制御が困難</simpara> +<simpara>アーキテクチャテスト +クラスの依存関係や実装ルールをコードとして表現し、自動テスト化する</simpara> +<itemizedlist> + <listitem> + <simpara>deptrac</simpara> + </listitem> + <listitem> + <simpara>phpat</simpara> + </listitem> +</itemizedlist> +<simpara>Independent Core Layer Pattern</simpara> +<simpara>アーキテクチャテストの失敗 * 実装誤り * or アーキテクチャが適切でない * +開発の過程でフィードバックしていく</simpara> +<simpara>モジュラーモノリス→マイクロサービスへ</simpara> +</blockquote> +</section> +</section> +<section xml:id="_day_2_20210328"> + <title>Day 2 (2021/03/28)</title> + <simpara>冒頭に書いた通り、2日目から体調が悪くまともに聴けていない。途中までは頭痛を我慢しつつ見ていたのだが、まともに入ってこなかった。</simpara> + <simpara>残念ではあるが、いずれにせよ見られていない発表は他にもあるので、今週末にでもまとめて見ようと思う。</simpara> +</section> +<section xml:id="_全体の感想"> + <title>全体の感想</title> + <simpara>Day 2 + にほとんど参加できなかったのは残念だが、イベント自体は大変楽しく、また興味深いものであった。自分がまったく知らない領域の話を聞けるのはこうしたイベントならではだと感じる。オンライン開催ゆえ現地に行く必要がなく、気軽に参加できたのも + (特に初参加者として) 嬉しいポイントだった。</simpara> +<simpara>今回、雑談/登壇者への質問等向けに Discord +サーバもあったのだが、こちらは参加こそしたものの ROM +のままになってしまった。発表に1ウィンドウ、メモを書くのに1ウィンドウ、Discord +表示に +1ウィンドウで私にはもう脳のリソースとディスプレイのスペースが追いつかなかった +(さらにいうと Zoom +でアンカンファレンスもやっていたようだ。こちらはまったく参加していない)。</simpara> +<simpara>1つ個人的な反省点としては、一つ一つのセッションを真剣に聞き過ぎたというものがある。もっと適当に聞いておけばよかった。これだけだと大変語弊があるのだが、言い方を変えると、Discord +しかりアンカンファレンスしかり「このイベントのこの瞬間にしかないコンテンツ」に触れずに、後から見返せる発表やスライドに注力してしまった、ということだ。発表の詳細な見直しはあとからできるのだから、今しかできないことを考えるべきだった。 +まあ初カンファレンスだし、とお茶を濁しておこう。</simpara> +<simpara>さて、カンファレンスで一つ気になったことがある。それは、Discord +という書き込み場所が増えたことでニコ生のコメントの流量が吸い取られてしまったのではないか、という点だ。ニコニコだけ見ていると過疎っているかのように見えた発表も、Discord +の方では盛り上がっている、というのを何度か見かけた。ニコニコのコメント方式は盛り上がりを如実に反映するが、逆もまたしかり。Discord +があったこと自体はプラスだったと思うが、この点はマイナスだったのではないかと感じる。</simpara> +<simpara><hr/></simpara> +<simpara>最後になりましたが、毎年の PHPerKaigi +開催にご尽力されている皆様、スピーカーの皆様、楽しい3日間でした。ありがとうございました! +(ずっと常体で書いてしまったのでいきなり仏頂面から笑顔になったようで気持ち悪い)</simpara> +<simpara>ではまた来年。</simpara> +</section> +</section> +</article> diff --git a/content/posts/2021-10-02/cpp-you-can-use-keywords-in-attributes.adoc b/content/posts/2021-10-02/cpp-you-can-use-keywords-in-attributes.adoc deleted file mode 100644 index afa0f3f..0000000 --- a/content/posts/2021-10-02/cpp-you-can-use-keywords-in-attributes.adoc +++ /dev/null @@ -1,106 +0,0 @@ -= [C++] 属性構文の属性名にはキーワードが使える -:tags: cpp, cpp17 -:description: C++ の属性構文の属性名には、キーワードが使える。ネタ記事。 -:revision-1: 2021-10-02 Qiita から移植 - -この記事は Qiita から移植してきたものです。 元 URL: -https://qiita.com/nsfisis/items/94090937bcf860cfa93b - -''''' - -タイトル落ち。まずはこのコードを見て欲しい。 - -[source,cpp] ----- -#include <iostream> - -[[alignas]] [[alignof]] [[and]] [[and_eq]] [[asm]] [[auto]] [[bitand]] -[[bitor]] [[bool]] [[break]] [[case]] [[catch]] [[char]] [[char16_t]] -[[char32_t]] [[class]] [[compl]] [[const]] [[const_cast]] [[constexpr]] -[[continue]] [[decltype]] [[default]] [[delete]] [[do]] [[double]] -[[dynamic_cast]] [[else]] [[enum]] [[explicit]] [[export]] [[extern]] [[false]] -[[final]] [[float]] [[for]] [[friend]] [[goto]] [[if]] [[inline]] [[int]] -[[long]] [[mutable]] [[namespace]] [[new]] [[noexcept]] [[not]] [[not_eq]] -[[nullptr]] [[operator]] [[or]] [[or_eq]] [[override]] [[private]] -[[protected]] [[public]] [[register]] [[reinterpret_cast]] [[return]] [[short]] -[[signed]] [[sizeof]] [[static]] [[static_assert]] [[static_cast]] [[struct]] -[[switch]] [[template]] [[this]] [[thread_local]] [[throw]] [[true]] [[try]] -[[typedef]] [[typeid]] [[typename]] [[union]] [[unsigned]] -[[virtual]] [[void]] [[volatile]] [[wchar_t]] [[while]] [[xor]] [[xor_eq]] -// [[using]] -int main() { - std::cout << "Hello, World!" << std::endl; -} ----- - -____ -コンパイラのバージョン $ clang++ –version Apple clang version 11.0.0 -(clang-1100.0.33.8) Target: x86_64-apple-darwin19.6.0 Thread model: -posix InstalledDir: -/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin - -コンパイルコマンド (C++17指定) $ clang++ –std=c++17 hoge.cpp -____ - -この記事から得られるものはこれ以上ないので以下は蛇足になる。 - -別件で cppreference.com の -https://en.cppreference.com/w/cpp/language/identifiers[identifier -のページ] を読んでいた時、次の文が目に止まった。 - -____ -* the identifiers that are keywords cannot be used for other purposes; -** The only place they can be used as non-keywords is in an -attribute-token. (e.g. [[private]] is a valid attribute) (since C++11) -____ - -キーワードでも属性として指定する場合は非キーワードとして使えるらしい。 -実際にやってみる。 - -同サイトの https://en.cppreference.com/w/cpp/keyword[keywords のページ] -から一覧を拝借し、上のコードが出来上がった (C++17 -においてキーワードでないものなど、一部省いている)。 大量の警告 (unknown -attribute `〇〇' ignored) -がコンパイラから出力されるが、コンパイルできる。 - -上のコードでは `[[using]]` をコメントアウトしているが、これは `using` -キーワードのみ属性構文の中で意味を持つからであり、このコメントアウトを外すとコンパイルに失敗する。 - -[source,cpp] ----- -// using の例 -[[using foo: attr1, attr2]] int x; // [[foo::attr1, foo::attr2]] の糖衣構文 ----- - -C++17 の仕様も見てみる (正確には標準化前のドラフト)。 - -引用元: https://timsong-cpp.github.io/cppwp/n4659/dcl.attr#grammar-4 - -____ -If a keyword or an alternative token that satisfies the syntactic -requirements of an identifier is contained in an attribute-token, it is -considered an identifier. -____ - -「`identifier` の構文上の要件を満たすキーワードまたは代替トークンが -`attribute-token` に含まれている場合、`identifier` -とみなされる」とある。どうやら間違いないようだ。 - -ところで、代替トークン (alternative token) とは `and` (`&`) や `bitor` -(`|`) などのことだが、`identifier` -の構文上の要件を満たさないような代替トークンなどあるのか? -疑問に思って調べたところ、代替トークンという語にはダイグラフも含まれるらしい -(参考: -https://timsong-cpp.github.io/cppwp/n4659/lex.digraph[同ドラフト]) - -* `<%` → `{` -* `%>` → `}` -* `<:` → `[` -* `:>` → `]` -* `%:` → `#` -* `%:%:` → `##` - -「`identifier` -の構文上の要件を満たさないような代替トークン」はこれらが当てはまると思われる。 - -調べた感想: 字句解析器か構文解析器が辛そう diff --git a/content/posts/2021-10-02/cpp-you-can-use-keywords-in-attributes.xml b/content/posts/2021-10-02/cpp-you-can-use-keywords-in-attributes.xml new file mode 100644 index 0000000..f212b7c --- /dev/null +++ b/content/posts/2021-10-02/cpp-you-can-use-keywords-in-attributes.xml @@ -0,0 +1,116 @@ +<?xml version="1.0" encoding="UTF-8"?> +<article xmlns="http://docbook.org/ns/docbook" xmlns:xl="http://www.w3.org/1999/xlink" version="5.0"> + <info> + <title>【C++】 属性構文の属性名にはキーワードが使える</title> + <abstract> + C++ の属性構文の属性名には、キーワードが使える。ネタ記事。 + </abstract> + <keywordset> + <keyword>cpp</keyword> + <keyword>cpp17</keyword> + </keywordset> + <revhistory> + <revision> + <date>2021-10-02</date> + <revremark>Qiita から移植</revremark> + </revision> + </revhistory> + </info> + <simpara>この記事は Qiita から移植してきたものです。 元 URL: + <link xl:href="https://qiita.com/nsfisis/items/94090937bcf860cfa93b">https://qiita.com/nsfisis/items/94090937bcf860cfa93b</link></simpara> +<simpara><hr/></simpara> +<simpara>タイトル落ち。まずはこのコードを見て欲しい。</simpara> +<programlisting language="cpp" linenumbering="unnumbered">#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; +}</programlisting> +<blockquote> + <simpara>コンパイラのバージョン $ clang++ –version Apple clang version 11.0.0 + (clang-1100.0.33.8) Target: x86_64-apple-darwin19.6.0 Thread model: + posix InstalledDir: + /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin</simpara> +<simpara>コンパイルコマンド (C17指定) $ clang –std=c++17 hoge.cpp</simpara> +</blockquote> +<simpara>この記事から得られるものはこれ以上ないので以下は蛇足になる。</simpara> +<simpara>別件で cppreference.com の +<link xl:href="https://en.cppreference.com/w/cpp/language/identifiers">identifier +のページ</link> を読んでいた時、次の文が目に止まった。</simpara> +<blockquote> + <itemizedlist> + <listitem> + <simpara>the identifiers that are keywords cannot be used for other purposes;</simpara> + <itemizedlist> + <listitem> + <simpara>The only place they can be used as non-keywords is in an + attribute-token. (e.g. [[private]] is a valid attribute) (since C++11)</simpara> + </listitem> + </itemizedlist> + </listitem> +</itemizedlist> +</blockquote> +<simpara>キーワードでも属性として指定する場合は非キーワードとして使えるらしい。 +実際にやってみる。</simpara> +<simpara>同サイトの <link xl:href="https://en.cppreference.com/w/cpp/keyword">keywords のページ</link> + から一覧を拝借し、上のコードが出来上がった (C++17 + においてキーワードでないものなど、一部省いている)。 大量の警告 (unknown + attribute `〇〇' ignored) + がコンパイラから出力されるが、コンパイルできる。</simpara> +<simpara>上のコードでは <literal>[[using]]</literal> をコメントアウトしているが、これは <literal>using</literal> + キーワードのみ属性構文の中で意味を持つからであり、このコメントアウトを外すとコンパイルに失敗する。</simpara> +<programlisting language="cpp" linenumbering="unnumbered">// using の例 +[[using foo: attr1, attr2]] int x; // [[foo::attr1, foo::attr2]] の糖衣構文</programlisting> +<simpara>C++17 の仕様も見てみる (正確には標準化前のドラフト)。</simpara> +<simpara>引用元: <link xl:href="https://timsong-cpp.github.io/cppwp/n4659/dcl.attr#grammar-4">https://timsong-cpp.github.io/cppwp/n4659/dcl.attr#grammar-4</link></simpara> +<blockquote> + <simpara>If a keyword or an alternative token that satisfies the syntactic + requirements of an identifier is contained in an attribute-token, it is + considered an identifier.</simpara> +</blockquote> +<simpara>「<literal>identifier</literal> の構文上の要件を満たすキーワードまたは代替トークンが +<literal>attribute-token</literal> に含まれている場合、<literal>identifier</literal> +とみなされる」とある。どうやら間違いないようだ。</simpara> +<simpara>ところで、代替トークン (alternative token) とは <literal>and</literal> (<literal>&</literal>) や <literal>bitor</literal> + (<literal>|</literal>) などのことだが、<literal>identifier</literal> + の構文上の要件を満たさないような代替トークンなどあるのか? + 疑問に思って調べたところ、代替トークンという語にはダイグラフも含まれるらしい + (参考: + <link xl:href="https://timsong-cpp.github.io/cppwp/n4659/lex.digraph">同ドラフト</link>)</simpara> +<itemizedlist> + <listitem> + <simpara><literal><%</literal> → <literal>{</literal></simpara> + </listitem> + <listitem> + <simpara><literal>%></literal> → <literal>}</literal></simpara> + </listitem> + <listitem> + <simpara><literal><:</literal> → <literal>[</literal></simpara> + </listitem> + <listitem> + <simpara><literal>:></literal> → <literal>]</literal></simpara> + </listitem> + <listitem> + <simpara><literal>%:</literal> → <literal>#</literal></simpara> + </listitem> + <listitem> + <simpara><literal>%:%:</literal> → <literal>##</literal></simpara> + </listitem> +</itemizedlist> +<simpara>「<literal>identifier</literal> + の構文上の要件を満たさないような代替トークン」はこれらが当てはまると思われる。</simpara> +<simpara>調べた感想: 字句解析器か構文解析器が辛そう</simpara> +</article> diff --git a/content/posts/2021-10-02/python-unbound-local-error.adoc b/content/posts/2021-10-02/python-unbound-local-error.adoc deleted file mode 100644 index f68ae4d..0000000 --- a/content/posts/2021-10-02/python-unbound-local-error.adoc +++ /dev/null @@ -1,66 +0,0 @@ -= [Python] クロージャとUnboundLocalError: local variable 'x' referenced before assignment -:tags: python, python3 -:description: Python における UnboundLocalError の理由と対処法。 -:revision-1: 2021-10-02 Qiita から移植 - -この記事は Qiita から移植してきたものです。 元 URL: -https://qiita.com/nsfisis/items/5d733703afcb35bbf399 - -''''' - -本記事は Python 3.7.6 の動作結果を元にして書かれている。 - -Python でクロージャを作ろうと、次のようなコードを書いた。 - -[source,python] ----- -def f(): - x = 0 - def g(): - x += 1 - g() - -f() ----- - -関数 `g` から 関数 `f` のスコープ内で定義された変数 `x` を参照し、それに -1 を足そうとしている。 これを実行すると `x += 1` -の箇所でエラーが発生する。 - -____ -UnboundLocalError: local variable `x' referenced before assignment -____ - -local変数 `x` が代入前に参照された、とある。これは、`f` の `x` -を参照するのではなく、新しく別の変数を `g` 内に作ってしまっているため。 -前述のコードを宣言と代入を便宜上分けて書き直すと次のようになる。`var` -を変数宣言のための構文として擬似的に利用している。 - -[source,python] ----- -# 注: 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() ----- - -当初の意図を表現するには、次のように書けばよい。 - -[source,python] ----- -def f(): - x = 0 - def g(): - nonlocal x ## (*) - x += 1 - g() ----- - -`(*)` のように、`nonlocal` を追加する。これにより一つ外側のスコープ (`g` -の一つ外側 = `f`) で定義されている `x` を探しに行くようになる。 diff --git a/content/posts/2021-10-02/python-unbound-local-error.xml b/content/posts/2021-10-02/python-unbound-local-error.xml new file mode 100644 index 0000000..3d33628 --- /dev/null +++ b/content/posts/2021-10-02/python-unbound-local-error.xml @@ -0,0 +1,60 @@ +<?xml version="1.0" encoding="UTF-8"?> +<article xmlns="http://docbook.org/ns/docbook" xmlns:xl="http://www.w3.org/1999/xlink" version="5.0"> + <info> + <title>【Python】 クロージャとUnboundLocalError: local variable 'x' referenced before assignment</title> + <abstract> + Python における UnboundLocalError の理由と対処法。 + </abstract> + <keywordset> + <keyword>python</keyword> + <keyword>python3</keyword> + </keywordset> + <revhistory> + <revision> + <date>2021-10-02</date> + <revremark>Qiita から移植</revremark> + </revision> + </revhistory> + </info> + <simpara>この記事は Qiita から移植してきたものです。 元 URL: + <link xl:href="https://qiita.com/nsfisis/items/5d733703afcb35bbf399">https://qiita.com/nsfisis/items/5d733703afcb35bbf399</link></simpara> +<simpara><hr/></simpara> +<simpara>本記事は Python 3.7.6 の動作結果を元にして書かれている。</simpara> +<simpara>Python でクロージャを作ろうと、次のようなコードを書いた。</simpara> +<programlisting language="python" linenumbering="unnumbered">def f(): +x = 0 +def g(): +x += 1 +g() + +f()</programlisting> +<simpara>関数 <literal>g</literal> から 関数 <literal>f</literal> のスコープ内で定義された変数 <literal>x</literal> を参照し、それに +1 を足そうとしている。 これを実行すると <literal>x += 1</literal> +の箇所でエラーが発生する。</simpara> +<blockquote> + <simpara>UnboundLocalError: local variable `x' referenced before assignment</simpara> +</blockquote> +<simpara>local変数 <literal>x</literal> が代入前に参照された、とある。これは、<literal>f</literal> の <literal>x</literal> + を参照するのではなく、新しく別の変数を <literal>g</literal> 内に作ってしまっているため。 + 前述のコードを宣言と代入を便宜上分けて書き直すと次のようになる。<literal>var</literal> + を変数宣言のための構文として擬似的に利用している。</simpara> +<programlisting language="python" linenumbering="unnumbered"># 注: 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()</programlisting> +<simpara>当初の意図を表現するには、次のように書けばよい。</simpara> +<programlisting language="python" linenumbering="unnumbered">def f(): +x = 0 +def g(): +nonlocal x ## (*) +x += 1 +g()</programlisting> +<simpara><literal>(*)</literal> のように、<literal>nonlocal</literal> を追加する。これにより一つ外側のスコープ (<literal>g</literal> + の一つ外側 = <literal>f</literal>) で定義されている <literal>x</literal> を探しに行くようになる。</simpara> +</article> diff --git a/content/posts/2021-10-02/ruby-detect-running-implementation.adoc b/content/posts/2021-10-02/ruby-detect-running-implementation.adoc deleted file mode 100644 index 1434891..0000000 --- a/content/posts/2021-10-02/ruby-detect-running-implementation.adoc +++ /dev/null @@ -1,72 +0,0 @@ -= [Ruby] 自身を実行している処理系の種類を判定する -:tags: ruby -:description: Ruby には複数の実装があるが、自身を実行している処理系の種類を \ - スクリプト上からどのように判定すればよいだろうか。 -:revision-1: 2021-10-02 Qiita から移植 - -この記事は Qiita から移植してきたものです。 元 URL: -https://qiita.com/nsfisis/items/74d7ffeeebc51b20d791 - -''''' - -Ruby -という言語には複数の実装があるが、それらをスクリプト上からどのようにして -programmatically に見分ければよいだろうか。 - -`Object` クラスに定義されている `RUBY_ENGINE` -という定数がこの用途に使える。 - -参考: -https://docs.ruby-lang.org/ja/latest/method/Object/c/RUBY_ENGINE.html[Object::RUBY_ENGINE] - -上記ページの例から引用する: - -[source,shell-session] ----- -$ 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 -に良い質問と回答があった。 - -https://stackoverflow.com/a/9894232[What values for RUBY_ENGINE -correspond to which Ruby implementations?] より引用: - -____ -[cols="^,<",options="header",] -|=== -|RUBY_ENGINE |Implementation -|<undefined> |MRI < 1.9 -|`ruby' |MRI >= 1.9 or REE -|`jruby' |JRuby -|`macruby' |MacRuby -|`rbx' |Rubinius -|`maglev' |MagLev -|`ironruby' |IronRuby -|`cardinal' |Cardinal -|=== -____ - -なお、この質問・回答は -2014年になされたものであり、値は変わっている可能性がある。MRI (aka -CRuby) については執筆時現在 (2020/12/8) も `'ruby'` -が返ってくることを確認済み。 - -この表にない主要な処理系として、https://mruby.org[mruby] は `'mruby'` -を返す。 - -https://github.com/mruby/mruby/blob/ed29d74bfd95362eaeb946fcf7e865d80346b62b/include/mruby/version.h#L32-L35[mruby -該当部分のソース] より引用: - -[source,c] ----- -/* - * Ruby engine. - */ -#define MRUBY_RUBY_ENGINE "mruby" ----- diff --git a/content/posts/2021-10-02/ruby-detect-running-implementation.xml b/content/posts/2021-10-02/ruby-detect-running-implementation.xml new file mode 100644 index 0000000..ccc797b --- /dev/null +++ b/content/posts/2021-10-02/ruby-detect-running-implementation.xml @@ -0,0 +1,95 @@ +<?xml version="1.0" encoding="UTF-8"?> +<article xmlns="http://docbook.org/ns/docbook" xmlns:xl="http://www.w3.org/1999/xlink" version="5.0"> + <info> + <title>【Ruby】 自身を実行している処理系の種類を判定する</title> + <abstract> + Ruby には複数の実装があるが、自身を実行している処理系の種類をスクリプト上からどのように判定すればよいだろうか。 + </abstract> + <keywordset> + <keyword>ruby</keyword> + </keywordset> + <revhistory> + <revision> + <date>2021-10-02</date> + <revremark>Qiita から移植</revremark> + </revision> + </revhistory> + </info> + <simpara>この記事は Qiita から移植してきたものです。 元 URL: + <link xl:href="https://qiita.com/nsfisis/items/74d7ffeeebc51b20d791">https://qiita.com/nsfisis/items/74d7ffeeebc51b20d791</link></simpara> +<simpara><hr/></simpara> +<simpara>Ruby +という言語には複数の実装があるが、それらをスクリプト上からどのようにして +programmatically に見分ければよいだろうか。</simpara> +<simpara><literal>Object</literal> クラスに定義されている <literal>RUBY_ENGINE</literal> + という定数がこの用途に使える。</simpara> +<simpara>参考: +<link xl:href="https://docs.ruby-lang.org/ja/latest/method/Object/c/RUBY_ENGINE.html">Object::RUBY_ENGINE</link></simpara> +<simpara>上記ページの例から引用する:</simpara> +<programlisting language="shell-session" linenumbering="unnumbered">$ 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"</programlisting> +<simpara>それぞれの処理系がどのような値を返すかだが、stack overflow +に良い質問と回答があった。</simpara> +<simpara><link xl:href="https://stackoverflow.com/a/9894232">What values for RUBY_ENGINE +correspond to which Ruby implementations?</link> より引用:</simpara> +<blockquote> + <table> + <thead> + <tr> + <td>RUBY_ENGINE</td> + <td>Implementation</td> + </tr> + </thead> + <tbody> + <tr> + <td><undefined></td> + <td>MRI < 1.9</td> + </tr> + <tr> + <td>`ruby'</td> + <td>MRI >= 1.9 or REE</td> + </tr> + <tr> + <td>`jruby'</td> + <td>JRuby</td> + </tr> + <tr> + <td>`macruby'</td> + <td>MacRuby</td> + </tr> + <tr> + <td>`rbx'</td> + <td>Rubinius</td> + </tr> + <tr> + <td>`maglev'</td> + <td>MagLev</td> + </tr> + <tr> + <td>`ironruby'</td> + <td>IronRuby</td> + </tr> + <tr> + <td>`cardinal'</td> + <td>Cardinal</td> + </tr> + </tbody> + </table> +</blockquote> +<simpara>なお、この質問・回答は +2014年になされたものであり、値は変わっている可能性がある。MRI (aka +CRuby) については執筆時現在 (2020/12/8) も <literal>'ruby'</literal> +が返ってくることを確認済み。</simpara> +<simpara>この表にない主要な処理系として、https://mruby.org[mruby] は <literal>'mruby'</literal> + を返す。</simpara> +<simpara><link xl:href="https://github.com/mruby/mruby/blob/ed29d74bfd95362eaeb946fcf7e865d80346b62b/include/mruby/version.h#L32-L35">mruby +該当部分のソース</link> より引用:</simpara> +<programlisting language="c" linenumbering="unnumbered">/* +* Ruby engine. +*/ +#define MRUBY_RUBY_ENGINE "mruby"</programlisting> +</article> diff --git a/content/posts/2021-10-02/ruby-then-keyword-and-case-in.adoc b/content/posts/2021-10-02/ruby-then-keyword-and-case-in.adoc deleted file mode 100644 index 94fcf48..0000000 --- a/content/posts/2021-10-02/ruby-then-keyword-and-case-in.adoc +++ /dev/null @@ -1,226 +0,0 @@ -= [Ruby] then キーワードと case in -:tags: ruby, ruby3 -:description: Ruby 3.0 で追加される case in 構文と、then キーワードについて。 -:revision-1: 2021-10-02 Qiita から移植 - -この記事は Qiita から移植してきたものです。 元 URL: -https://qiita.com/nsfisis/items/787a8cf888a304497223 - -''''' - -== TL; DR - -`case` - `in` によるパターンマッチング構文でも、`case` - `when` -と同じように `then` が使える (場合によっては使う必要がある)。 - -== `then` とは - -使われることは稀だが、Ruby では `then` -がキーワードになっている。次のように使う: - -[source,ruby] ----- -if cond then - puts "Y" -else - puts "N" -end ----- - -このキーワードが現れうる場所はいくつかあり、`if`、`unless`、`rescue`、`case` -構文がそれに当たる。 上記のように、何か条件を書いた後 `then` -を置き、式がそこで終了していることを示すマーカーとして機能する。 - -[source,ruby] ----- -# Example: - -if x then - a -end - -unless x then - a -end - -begin - a -rescue then - b -end - -case x -when p then - a -end ----- - -== なぜ普段は書かなくてもよいのか - -普通 Ruby のコードで `then` -を書くことはない。なぜか。次のコードを実行してみるとわかる。 - -[source,ruby] ----- -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 -.... - -二つ目のメッセージは無視して一つ目を読むと、`then` か `;` -か改行が来るはずのところ変数だかメソッドだかが現れたことによりエラーとなっているようだ。 - -ポイントは改行が `then` (や `;`) の代わりとなることである。`true` -の後に改行を入れてみる。 - -[source,ruby] ----- -if true -puts 'Hello, World!' end ----- - -無事 Hello, World! と出力されるようになった。 - -== なぜ `then` や `;` や改行が必要か - -なぜ `then` や `;` や改行 (以下 「`then` 等」) -が必要なのだろうか。次の例を見てほしい: - -[source,ruby] ----- -if a b end ----- - -`then` も `;` -も改行もないのでエラーになるが、これは条件式がどこまで続いているのかわからないためだ。 -この例は二通りに解釈できる。 - -[source,ruby] ----- -# a という変数かメソッドの評価結果が truthy なら b という変数かメソッドを評価 -if a then - b -end ----- - -[source,ruby] ----- -# a というメソッドに b という変数かメソッドの評価結果を渡して呼び出し、 -# その結果が truthy なら何もしない -if a(b) then -end ----- - -`then` 等はこの曖昧性を排除するためにあり、条件式は `if` から `then` -等までの間にある、ということを明確にする。 C系の `if` 後に来る `(`/`)` -や、Python の `:`、Rust/Go/Swift などの `{` も同じ役割を持つ。 - -Ruby の場合、プログラマーが書きやすいよう改行でもって `then` -が代用できるので、ほとんどの場合 `then` は必要ない。 - -== `case` - `in` における `then` - -ようやく本題にたどり着いた。来る Ruby 3.0 では `case` と `in` -キーワードを使ったパターンマッチングの構文が入る予定である。この構文でもパターン部との区切りとして -`then` 等が必要になる。 (現在の) Ruby には formal -な形式での文法仕様は存在しないので、yacc の定義ファイルを参照した (yacc -の説明は省略)。 - -https://github.com/ruby/ruby/blob/221ca0f8281d39f0dfdfe13b2448875384bbf735/parse.y#L3961-L3986 - -[source,yacc] ----- -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)) %*/ - } - ; ----- - -簡略版: - -[source,yacc] ----- -p_case_body : keyword_in p_top_expr then compstmt p_cases - ; ----- - -ここで、`keyword_in` は文字通り `in`、`p_top_expr` -はいわゆるパターン、`then` は `then` -キーワードのことではなく、この記事で `then` 等と呼んでいるもの、つまり -`then` キーワード、`;`、改行のいずれかである。 - -これにより、`case` - `when` による従来の構文と同じように、`then` -等をパターンの後ろに挿入すればよいことがわかった。つまり次の3通りのいずれかになる: - -[source,ruby] ----- -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` と似たような見た目になる。 - -[source,ruby] ----- -case x -in 0 then a -in n if n < 0 then b -in n then c -end ----- - -== まとめ - -* `if` や `case` の条件の後ろには `then`、`;`、改行のいずれかが必要 -** 通常は改行しておけばよい -* 3.0 で入る予定の `case` - `in` でも `then` 等が必要になる -* Ruby の構文を正確に知るには (現状) `parse.y` を直接読めばよい diff --git a/content/posts/2021-10-02/ruby-then-keyword-and-case-in.xml b/content/posts/2021-10-02/ruby-then-keyword-and-case-in.xml new file mode 100644 index 0000000..c3fd933 --- /dev/null +++ b/content/posts/2021-10-02/ruby-then-keyword-and-case-in.xml @@ -0,0 +1,191 @@ +<?xml version="1.0" encoding="UTF-8"?> +<article xmlns="http://docbook.org/ns/docbook" xmlns:xl="http://www.w3.org/1999/xlink" version="5.0"> + <info> + <title>【Ruby】 then キーワードと case in</title> + <abstract> + Ruby 3.0 で追加される case in 構文と、then キーワードについて。 + </abstract> + <keywordset> + <keyword>ruby</keyword> + <keyword>ruby3</keyword> + </keywordset> + <revhistory> + <revision> + <date>2021-10-02</date> + <revremark>Qiita から移植</revremark> + </revision> + </revhistory> + </info> + <simpara>この記事は Qiita から移植してきたものです。 元 URL: + <link xl:href="https://qiita.com/nsfisis/items/787a8cf888a304497223">https://qiita.com/nsfisis/items/787a8cf888a304497223</link></simpara> +<simpara><hr/></simpara> +<section xml:id="_tl_dr"> + <title>TL; DR</title> + <simpara><literal>case</literal> - <literal>in</literal> によるパターンマッチング構文でも、<literal>case</literal> - <literal>when</literal> + と同じように <literal>then</literal> が使える (場合によっては使う必要がある)。</simpara> +</section> +<section xml:id="_then_とは"> + <title><literal>then</literal> とは</title> + <simpara>使われることは稀だが、Ruby では <literal>then</literal> + がキーワードになっている。次のように使う:</simpara> + <programlisting language="ruby" linenumbering="unnumbered">if cond then + puts "Y" + else + puts "N" + end</programlisting> +<simpara>このキーワードが現れうる場所はいくつかあり、<literal>if</literal>、<literal>unless</literal>、<literal>rescue</literal>、<literal>case</literal> + 構文がそれに当たる。 上記のように、何か条件を書いた後 <literal>then</literal> + を置き、式がそこで終了していることを示すマーカーとして機能する。</simpara> +<programlisting language="ruby" linenumbering="unnumbered"># Example: + +if x then +a +end + +unless x then +a +end + +begin +a +rescue then +b +end + +case x +when p then +a +end</programlisting> +</section> +<section xml:id="_なぜ普段は書かなくてもよいのか"> + <title>なぜ普段は書かなくてもよいのか</title> + <simpara>普通 Ruby のコードで <literal>then</literal> + を書くことはない。なぜか。次のコードを実行してみるとわかる。</simpara> + <programlisting language="ruby" linenumbering="unnumbered">if true puts 'Hello, World!' end</programlisting> + <simpara>次のような構文エラーが出力される。</simpara> + <literallayout class="monospaced">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</literallayout> +<simpara>二つ目のメッセージは無視して一つ目を読むと、<literal>then</literal> か <literal>;</literal> + か改行が来るはずのところ変数だかメソッドだかが現れたことによりエラーとなっているようだ。</simpara> +<simpara>ポイントは改行が <literal>then</literal> (や <literal>;</literal>) の代わりとなることである。<literal>true</literal> + の後に改行を入れてみる。</simpara> +<programlisting language="ruby" linenumbering="unnumbered">if true +puts 'Hello, World!' end</programlisting> +<simpara>無事 Hello, World! と出力されるようになった。</simpara> +</section> +<section xml:id="_なぜ_then_や_や改行が必要か"> + <title>なぜ <literal>then</literal> や <literal>;</literal> や改行が必要か</title> + <simpara>なぜ <literal>then</literal> や <literal>;</literal> や改行 (以下 「<literal>then</literal> 等」) + が必要なのだろうか。次の例を見てほしい:</simpara> +<programlisting language="ruby" linenumbering="unnumbered">if a b end</programlisting> +<simpara><literal>then</literal> も <literal>;</literal> + も改行もないのでエラーになるが、これは条件式がどこまで続いているのかわからないためだ。 + この例は二通りに解釈できる。</simpara> +<programlisting language="ruby" linenumbering="unnumbered"># a という変数かメソッドの評価結果が truthy なら b という変数かメソッドを評価 +if a then +b +end</programlisting> +<programlisting language="ruby" linenumbering="unnumbered"># a というメソッドに b という変数かメソッドの評価結果を渡して呼び出し、 +# その結果が truthy なら何もしない +if a(b) then +end</programlisting> +<simpara><literal>then</literal> 等はこの曖昧性を排除するためにあり、条件式は <literal>if</literal> から <literal>then</literal> + 等までの間にある、ということを明確にする。 C系の <literal>if</literal> 後に来る <literal>(</literal>/<literal>)</literal> + や、Python の <literal>:</literal>、Rust/Go/Swift などの <literal>{</literal> も同じ役割を持つ。</simpara> +<simpara>Ruby の場合、プログラマーが書きやすいよう改行でもって <literal>then</literal> + が代用できるので、ほとんどの場合 <literal>then</literal> は必要ない。</simpara> +</section> +<section xml:id="_case_in_における_then"> + <title><literal>case</literal> - <literal>in</literal> における <literal>then</literal></title> + <simpara>ようやく本題にたどり着いた。来る Ruby 3.0 では <literal>case</literal> と <literal>in</literal> + キーワードを使ったパターンマッチングの構文が入る予定である。この構文でもパターン部との区切りとして + <literal>then</literal> 等が必要になる。 (現在の) Ruby には formal + な形式での文法仕様は存在しないので、yacc の定義ファイルを参照した (yacc + の説明は省略)。</simpara> + <simpara><link xl:href="https://github.com/ruby/ruby/blob/221ca0f8281d39f0dfdfe13b2448875384bbf735/parse.y#L3961-L3986">https://github.com/ruby/ruby/blob/221ca0f8281d39f0dfdfe13b2448875384bbf735/parse.y#L3961-L3986</link></simpara> + <programlisting language="yacc" linenumbering="unnumbered">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)) %*/ + } + ;</programlisting> +<simpara>簡略版:</simpara> +<programlisting language="yacc" linenumbering="unnumbered">p_case_body : keyword_in p_top_expr then compstmt p_cases +;</programlisting> +<simpara>ここで、<literal>keyword_in</literal> は文字通り <literal>in</literal>、<literal>p_top_expr</literal> + はいわゆるパターン、<literal>then</literal> は <literal>then</literal> + キーワードのことではなく、この記事で <literal>then</literal> 等と呼んでいるもの、つまり + <literal>then</literal> キーワード、<literal>;</literal>、改行のいずれかである。</simpara> +<simpara>これにより、<literal>case</literal> - <literal>when</literal> による従来の構文と同じように、<literal>then</literal> + 等をパターンの後ろに挿入すればよいことがわかった。つまり次の3通りのいずれかになる:</simpara> +<programlisting language="ruby" linenumbering="unnumbered">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</programlisting> +<simpara>ところで、<literal>p_top_expr</literal> には <literal>if</literal> による guard clause +が書けるので、その場合は <literal>if</literal> - <literal>then</literal> と似たような見た目になる。</simpara> +<programlisting language="ruby" linenumbering="unnumbered">case x +in 0 then a +in n if n < 0 then b +in n then c +end</programlisting> +</section> +<section xml:id="_まとめ"> + <title>まとめ</title> + <itemizedlist> + <listitem> + <simpara><literal>if</literal> や <literal>case</literal> の条件の後ろには <literal>then</literal>、<literal>;</literal>、改行のいずれかが必要</simpara> + <itemizedlist> + <listitem> + <simpara>通常は改行しておけばよい</simpara> + </listitem> + </itemizedlist> + </listitem> + <listitem> + <simpara>3.0 で入る予定の <literal>case</literal> - <literal>in</literal> でも <literal>then</literal> 等が必要になる</simpara> + </listitem> + <listitem> + <simpara>Ruby の構文を正確に知るには (現状) <literal>parse.y</literal> を直接読めばよい</simpara> + </listitem> + </itemizedlist> +</section> +</article> diff --git a/content/posts/2021-10-02/rust-where-are-primitive-types-from.adoc b/content/posts/2021-10-02/rust-where-are-primitive-types-from.adoc deleted file mode 100644 index 8fc70b4..0000000 --- a/content/posts/2021-10-02/rust-where-are-primitive-types-from.adoc +++ /dev/null @@ -1,201 +0,0 @@ -= Rust のプリミティブ型はどこからやって来るか -:tags: rust -:description: Rust のプリミティブ型は予約語ではなく普通の識別子である。 \ - どのようにこれが名前解決されるのかを調べた。 -:revision-1: 2021-10-02 Qiita から移植 - -この記事は Qiita から移植してきたものです。 元 URL: -https://qiita.com/nsfisis/items/9a429432258bbcd6c565 - -''''' - -== 前置き - -Rust -において、プリミティブ型の名前は予約語でない。したがって、次のコードは合法である。 - -[source,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; ----- - -では、普段単に `bool` と書いたとき、この `bool` -は一体どこから来ているのか。rustc のソースを追ってみた。 - -____ -前提知識: 一般的なコンパイラの構造、用語。`rustc` そのものの知識は不要 -(というよりも筆者自身がよく知らない) -____ - -== 調査 - -調査に使用したソース (調査時点での最新 master) - -https://github.com/rust-lang/rust/tree/511ed9f2356af365ad8affe046b3dd33f7ac3c98 - -どのようにして調べるか。rustc -の構造には詳しくないため、すぐに当たりをつけるのは難しい。 - -大雑把な構造としては、`compiler` フォルダ以下に `rustc_*` -という名前のクレートが数十個入っている。これがどうやら `rustc` -コマンドの実装部のようだ。 - -`rustc` はセルフホストされている (= `rustc` 自身が Rust で書かれている) -ので、`bool` や `char` -などで適当に検索をかけてもノイズが多すぎて話にならない。 -しかし、お誂え向きなことに `i128`/`u128` -というコンパイラ自身が使うことがなさそうな型が存在するのでこれを使って -`git grep` してみる。 - -.... -$ git grep "\bi128\b" | wc # i128 - 165 1069 15790 - -$ git grep "\bu128\b" | wc # u128 - 293 2127 26667 - -$ git grep "\bbool\b" | wc # cf. bool の結果 - 3563 23577 294659 -.... - -165 -程度であれば探すことができそうだ。今回は、クレート名を見ておおよその当たりをつけた。 - -.... -$ git grep "\bi128\b" -... -rustc_resolve/src/lib.rs: table.insert(sym::i128, Int(IntTy::I128)); -... -.... - -`rustc_resolve` -というのはいかにも名前解決を担いそうなクレート名である。該当箇所を見てみる。 - -[source,rust] ----- -/// Interns the names of the primitive types. -/// -/// All other types are defined somewhere and possibly imported, but the primitive ones need -/// special handling, since they have no place of origin. -struct PrimitiveTypeTable { - primitive_types: FxHashMap<Symbol, PrimTy>, -} - -impl PrimitiveTypeTable { - fn new() -> PrimitiveTypeTable { - let mut table = FxHashMap::default(); - - table.insert(sym::bool, Bool); - table.insert(sym::char, Char); - table.insert(sym::f32, Float(FloatTy::F32)); - table.insert(sym::f64, Float(FloatTy::F64)); - table.insert(sym::isize, Int(IntTy::Isize)); - table.insert(sym::i8, Int(IntTy::I8)); - table.insert(sym::i16, Int(IntTy::I16)); - table.insert(sym::i32, Int(IntTy::I32)); - table.insert(sym::i64, Int(IntTy::I64)); - table.insert(sym::i128, Int(IntTy::I128)); - table.insert(sym::str, Str); - table.insert(sym::usize, Uint(UintTy::Usize)); - table.insert(sym::u8, Uint(UintTy::U8)); - table.insert(sym::u16, Uint(UintTy::U16)); - table.insert(sym::u32, Uint(UintTy::U32)); - table.insert(sym::u64, Uint(UintTy::U64)); - table.insert(sym::u128, Uint(UintTy::U128)); - Self { primitive_types: table } - } -} ----- - -これは初めに列挙したプリミティブ型の一覧と一致している。doc comment -にも、 - -____ -All other types are defined somewhere and possibly imported, but the -primitive ones need special handling, since they have no place of -origin. -____ - -とある。次はこの struct -の使用箇所を追う。追うと言っても使われている箇所は次の一箇所しかない。なお説明に不要な箇所は大きく削っている。 - -[source,rust] ----- - /// This resolves the identifier `ident` in the namespace `ns` in the current lexical scope. - /// (略) - fn resolve_ident_in_lexical_scope( - &mut self, - mut ident: Ident, - ns: Namespace, - // (略) - ) -> Option<LexicalScopeBinding<'a>> { - // (略) - - if ns == TypeNS { - if let Some(prim_ty) = self.primitive_type_table.primitive_types.get(&ident.name) { - let binding = - (Res::PrimTy(*prim_ty), ty::Visibility::Public, DUMMY_SP, ExpnId::root()) - .to_name_binding(self.arenas); - return Some(LexicalScopeBinding::Item(binding)); - } - } - - None - } ----- - -関数名や doc comment が示している通り、この関数は識別子 (identifier, -ident) を現在のレキシカルスコープ内で解決 (resolve) する。 -`if ns == TypeNS` のブロック内では、`primitive_type_table` (上記の -`PrimitiveTypeTable::new()` で作られた変数) に含まれている識別子 -(`bool`、`i32` など) -かどうか判定し、そうであればそれに紐づけられたプリミティブ型を返している。 - -なお、`ns` は「名前空間」を示す変数である。Rust -における名前空間はC言語におけるそれとほとんど同じで、今探している名前が関数名/変数名なのか型なのかマクロなのかを区別している。この -`if` -は、プリミティブ型に解決されるのは型を探しているときだけだ、と言っている。 - -重要なのは、これが `resolve_ident_in_lexical_scope()` -の最後に書かれている点である。つまり、最初に挙げたプリミティブ型の識別子は、「名前解決の最終段階で」、「他に同名の型が見つかっていなければ」プリミティブ型として解決される。 - -動作がわかったところで、例として次のコードを考える。 - -[source,rust] ----- -#![allow(non_camel_case_types)] - -struct bool; - -fn main() { - let _: bool = bool; -} ----- - -ここで `main()` の `bool` は `struct bool` -として解決される。なぜなら、プリミティブ型の判定をする前に `bool` -という名前の別の型が見つかるからだ。 - -== まとめ - -Rust -のプリミティブ型は予約語ではない。名前解決の最終段階で特別扱いされ、他に同名の型が見つかっていなければ対応するプリミティブ型に解決される。 diff --git a/content/posts/2021-10-02/rust-where-are-primitive-types-from.xml b/content/posts/2021-10-02/rust-where-are-primitive-types-from.xml new file mode 100644 index 0000000..35ec0c8 --- /dev/null +++ b/content/posts/2021-10-02/rust-where-are-primitive-types-from.xml @@ -0,0 +1,174 @@ +<?xml version="1.0" encoding="UTF-8"?> +<article xmlns="http://docbook.org/ns/docbook" xmlns:xl="http://www.w3.org/1999/xlink" version="5.0"> + <info> + <title>Rust のプリミティブ型はどこからやって来るか</title> + <abstract> + Rust のプリミティブ型は予約語ではなく普通の識別子である。どのようにこれが名前解決されるのかを調べた。 + </abstract> + <keywordset> + <keyword>rust</keyword> + </keywordset> + <revhistory> + <revision> + <date>2021-10-02</date> + <revremark>Qiita から移植</revremark> + </revision> + </revhistory> + </info> + <simpara>この記事は Qiita から移植してきたものです。 元 URL: + <link xl:href="https://qiita.com/nsfisis/items/9a429432258bbcd6c565">https://qiita.com/nsfisis/items/9a429432258bbcd6c565</link></simpara> +<simpara><hr/></simpara> +<section xml:id="_前置き"> + <title>前置き</title> + <simpara>Rust + において、プリミティブ型の名前は予約語でない。したがって、次のコードは合法である。</simpara> +<programlisting language="rust" linenumbering="unnumbered">#![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;</programlisting> +<simpara>では、普段単に <literal>bool</literal> と書いたとき、この <literal>bool</literal> + は一体どこから来ているのか。rustc のソースを追ってみた。</simpara> +<blockquote> + <simpara>前提知識: 一般的なコンパイラの構造、用語。<literal>rustc</literal> そのものの知識は不要 + (というよりも筆者自身がよく知らない)</simpara> +</blockquote> +</section> +<section xml:id="_調査"> + <title>調査</title> + <simpara>調査に使用したソース (調査時点での最新 master)</simpara> + <simpara><link xl:href="https://github.com/rust-lang/rust/tree/511ed9f2356af365ad8affe046b3dd33f7ac3c98">https://github.com/rust-lang/rust/tree/511ed9f2356af365ad8affe046b3dd33f7ac3c98</link></simpara> + <simpara>どのようにして調べるか。rustc + の構造には詳しくないため、すぐに当たりをつけるのは難しい。</simpara> +<simpara>大雑把な構造としては、<literal>compiler</literal> フォルダ以下に <literal>rustc_*</literal> + という名前のクレートが数十個入っている。これがどうやら <literal>rustc</literal> + コマンドの実装部のようだ。</simpara> +<simpara><literal>rustc</literal> はセルフホストされている (= <literal>rustc</literal> 自身が Rust で書かれている) +ので、<literal>bool</literal> や <literal>char</literal> +などで適当に検索をかけてもノイズが多すぎて話にならない。 +しかし、お誂え向きなことに <literal>i128</literal>/<literal>u128</literal> +というコンパイラ自身が使うことがなさそうな型が存在するのでこれを使って +<literal>git grep</literal> してみる。</simpara> +<literallayout class="monospaced">$ 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</literallayout> +<simpara>165 +程度であれば探すことができそうだ。今回は、クレート名を見ておおよその当たりをつけた。</simpara> +<literallayout class="monospaced">$ git grep "\bi128\b" +... +rustc_resolve/src/lib.rs: table.insert(sym::i128, Int(IntTy::I128)); +...</literallayout> +<simpara><literal>rustc_resolve</literal> + というのはいかにも名前解決を担いそうなクレート名である。該当箇所を見てみる。</simpara> +<programlisting language="rust" linenumbering="unnumbered">/// 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 } +} +}</programlisting> +<simpara>これは初めに列挙したプリミティブ型の一覧と一致している。doc comment +にも、</simpara> +<blockquote> + <simpara>All other types are defined somewhere and possibly imported, but the + primitive ones need special handling, since they have no place of + origin.</simpara> +</blockquote> +<simpara>とある。次はこの struct +の使用箇所を追う。追うと言っても使われている箇所は次の一箇所しかない。なお説明に不要な箇所は大きく削っている。</simpara> +<programlisting language="rust" linenumbering="unnumbered"> /// 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 +}</programlisting> +<simpara>関数名や doc comment が示している通り、この関数は識別子 (identifier, +ident) を現在のレキシカルスコープ内で解決 (resolve) する。 +<literal>if ns == TypeNS</literal> のブロック内では、<literal>primitive_type_table</literal> (上記の +<literal>PrimitiveTypeTable::new()</literal> で作られた変数) に含まれている識別子 +(<literal>bool</literal>、<literal>i32</literal> など) +かどうか判定し、そうであればそれに紐づけられたプリミティブ型を返している。</simpara> +<simpara>なお、<literal>ns</literal> は「名前空間」を示す変数である。Rust +における名前空間はC言語におけるそれとほとんど同じで、今探している名前が関数名/変数名なのか型なのかマクロなのかを区別している。この +<literal>if</literal> +は、プリミティブ型に解決されるのは型を探しているときだけだ、と言っている。</simpara> +<simpara>重要なのは、これが <literal>resolve_ident_in_lexical_scope()</literal> + の最後に書かれている点である。つまり、最初に挙げたプリミティブ型の識別子は、「名前解決の最終段階で」、「他に同名の型が見つかっていなければ」プリミティブ型として解決される。</simpara> +<simpara>動作がわかったところで、例として次のコードを考える。</simpara> +<programlisting language="rust" linenumbering="unnumbered">#![allow(non_camel_case_types)] + +struct bool; + +fn main() { +let _: bool = bool; +}</programlisting> +<simpara>ここで <literal>main()</literal> の <literal>bool</literal> は <literal>struct bool</literal> + として解決される。なぜなら、プリミティブ型の判定をする前に <literal>bool</literal> + という名前の別の型が見つかるからだ。</simpara> +</section> +<section xml:id="_まとめ"> + <title>まとめ</title> + <simpara>Rust + のプリミティブ型は予約語ではない。名前解決の最終段階で特別扱いされ、他に同名の型が見つかっていなければ対応するプリミティブ型に解決される。</simpara> +</section> +</article> diff --git a/content/posts/2021-10-02/vim-difference-between-autocmd-bufwrite-and-bufwritepre.adoc b/content/posts/2021-10-02/vim-difference-between-autocmd-bufwrite-and-bufwritepre.adoc deleted file mode 100644 index 061e764..0000000 --- a/content/posts/2021-10-02/vim-difference-between-autocmd-bufwrite-and-bufwritepre.adoc +++ /dev/null @@ -1,120 +0,0 @@ -= [Vim] autocmd events の BufWrite/BufWritePre の違い -:tags: vim -:description: Vim の autocmd events における BufWrite/BufWritePre がどう違うのかを調べた結果、 \ - 違いはないことがわかった。 -:revision-1: 2021-10-02 Qiita から移植 - -この記事は Qiita から移植してきたものです。 元 URL: -https://qiita.com/nsfisis/items/79ab4db8564032de0b25 - -''''' - -== TL; DR - -違いはない。ただのエイリアス。 - -== 調査記録 - -Vim の autocmd events には似通った名前のものがいくつかある。大抵は -`:help` -に説明があるが、この記事のタイトルにある2つを含めた以下のイベントには、その違いについて説明がない。 - -* `BufRead`/`BufReadPost` -* `BufWrite`/`BufWritePre` -* `BufAdd`/`BufCreate` - -このうち、`BufAdd`/`BufCreate` に関しては、`:help BufCreate` に - -____ -The BufCreate event is for historic reasons. -____ - -とあり、おそらくは `BufAdd` -のエイリアスであろうということがわかる。他の2組も同様ではないかと予想されるが、確認のため -vim と neovim のソースコードを調査した。 - -____ -ソースコードへのリンク -https://github.com/vim/vim/tree/8e6be34338f13a6a625f19bcef82019c9adc65f2[vim -(調査時点での master branch)] -https://github.com/neovim/neovim/tree/71d4f5851f068eeb432af34850dddda8cc1c71e3[neovim -(上に同じ)] -____ - -=== vim のソースコード - -以下は、autocmd events -の名前と内部で使われている整数値とのマッピングを定義している箇所である。見ての通り、上でエイリアスではないかと述べた3組には、それぞれ同じ内部値が使われている。 - -https://github.com/vim/vim/blob/8e6be34338f13a6a625f19bcef82019c9adc65f2/src/autocmd.c#L85-L86 - -[source,c] ----- - {"BufAdd", EVENT_BUFADD}, - {"BufCreate", EVENT_BUFADD}, ----- - -https://github.com/vim/vim/blob/8e6be34338f13a6a625f19bcef82019c9adc65f2/src/autocmd.c#L95-L97 - -[source,c] ----- - {"BufRead", EVENT_BUFREADPOST}, - {"BufReadCmd", EVENT_BUFREADCMD}, - {"BufReadPost", EVENT_BUFREADPOST}, ----- - -https://github.com/vim/vim/blob/8e6be34338f13a6a625f19bcef82019c9adc65f2/src/autocmd.c#L103-L105 - -[source,c] ----- - {"BufWrite", EVENT_BUFWRITEPRE}, - {"BufWritePost", EVENT_BUFWRITEPOST}, - {"BufWritePre", EVENT_BUFWRITEPRE}, ----- - -=== neovim のソースコード - -neovim の場合でも同様のマッピングが定義されているが、こちらの場合は Lua -で書かれている。以下にある通り、はっきり `aliases` と書かれている。 - -https://github.com/neovim/neovim/blob/71d4f5851f068eeb432af34850dddda8cc1c71e3/src/nvim/auevents.lua#L119-L124 - -[source,lua] ----- - aliases = { - BufCreate = 'BufAdd', - BufRead = 'BufReadPost', - BufWrite = 'BufWritePre', - FileEncoding = 'EncodingChanged', - }, ----- - -ところで、上では取り上げなかった `FileEncoding` だが、これは -`:help FileEncoding` にしっかりと書いてある。 - -.... - *FileEncoding* -FileEncoding Obsolete. It still works and is equivalent - to |EncodingChanged|. -.... - -=== まとめ - -記事タイトルについて言えば、どちらも変わらないので好きな方を使えばよい。あえて言えば、次のようになるだろう。 - -* `BufAdd`/`BufCreate` -** → `BufCreate` は歴史的な理由により (``for historic reasons'') -存在しているため、新しい方 (`BufAdd`) を使う -* `BufRead`/`BufReadPost` -** → `BufReadPre` との対称性のため、あるいは `BufWritePost` -との対称性のため `BufReadPost` を使う -* `BufWrite`/`BufWritePre` -** → `BufWritePost` との対称性のため、あるいは `BufReadPre` -との対称性のため `BufWritePre` を使う -* `FileEncoding`/`EncodingChanged` -** → `FileEncoding` は ``Obsolete'' -と明言されているので、`EncodingChanged` を使う - -ところでこの調査で知ったのだが、`BufRead` と `BufWrite` -は上にある通り発火するタイミングが「後」と「前」で対称性がない。可能なら -`Pre`/`Post` 付きのものを使った方が分かりやすいだろう。 diff --git a/content/posts/2021-10-02/vim-difference-between-autocmd-bufwrite-and-bufwritepre.xml b/content/posts/2021-10-02/vim-difference-between-autocmd-bufwrite-and-bufwritepre.xml new file mode 100644 index 0000000..ed7f03f --- /dev/null +++ b/content/posts/2021-10-02/vim-difference-between-autocmd-bufwrite-and-bufwritepre.xml @@ -0,0 +1,134 @@ +<?xml version="1.0" encoding="UTF-8"?> +<article xmlns="http://docbook.org/ns/docbook" xmlns:xl="http://www.w3.org/1999/xlink" version="5.0"> + <info> + <title>【Vim】 autocmd events の BufWrite/BufWritePre の違い</title> + <abstract> + Vim の autocmd events における BufWrite/BufWritePre がどう違うのかを調べた結果、違いはないことがわかった。 + </abstract> + <keywordset> + <keyword>vim</keyword> + </keywordset> + <revhistory> + <revision> + <date>2021-10-02</date> + <revremark>Qiita から移植</revremark> + </revision> + </revhistory> + </info> + <simpara>この記事は Qiita から移植してきたものです。 元 URL: + <link xl:href="https://qiita.com/nsfisis/items/79ab4db8564032de0b25">https://qiita.com/nsfisis/items/79ab4db8564032de0b25</link></simpara> +<simpara><hr/></simpara> +<section xml:id="_tl_dr"> + <title>TL; DR</title> + <simpara>違いはない。ただのエイリアス。</simpara> +</section> +<section xml:id="_調査記録"> + <title>調査記録</title> + <simpara>Vim の autocmd events には似通った名前のものがいくつかある。大抵は + <literal>:help</literal> + に説明があるが、この記事のタイトルにある2つを含めた以下のイベントには、その違いについて説明がない。</simpara> +<itemizedlist> + <listitem> + <simpara><literal>BufRead</literal>/<literal>BufReadPost</literal></simpara> + </listitem> + <listitem> + <simpara><literal>BufWrite</literal>/<literal>BufWritePre</literal></simpara> + </listitem> + <listitem> + <simpara><literal>BufAdd</literal>/<literal>BufCreate</literal></simpara> + </listitem> +</itemizedlist> +<simpara>このうち、<literal>BufAdd</literal>/<literal>BufCreate</literal> に関しては、<literal>:help BufCreate</literal> に</simpara> +<blockquote> + <simpara>The BufCreate event is for historic reasons.</simpara> +</blockquote> +<simpara>とあり、おそらくは <literal>BufAdd</literal> + のエイリアスであろうということがわかる。他の2組も同様ではないかと予想されるが、確認のため + vim と neovim のソースコードを調査した。</simpara> +<blockquote> + <simpara>ソースコードへのリンク + <link xl:href="https://github.com/vim/vim/tree/8e6be34338f13a6a625f19bcef82019c9adc65f2">vim + (調査時点での master branch)</link> +<link xl:href="https://github.com/neovim/neovim/tree/71d4f5851f068eeb432af34850dddda8cc1c71e3">neovim +(上に同じ)</link></simpara> +</blockquote> +<section xml:id="_vim_のソースコード"> + <title>vim のソースコード</title> + <simpara>以下は、autocmd events + の名前と内部で使われている整数値とのマッピングを定義している箇所である。見ての通り、上でエイリアスではないかと述べた3組には、それぞれ同じ内部値が使われている。</simpara> +<simpara><link xl:href="https://github.com/vim/vim/blob/8e6be34338f13a6a625f19bcef82019c9adc65f2/src/autocmd.c#L85-L86">https://github.com/vim/vim/blob/8e6be34338f13a6a625f19bcef82019c9adc65f2/src/autocmd.c#L85-L86</link></simpara> +<programlisting language="c" linenumbering="unnumbered"> {"BufAdd", EVENT_BUFADD}, +{"BufCreate", EVENT_BUFADD},</programlisting> +<simpara><link xl:href="https://github.com/vim/vim/blob/8e6be34338f13a6a625f19bcef82019c9adc65f2/src/autocmd.c#L95-L97">https://github.com/vim/vim/blob/8e6be34338f13a6a625f19bcef82019c9adc65f2/src/autocmd.c#L95-L97</link></simpara> +<programlisting language="c" linenumbering="unnumbered"> {"BufRead", EVENT_BUFREADPOST}, +{"BufReadCmd", EVENT_BUFREADCMD}, +{"BufReadPost", EVENT_BUFREADPOST},</programlisting> +<simpara><link xl:href="https://github.com/vim/vim/blob/8e6be34338f13a6a625f19bcef82019c9adc65f2/src/autocmd.c#L103-L105">https://github.com/vim/vim/blob/8e6be34338f13a6a625f19bcef82019c9adc65f2/src/autocmd.c#L103-L105</link></simpara> +<programlisting language="c" linenumbering="unnumbered"> {"BufWrite", EVENT_BUFWRITEPRE}, +{"BufWritePost", EVENT_BUFWRITEPOST}, +{"BufWritePre", EVENT_BUFWRITEPRE},</programlisting> +</section> +<section xml:id="_neovim_のソースコード"> + <title>neovim のソースコード</title> + <simpara>neovim の場合でも同様のマッピングが定義されているが、こちらの場合は Lua + で書かれている。以下にある通り、はっきり <literal>aliases</literal> と書かれている。</simpara> +<simpara><link xl:href="https://github.com/neovim/neovim/blob/71d4f5851f068eeb432af34850dddda8cc1c71e3/src/nvim/auevents.lua#L119-L124">https://github.com/neovim/neovim/blob/71d4f5851f068eeb432af34850dddda8cc1c71e3/src/nvim/auevents.lua#L119-L124</link></simpara> +<programlisting language="lua" linenumbering="unnumbered"> aliases = { +BufCreate = 'BufAdd', +BufRead = 'BufReadPost', +BufWrite = 'BufWritePre', +FileEncoding = 'EncodingChanged', +},</programlisting> +<simpara>ところで、上では取り上げなかった <literal>FileEncoding</literal> だが、これは +<literal>:help FileEncoding</literal> にしっかりと書いてある。</simpara> +<literallayout class="monospaced"> *FileEncoding* +FileEncoding Obsolete. It still works and is equivalent +to |EncodingChanged|.</literallayout> +</section> +<section xml:id="_まとめ"> + <title>まとめ</title> + <simpara>記事タイトルについて言えば、どちらも変わらないので好きな方を使えばよい。あえて言えば、次のようになるだろう。</simpara> + <itemizedlist> + <listitem> + <simpara><literal>BufAdd</literal>/<literal>BufCreate</literal></simpara> + <itemizedlist> + <listitem> + <simpara>→ <literal>BufCreate</literal> は歴史的な理由により (<literal>`for historic reasons'') + 存在しているため、新しい方 (`BufAdd</literal>) を使う</simpara> + </listitem> + </itemizedlist> +</listitem> +<listitem> + <simpara><literal>BufRead</literal>/<literal>BufReadPost</literal></simpara> + <itemizedlist> + <listitem> + <simpara>→ <literal>BufReadPre</literal> との対称性のため、あるいは <literal>BufWritePost</literal> + との対称性のため <literal>BufReadPost</literal> を使う</simpara> + </listitem> + </itemizedlist> +</listitem> +<listitem> + <simpara><literal>BufWrite</literal>/<literal>BufWritePre</literal></simpara> + <itemizedlist> + <listitem> + <simpara>→ <literal>BufWritePost</literal> との対称性のため、あるいは <literal>BufReadPre</literal> + との対称性のため <literal>BufWritePre</literal> を使う</simpara> + </listitem> + </itemizedlist> +</listitem> +<listitem> + <simpara><literal>FileEncoding</literal>/<literal>EncodingChanged</literal></simpara> + <itemizedlist> + <listitem> + <simpara>→ <literal>FileEncoding</literal> は <literal>`Obsolete'' + と明言されているので、`EncodingChanged</literal> を使う</simpara> +</listitem> +</itemizedlist> +</listitem> +</itemizedlist> +<simpara>ところでこの調査で知ったのだが、<literal>BufRead</literal> と <literal>BufWrite</literal> + は上にある通り発火するタイミングが「後」と「前」で対称性がない。可能なら + <literal>Pre</literal>/<literal>Post</literal> 付きのものを使った方が分かりやすいだろう。</simpara> +</section> +</section> +</article> diff --git a/content/posts/2021-10-02/vim-swap-order-of-selected-lines.adoc b/content/posts/2021-10-02/vim-swap-order-of-selected-lines.adoc deleted file mode 100644 index b937625..0000000 --- a/content/posts/2021-10-02/vim-swap-order-of-selected-lines.adoc +++ /dev/null @@ -1,165 +0,0 @@ -= Vimで選択した行の順番を入れ替える -:tags: vim -:description: Vim で選択した行の順番を入れ替える方法。 -:revision-1: 2021-10-02 Qiita から移植 - -この記事は Qiita から移植してきたものです。 元 URL: -https://qiita.com/nsfisis/items/4fefb361d9a693803520 - -''''' - -== バージョン情報 - -`:version` の一部 - -____ -VIM - Vi IMproved 8.2 (2019 Dec 12, compiled Jan 26 2020 11:30:30) macOS -version Included patches: 1-148 Huge version without GUI. -____ - -== よく紹介されている手法 - -=== `tac` / `tail` - -`tac` や `tail -r` などの外部コマンドを `!` -を使って呼び出し、置き換える。 - -____ -:h v_! -____ - -`tac` コマンドや `tail` の `-r` -オプションは環境によって利用できないことがあり、複数の環境を行き来する場合に採用しづらい - -=== `:g/^/m0` - -こちらは外部コマンドに頼らず、Vim の機能のみを使う。`g` は `:global` -コマンドの、`m` は `:move` コマンドの略 - -`:global` コマンドは `:[range]global/{pattern}/[command]` -のように使い、`[range]` で指定された範囲の行のうち、`{pattern}` -で指定された検索パターンにマッチする行に対して、順番に `[command]` -で指定された Ex コマンドを呼び出す。 - -____ -:h :global -____ - -`:move` コマンドは `[range]:move {address}` のように使い、`[range]` -で指定された範囲の行を `{address}` で指定された位置に移動させる。 - -____ -:h :move -____ - -`:g/^/m0` のように組み合わせると、「すべての行を1行ずつ -0行目(1行目の上)に動かす」という動きをする。これは確かに行の入れ替えになっている。 - -なお、`:g/^/m0` は全ての行を入れ替えるが、`:N,Mg/^/mN-1` とすることで -N行目から -M行目を処理範囲とするよう拡張できる。手でこれを入力するわけにはいかないので、次のようなコマンドを用意する。 - -[source,vim] ----- -command! -bar -range=% - \ Reverse - \ <line1>,<line2>g/^/m<line1>-1 ----- - -これは望みの動作をするが、実際に実行してみると全行がハイライトされてしまう。次節で詳細を述べる。 - -== `:g/^/m0` の問題点 - -`:global` -コマンドは各行に対してマッチングを行う際、現在の検索パターンを上書きしてしまう。`^` -は行の先頭にマッチするため、結果として全ての行がハイライトされてしまう。`'hlsearch'` -オプションを無効にしている場合その限りではないが、その場合でも直前の検索パターンが失われてしまうと -`n` コマンドなどの際に不便である。 - -____ -:h @/ -____ - -== 解決策 - -____ -[2020/9/28追記] より簡潔な方法を見つけたので次節に追記した -____ - -前述した `:Reverse` コマンドの定義を少し変えて、次のようにする: - -[source,vim] ----- -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>) ----- - -実行しているコマンドが変わったわけではないが、関数呼び出しを経由するようにした。これだけで前述の問題が解決する。 - -この理由は、ユーザー定義関数を実行する際は検索パターンが一度保存され、実行が終了したあと復元されるため。結果として検索パターンが -`^` で上書きされることがなくなる。 - -Vim のヘルプから該当箇所を引用する (強調は筆者による)。 - -____ -:h autocmd-searchpat - -*Autocommands do not change the current search patterns.* Vim saves the -current search patterns before executing autocommands then restores them -after the autocommands finish. This means that autocommands do not -affect the strings highlighted with the `hlsearch' option. -____ - -これは autocommand -の実行に関しての記述だが、これと同じことがユーザー定義関数の実行時にも適用される。このことは -`:nohlsearch` のヘルプにある。同じく該当箇所を引用する -(強調は筆者による)。 - -____ -:h :nohlsearch - -(略) This command doesn’t work in an autocommand, because the -highlighting state is saved and restored when executing autocommands -|autocmd-searchpat|. *Same thing for when invoking a user function.* -____ - -この仕様により、`:g/^/m0` -の呼び出しをユーザー定義関数に切り出すことで上述の問題を解決できる。 - -== 解決策 (改訂版) - -____ -[2020/9/28追記] より簡潔な方法を見つけたため追記する -____ - -[source,vim] ----- -command! -bar -range=% - \ Reverse - \ keeppatterns <line1>,<line2>g/^/m<line1>-1 ----- - -まさにこのための Exコマンド、`:keeppatterns` -が存在する。`:keeppatterns {command}` -のように使い、読んで字の如く、後ろに続く -Exコマンドを「現在の検索パターンを保ったまま」実行する。はるかに分かりやすく意図を表現できる。 - -____ -:h :keeppatterns -____ - -== コピペ用再掲 - -[source,vim] ----- -" License: Public Domain - -command! -bar -range=% - \ Reverse - \ keeppatterns <line1>,<line2>g/^/m<line1>-1 ----- diff --git a/content/posts/2021-10-02/vim-swap-order-of-selected-lines.xml b/content/posts/2021-10-02/vim-swap-order-of-selected-lines.xml new file mode 100644 index 0000000..9b7c809 --- /dev/null +++ b/content/posts/2021-10-02/vim-swap-order-of-selected-lines.xml @@ -0,0 +1,140 @@ +<?xml version="1.0" encoding="UTF-8"?> +<article xmlns="http://docbook.org/ns/docbook" xmlns:xl="http://www.w3.org/1999/xlink" version="5.0"> + <info> + <title>Vimで選択した行の順番を入れ替える</title> + <abstract> + Vim で選択した行の順番を入れ替える方法。 + </abstract> + <keywordset> + <keyword>vim</keyword> + </keywordset> + <revhistory> + <revision> + <date>2021-10-02</date> + <revremark>Qiita から移植</revremark> + </revision> + </revhistory> + </info> + <simpara>この記事は Qiita から移植してきたものです。 元 URL: + <link xl:href="https://qiita.com/nsfisis/items/4fefb361d9a693803520">https://qiita.com/nsfisis/items/4fefb361d9a693803520</link></simpara> +<simpara><hr/></simpara> +<section xml:id="_バージョン情報"> + <title>バージョン情報</title> + <simpara><literal>:version</literal> の一部</simpara> + <blockquote> + <simpara>VIM - Vi IMproved 8.2 (2019 Dec 12, compiled Jan 26 2020 11:30:30) macOS + version Included patches: 1-148 Huge version without GUI.</simpara> +</blockquote> +</section> +<section xml:id="_よく紹介されている手法"> + <title>よく紹介されている手法</title> + <section xml:id="_tac_tail"> + <title><literal>tac</literal> / <literal>tail</literal></title> + <simpara><literal>tac</literal> や <literal>tail -r</literal> などの外部コマンドを <literal>!</literal> + を使って呼び出し、置き換える。</simpara> + <blockquote> + <simpara>:h v_!</simpara> + </blockquote> + <simpara><literal>tac</literal> コマンドや <literal>tail</literal> の <literal>-r</literal> + オプションは環境によって利用できないことがあり、複数の環境を行き来する場合に採用しづらい</simpara> + </section> + <section xml:id="_gm0"> + <title><literal>:g/^/m0</literal></title> + <simpara>こちらは外部コマンドに頼らず、Vim の機能のみを使う。<literal>g</literal> は <literal>:global</literal> + コマンドの、<literal>m</literal> は <literal>:move</literal> コマンドの略</simpara> + <simpara><literal>:global</literal> コマンドは <literal>:[range]global/{pattern}/[command]</literal> + のように使い、<literal>[range]</literal> で指定された範囲の行のうち、<literal>{pattern}</literal> + で指定された検索パターンにマッチする行に対して、順番に <literal>[command]</literal> + で指定された Ex コマンドを呼び出す。</simpara> + <blockquote> + <simpara>:h :global</simpara> + </blockquote> + <simpara><literal>:move</literal> コマンドは <literal>[range]:move {address}</literal> のように使い、<literal>[range]</literal> + で指定された範囲の行を <literal>{address}</literal> で指定された位置に移動させる。</simpara> + <blockquote> + <simpara>:h :move</simpara> + </blockquote> + <simpara><literal>:g/^/m0</literal> のように組み合わせると、「すべての行を1行ずつ + 0行目(1行目の上)に動かす」という動きをする。これは確かに行の入れ替えになっている。</simpara> + <simpara>なお、<literal>:g/^/m0</literal> は全ての行を入れ替えるが、<literal>:N,Mg/^/mN-1</literal> とすることで + N行目から + M行目を処理範囲とするよう拡張できる。手でこれを入力するわけにはいかないので、次のようなコマンドを用意する。</simpara> +<programlisting language="vim" linenumbering="unnumbered">command! -bar -range=% +\ Reverse +\ <line1>,<line2>g/^/m<line1>-1</programlisting> +<simpara>これは望みの動作をするが、実際に実行してみると全行がハイライトされてしまう。次節で詳細を述べる。</simpara> +</section> +</section> +<section xml:id="_gm0_の問題点"> + <title><literal>:g/^/m0</literal> の問題点</title> + <simpara><literal>:global</literal> + コマンドは各行に対してマッチングを行う際、現在の検索パターンを上書きしてしまう。<literal>^</literal> + は行の先頭にマッチするため、結果として全ての行がハイライトされてしまう。<literal>'hlsearch'</literal> + オプションを無効にしている場合その限りではないが、その場合でも直前の検索パターンが失われてしまうと + <literal>n</literal> コマンドなどの際に不便である。</simpara> + <blockquote> + <simpara>:h @/</simpara> + </blockquote> +</section> +<section xml:id="_解決策"> + <title>解決策</title> + <blockquote> + <simpara>[2020/9/28追記] より簡潔な方法を見つけたので次節に追記した</simpara> + </blockquote> + <simpara>前述した <literal>:Reverse</literal> コマンドの定義を少し変えて、次のようにする:</simpara> + <programlisting language="vim" linenumbering="unnumbered">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>)</programlisting> +<simpara>実行しているコマンドが変わったわけではないが、関数呼び出しを経由するようにした。これだけで前述の問題が解決する。</simpara> +<simpara>この理由は、ユーザー定義関数を実行する際は検索パターンが一度保存され、実行が終了したあと復元されるため。結果として検索パターンが +<literal>^</literal> で上書きされることがなくなる。</simpara> +<simpara>Vim のヘルプから該当箇所を引用する (強調は筆者による)。</simpara> +<blockquote> + <simpara>:h autocmd-searchpat</simpara> + <simpara><emphasis role="strong">Autocommands do not change the current search patterns.</emphasis> Vim saves the + current search patterns before executing autocommands then restores them + after the autocommands finish. This means that autocommands do not + affect the strings highlighted with the `hlsearch' option.</simpara> +</blockquote> +<simpara>これは autocommand +の実行に関しての記述だが、これと同じことがユーザー定義関数の実行時にも適用される。このことは +<literal>:nohlsearch</literal> のヘルプにある。同じく該当箇所を引用する +(強調は筆者による)。</simpara> +<blockquote> + <simpara>:h :nohlsearch</simpara> + <simpara>(略) This command doesn’t work in an autocommand, because the + highlighting state is saved and restored when executing autocommands + |autocmd-searchpat|. <emphasis role="strong">Same thing for when invoking a user function.</emphasis></simpara> +</blockquote> +<simpara>この仕様により、<literal>:g/^/m0</literal> + の呼び出しをユーザー定義関数に切り出すことで上述の問題を解決できる。</simpara> +</section> +<section xml:id="_解決策_改訂版"> + <title>解決策 (改訂版)</title> + <blockquote> + <simpara>[2020/9/28追記] より簡潔な方法を見つけたため追記する</simpara> + </blockquote> + <programlisting language="vim" linenumbering="unnumbered">command! -bar -range=% + \ Reverse + \ keeppatterns <line1>,<line2>g/^/m<line1>-1</programlisting> +<simpara>まさにこのための Exコマンド、<literal>:keeppatterns</literal> + が存在する。<literal>:keeppatterns {command}</literal> + のように使い、読んで字の如く、後ろに続く + Exコマンドを「現在の検索パターンを保ったまま」実行する。はるかに分かりやすく意図を表現できる。</simpara> +<blockquote> + <simpara>:h :keeppatterns</simpara> +</blockquote> +</section> +<section xml:id="_コピペ用再掲"> + <title>コピペ用再掲</title> + <programlisting language="vim" linenumbering="unnumbered">" License: Public Domain + + command! -bar -range=% + \ Reverse + \ keeppatterns <line1>,<line2>g/^/m<line1>-1</programlisting> +</section> +</article> diff --git a/content/posts/2022-04-09/phperkaigi-2022-tokens.adoc b/content/posts/2022-04-09/phperkaigi-2022-tokens.adoc deleted file mode 100644 index 51d9a36..0000000 --- a/content/posts/2022-04-09/phperkaigi-2022-tokens.adoc +++ /dev/null @@ -1,469 +0,0 @@ -= PHPerKaigi 2022 トークン問題の解説 -:tags: conference, php, phperkaigi -:description: PHPerKaigi 2022 で私が作成した PHPer チャレンジ問題を解説する。 -:revision-1: 2022-04-09 公開 -:revision-2: 2022-04-16 2問目、3問目の解説を追加、1問目に加筆 - -== はじめに - -本日開始された https://phperkaigi.jp/2022/[PHPerKaigi 2022] の PHPer -チャレンジにおいて、弊社 -https://www.dgcircus.com/[デジタルサーカス株式会社] の問題を -3問作成した。この記事では、これらの問題の解説をおこなう。 - -リポジトリはこちら: https://github.com/nsfisis/PHPerKaigi2022-tokens - -== 第1問 brainf_ck.php - -ソースコードはこちら。実行には PHP 8.1 以上が必要なので注意。 - -[source,php] ----- -<?php - -declare(strict_types=0O1); - -namespace Dgcircus\PHPerKaigi\Y2022; - -/** - * @todo - * Run this program to acquire a PHPer token. - */ - -https://creativecommons.org/publicdomain/zero/1.0/ - -\error_reporting(~+!'We are hiring!'); - -$z = fn($f) => (fn($x) => $f(fn(...$xs) => $x($x)(...$xs)))(fn($x) => $f(fn(...$xs) => $x($x)(...$xs))); -$id = \spl_object_id(...); -$put = fn($c) => \printf('%c', $c); -$mm = fn($p, $n) => new \ArrayObject(\array_fill(+!![], $n, $p)); - -$👉 = fn($m, $p, $b, $e, $mp, $pc) => [++$mp, ++$pc]; -$👈 = fn($m, $p, $b, $e, $mp, $pc) => [--$mp, ++$pc]; -$👍 = fn($m, $p, $b, $e, $mp, $pc) => [$mp, ++$pc, ++$m[$mp]]; -$👎 = fn($m, $p, $b, $e, $mp, $pc) => [$mp, ++$pc, --$m[$mp]]; -$📝 = fn($m, $p, $b, $e, $mp, $pc) => [$mp, ++$pc, $put($m[$mp])]; -$🤡 = fn($m, $p, $b, $e, $mp, $pc) => match ($m[$mp]) { - +!![] => [$mp, $z(fn($loop) => fn($pc, $n) => match ($id($p[$pc])) { - $b => $loop(++$pc, ++$n), - $e => $n === +!![] ? ++$pc : $loop(++$pc, --$n), - default => $loop(++$pc, $n), - })($pc, -![])], - default => [$mp, ++$pc], -}; -$🎪 = fn($m, $p, $b, $e, $mp, $pc) => match ($m[$mp]) { - +!![] => [$mp, ++$pc], - default => [$mp, $z(fn($loop) => fn($pc, $n) => match ($id($p[$pc])) { - $e => $loop(--$pc, ++$n), - $b => $n === +!![] ? $pc+![] : $loop(--$pc, --$n), - default => $loop(--$pc, $n), - })($pc, -![])], -}; -$🐘 = fn($p) => $z(fn($loop) => fn($m, $p, $b, $e, $mp, $pc) => - isset($p[$pc]) && $loop($m, $p, $b, $e, ...($p[$pc]($m, $p, $b, $e, $mp, $pc))) -)($mm(+!![], +(![].![])), $p, $id($🤡), $id($🎪), +!![], +!![]); - -$🐘([ - $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, - $🤡, - $👉, $👍, $👍, $👍, - $👉, $👍, $👍, $👍, $👍, $👍, - $👉, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, - $👉, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, - $👈, $👈, $👈, $👈, $👎, - $🎪, - $👉, $👍, $👍, $👍, $👍, $👍, $📝, - $👎, $👎, $📝, - $👉, $👎, $👎, $👎, $📝, - $👉, $👎, $👎, $👎, $📝, - $👎, $👎, $📝, - $👎, $📝, - $👈, $📝, - $👉, $👉, $👎, $👎, $📝, - $👍, $👍, $👍, $👍, $👍, $👍, $👍, $📝, - $👈, $👎, $👎, $👎, $👎, $📝, - $👈, $📝, - $👉, $👍, $👍, $📝, - $👉, $👎, $📝, - $👈, $📝, -]); ----- - -この問題は、単に適切なバージョンの PHP で動かせばトークンが得られる。 - -=== 解説 - -==== 絵文字 - -まず目につくのは大量の絵文字だろう。 PHP -は識別子に使用できる文字の範囲が広く、絵文字も使うことができる。 - -==== プログラム全体 - -Brainf*ck のインタプリタとプログラムになっている。 Brainf*ck -とは、難解プログラミング言語のひとつであり、ここで説明するよりも -Wikipedia の該当ページを読んだ方がよい。 - -https://ja.wikipedia.org/wiki/Brainfuck - -なお、brainf*ck プログラムを普通の書き方で書くと、次のようになる。 - -.... -+ + + + + + + + + + -[ - > + + + - > + + + + + - > + + + + + + + + + + + + - > + + + + + + + + + + - < < < < - -] -> + + + + + . -- - . -> - - - . -> - - - . -- - . -- . -< . -> > - - . -+ + + + + + + . -< - - - - . -< . -> + + . -> - . -< . -.... - -実行結果はこちら: https://ideone.com/22VWmb - -それぞれの絵文字で表された関数が、各命令に対応している。 - -* `$👉`: `>` -* `$👈`: `<` -* `$👍`: `+` -* `$👎`: `-` -* `$📝`: `.` -* `$🤡`: `[` -* `$🎪`: `]` - -`,` (入力) に対応する関数はない -(このプログラムでは使わないので用意していない)。 - -なお、`$🐘` はいわゆる main 関数であり、プログラムの実行部分である。 - -==== 絵文字の選択 - -おおよそ意味に合致するよう選んでいるが、`$🤡` と `$🎪` -は弊社デジタルサーカスにちなんでいる。 また、`$🐘` は PHP -のマスコットの象に由来する。 - -==== strict_types - -`declare` 文の `strict_types` に指定できるのは、`0` か `1` -の数値リテラルだが、 `0x0` や `0b1` のような値も受け付ける。 今回は、PHP -8.1 から追加された、`0O` または `0o` から始まる八進数リテラルを使った。 - -==== URL - -ソースコードのライセンスを示したこの部分だが、 - -[source,php] ----- -https://creativecommons.org/publicdomain/zero/1.0/ ----- - -完全に合法な PHP のコードである。 `https:` 部分はラベル、`//` -以降は行コメントになっている。 - -==== リテラルなしで数値を生成する - -ソースコード中に、ほとんど数値リテラルが書かれていないことにお気づきだろうか。 -PHP では、型変換を利用することで任意の整数を作り出すことができる。 - -[source,php] ----- -assert(0 === +!![]); -assert(1 === +![]); -assert(2 === ![]+![]); -assert(3 === ![]+![]+![]); -assert(10 === +(![].+!![])); ----- - -`[]` に `!` を適用すると `true` が返ってくる。それに `+` -を適用すると、`bool` から `int` ヘの型変換が走り、`1` が生成される。`10` -はさらにトリッキーだ。まず `1` と `0` を作り、`.` で文字列として結合する -(`'10'`)。これに `+` を適用すると、`string` から `int` -への型変換が走り、`10` が生まれる (コード量に頓着しないなら、`1` を 10 -個足し合わせてももちろん 10 が作れる)。 - -また、`error_reporting` に指定しているのは `-1` である。 これは、`!` -によって文字列を `false` にし、`+` によって `false` を `0` -にし、さらにビット反転して `-1` にしている。 - -==== `if` 文なしで条件分岐 - -三項演算子ないし `match` 式を使うことで、`if` -を一切書かずに条件分岐ができる。 また、`&&` / `||` も使えることがある。 -遅延評価が不要なケースでは、`[$t, $f][$cond]` -のような形で分岐することもできる。 - -==== `while`、`for` 文なしでループ - -不動点コンビネータを使って無名再帰する -(詳しい説明は省略する。これらの単語で検索してほしい)。 ここでは、一般に -Z コンビネータとして知られるものを使った (`$z`)。 - -実際のところ、`$🤡` や `$🎪`、`$🐘` は、一度 Scheme (Lisp の一種) -で書いてから PHP に翻訳する形で記述した。 - -なお、PHP は末尾再帰の最適化をおこなわない (少なくとも今のところは) -ので、 あまりに長い brainf*ck -プログラムを書くとスタックオーバーフローする。 - -== 第2問 riddle.php - -ソースコードはこちら。実行には PHP 8.0 以上が必要なので注意。 - -[source,php] ----- -<?php - -/********************************************************* - * This program displays a PHPer token. * - * Guess 'N'. * - * * - * Hints: * - * - N itself has no special meaning, e.g., 42, 8128, * - * it is selected at random. * - * - Each element of $token represents a single letter. * - * - One letter consists of 5x5 cells. * - * - Remember, the output is a complete PHPer token. * - * * - * License: * - * https://creativecommons.org/publicdomain/zero/1.0/ * - *********************************************************/ -const N = 0 /* Change it to your answer. */; -assert(0 <= N && N <= 0b11111_11111_11111_11111_11111); - -$token = [ - 0x14B499C, - 0x0BE34CC, 0x01C9C69, - 0x0ECA069, 0x01C2449, 0x0FDB166, 0x01C9C69, - 0x01C1C66, 0x0FC1C47, 0x01C1C66, - 0x10C5858, 0x1E4E3B8, 0x1A2F2F8, -]; -foreach ($token as $x) { - $x = $x ^ N; - - $x = sprintf('%025b', $x); - $x = str_replace(search: ['0', '1'], replace: [' ', '#'], subject: $x); - $x = implode("\n", str_split($x, length: 5)); - echo "{$x}\n\n"; -} ----- - -さて、この問題はさきほどのように単純に実行しただけでは、謎のブロックが表示されるだけでトークンは得られない。 -トークンを得るためには、ソースコードを読み、定数 `N` -を特定する必要がある。 - -ここでは、私の想定解を解説する。 - -=== 読解 - -まずはソースコードを読んでいく。 - -[source,php] ----- -$token = [ - // 略 -]; ----- - -数値からなる `$token` があり、各要素をループしている。 - -[source,php] ----- - $x = $x ^ N; ----- - -まずは排他的論理和 (xor) を取り、 - -[source,php] ----- - $x = sprintf('%025b', $x); ----- - -二進数に変換して、 - -[source,php] ----- - $x = str_replace(search: ['0', '1'], replace: [' ', '#'], subject: $x); ----- - -0 を空白に、1 を `#` にし、 - -[source,php] ----- - $x = implode("\n", str_split($x, length: 5)); ----- - -5文字ごとに区切ったあと、改行で結合している。 - -=== ヒント - -次に、ソースコードに書いてあるヒントを読んでいく。 - -* `N` それ自体は、42 や 8128 -といったような特別な意味を持たず、ランダムに決められている -* `$token` の各要素は、1文字を表す -* 1文字は 5x5 のセルからなる -* 出力されるのは、完全な PHPer トークンである - -ここで、PHPer トークンは必ず `#` 記号から始まることを思いだすと、 -`$token` の最初の数字 `0x14B499C` は、変換の結果 `#` -になるのではないかと予想される (なお、このことは、リポジトリの README -ファイルに追加ヒントとして書かれている)。 - -=== 解く - -ここまでわかれば、あと一歩で解ける。すなわち、`0x14B499C` が `#` -に変換されるような `N` を見つければよい。 - -`N` は高々 - -[source,php] ----- -assert(0 <= N && N <= 0b11111_11111_11111_11111_11111); ----- - -なのでブルートフォースしてもよいが、ここではブルートフォースしない方法を紹介する。 - -[source,php] ----- -<?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" . - " # # "); ----- - -この一連の変換に対する逆変換を考えると、次のようになる。 - -[source,php] ----- -<?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` が得られる。 - -== 第3問 toquine.php - -ソースコードはこちら。 - -[source,php] ----- -<?php - -// License: https://creativecommons.org/publicdomain/zero/1.0/ -// This is a quine-like program to generate a PHPer token. -// Execute it like this: php toquine.php | php | php | php | ... - -$s = <<<'Q' -<?cuc -// Yvprafr: uggcf://perngvirpbzzbaf.bet/choyvpqbznva/mreb/1.0/ -// Guvf vf n dhvar-yvxr cebtenz gb trarengr n CUCre gbxra. -// Rkrphgr vg yvxr guvf: cuc gbdhvar.cuc | cuc | cuc | cuc | ... -%f$f = %f; -$f = fge_ebg13($f); $kf = [ -%f, -]; -$g = ahyy.snyfr; sbe ($v = 0; $v <= vagqvi(__YVAR__-035,6); ++$v) vs (!vffrg($kf[$v])) oernx; ryfr -$g .= vzcybqr("\a", fge_fcyvg(fge_ercynpr(['0','1'], [' ','##'], fcevags(pue(37) . '025o', $kf[$v])), 012)) . "\a\a"; -$jf = neenl_znc(sa($j) => vzcybqr(', ', $j), neenl_puhax(neenl_znc(sa($k) => fcevags('0k' . pue(37) . '07K', $k), $kf), 10)); -cevags($f, $g, fge_ebg13("<<<'Q'\a{$f}\aQ"), vzcybqr(",\a", $jf)); -Q; -$s = str_rot13($s); $xs = [ -0x0AFABEA, 0x1F294A7, 0x1F2109F, 0x1F294A7, 0x0002800, 0x1F2109F, 0x0117041, 0x1F294A7, 0x1FAD6B5, 0x1F295B7, -0x010FC21, 0x1FAD6B5, 0x1151151, 0x010FC21, 0x1F294A7, 0x1F295B7, 0x1FAD6B5, 0x1F294A7, 0x1F295B7, 0x1F8C63F, -0x1F8C631, 0x1FAD6B5, 0x17AD6BD, 0x17AD6BD, 0x1F8C63F, 0x1F295B7, -]; -$t = null.false; for ($i = 0; $i <= intdiv(__LINE__-035,6); ++$i) if (!isset($xs[$i])) break; else -$t .= implode("\n", str_split(str_replace(['0','1'], [' ','##'], sprintf(chr(37) . '025b', $xs[$i])), 012)) . "\n\n"; -$ws = array_map(fn($w) => implode(', ', $w), array_chunk(array_map(fn($x) => sprintf('0x' . chr(37) . '07X', $x), $xs), 10)); -printf($s, $t, str_rot13("<<<'D'\n{$s}\nD"), implode(",\n", $ws)); ----- - -コメントにもあるとおり、次のようにして実行すれば答えがでてくる。 - -[source,shell-session] ----- -$ php toquine.php | php | php | php | ... ----- - -実際にはもう少しパイプで繋げなければならない。 - -=== 解説 - -==== プログラム全体 - -コメントにもあるとおり、これは quine (風) のプログラムになっている。 -Quine -とは、自分のソースコードをそっくりそのまま出力するようなプログラムのことである。 - -このプログラムは、実行すると自身とほとんど同じプログラムを出力する。 -異なるのはトークンになっている部分のみである。 - -==== トークン - -`$xs` がトークンに対応している。変換のロジックは `riddle.php` -とほぼ同じなので省略する。 - -==== 状態保持 - -トークンの何文字目まで出力したかを、ソースコードを変えずに (quine -なので) 覚えておく必要がある。 -このプログラムでは、トークンが出力されるとソースコードがだんだんと長くなっていくのを利用して、`__LINE__` -から情報を取得している。 - -==== ROT 13 - -Quine は、素朴に書くとプログラムの一部が 2回記述されてしまう。 -これがあまり美しくないので、`toquine.php` では、ROT 13 -変換を使って難読化した。 - -それにしてもなぜこんなものが標準ライブラリに……。 - -== おわりに - -解いていただいたみなさん、また、難易度調整につきあっていただいた社内のみなさん、ありがとうございました。 - -今回は直前に作りはじめたのもあり、3問だけかつ使い古されたネタばかりになってしまいましたが、 -来年は 5問、より面白い問題を持っていきます。 - -実はもう作りはじめているので、どうか来年もありますように……。 diff --git a/content/posts/2022-04-09/phperkaigi-2022-tokens.xml b/content/posts/2022-04-09/phperkaigi-2022-tokens.xml new file mode 100644 index 0000000..b0c749b --- /dev/null +++ b/content/posts/2022-04-09/phperkaigi-2022-tokens.xml @@ -0,0 +1,424 @@ +<?xml version="1.0" encoding="UTF-8"?> +<article xmlns="http://docbook.org/ns/docbook" xmlns:xl="http://www.w3.org/1999/xlink" version="5.0"> + <info> + <title>PHPerKaigi 2022 トークン問題の解説</title> + <abstract> + PHPerKaigi 2022 で私が作成した PHPer チャレンジ問題を解説する。 + </abstract> + <keywordset> + <keyword>conference</keyword> + <keyword>php</keyword> + <keyword>phperkaigi</keyword> + </keywordset> + <revhistory> + <revision> + <date>2022-04-09</date> + <revremark>公開</revremark> + </revision> + <revision> + <date>2022-04-16</date> + <revremark>2問目、3問目の解説を追加、1問目に加筆</revremark> + </revision> + </revhistory> + </info> + <section xml:id="_はじめに"> + <title>はじめに</title> + <simpara>本日開始された <link xl:href="https://phperkaigi.jp/2022/">PHPerKaigi 2022</link> の PHPer + チャレンジにおいて、弊社 + <link xl:href="https://www.dgcircus.com/">デジタルサーカス株式会社</link> の問題を + 3問作成した。この記事では、これらの問題の解説をおこなう。</simpara> + <simpara>リポジトリはこちら: <link xl:href="https://github.com/nsfisis/PHPerKaigi2022-tokens">https://github.com/nsfisis/PHPerKaigi2022-tokens</link></simpara> +</section> +<section xml:id="_第1問_brainf_ck_php"> + <title>第1問 brainf_ck.php</title> + <simpara>ソースコードはこちら。実行には PHP 8.1 以上が必要なので注意。</simpara> + <programlisting language="php" linenumbering="unnumbered"><?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($🎪), +!![], +!![]); + + $🐘([ + $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, + $🤡, + $👉, $👍, $👍, $👍, + $👉, $👍, $👍, $👍, $👍, $👍, + $👉, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, + $👉, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, + $👈, $👈, $👈, $👈, $👎, + $🎪, + $👉, $👍, $👍, $👍, $👍, $👍, $📝, + $👎, $👎, $📝, + $👉, $👎, $👎, $👎, $📝, + $👉, $👎, $👎, $👎, $📝, + $👎, $👎, $📝, + $👎, $📝, + $👈, $📝, + $👉, $👉, $👎, $👎, $📝, + $👍, $👍, $👍, $👍, $👍, $👍, $👍, $📝, + $👈, $👎, $👎, $👎, $👎, $📝, + $👈, $📝, + $👉, $👍, $👍, $📝, + $👉, $👎, $📝, + $👈, $📝, + ]);</programlisting> +<simpara>この問題は、単に適切なバージョンの PHP で動かせばトークンが得られる。</simpara> +<section xml:id="_解説"> + <title>解説</title> + <section xml:id="_絵文字"> + <title>絵文字</title> + <simpara>まず目につくのは大量の絵文字だろう。 PHP + は識別子に使用できる文字の範囲が広く、絵文字も使うことができる。</simpara> +</section> +<section xml:id="_プログラム全体"> + <title>プログラム全体</title> + <simpara>Brainf*ck のインタプリタとプログラムになっている。 Brainf*ck + とは、難解プログラミング言語のひとつであり、ここで説明するよりも + Wikipedia の該当ページを読んだ方がよい。</simpara> +<simpara><link xl:href="https://ja.wikipedia.org/wiki/Brainfuck">https://ja.wikipedia.org/wiki/Brainfuck</link></simpara> +<simpara>なお、brainf*ck プログラムを普通の書き方で書くと、次のようになる。</simpara> +<literallayout class="monospaced">+ + + + + + + + + + +[ +> + + + +> + + + + + +> + + + + + + + + + + + + +> + + + + + + + + + + +< < < < - +] +> + + + + + . +- - . +> - - - . +> - - - . +- - . +- . +< . +> > - - . ++ + + + + + + . +< - - - - . +< . +> + + . +> - . +< .</literallayout> +<simpara>実行結果はこちら: <link xl:href="https://ideone.com/22VWmb">https://ideone.com/22VWmb</link></simpara> +<simpara>それぞれの絵文字で表された関数が、各命令に対応している。</simpara> +<itemizedlist> + <listitem> + <simpara><literal>$👉</literal>: <literal>></literal></simpara> + </listitem> + <listitem> + <simpara><literal>$👈</literal>: <literal><</literal></simpara> + </listitem> + <listitem> + <simpara><literal>$👍</literal>: <literal>+</literal></simpara> + </listitem> + <listitem> + <simpara><literal>$👎</literal>: <literal>-</literal></simpara> + </listitem> + <listitem> + <simpara><literal>$📝</literal>: <literal>.</literal></simpara> + </listitem> + <listitem> + <simpara><literal>$🤡</literal>: <literal>[</literal></simpara> + </listitem> + <listitem> + <simpara><literal>$🎪</literal>: <literal>]</literal></simpara> + </listitem> +</itemizedlist> +<simpara><literal>,</literal> (入力) に対応する関数はない +(このプログラムでは使わないので用意していない)。</simpara> +<simpara>なお、<literal>$🐘</literal> はいわゆる main 関数であり、プログラムの実行部分である。</simpara> +</section> +<section xml:id="_絵文字の選択"> + <title>絵文字の選択</title> + <simpara>おおよそ意味に合致するよう選んでいるが、<literal>$🤡</literal> と <literal>$🎪</literal> + は弊社デジタルサーカスにちなんでいる。 また、<literal>$🐘</literal> は PHP + のマスコットの象に由来する。</simpara> +</section> +<section xml:id="_strict_types"> + <title>strict_types</title> + <simpara><literal>declare</literal> 文の <literal>strict_types</literal> に指定できるのは、<literal>0</literal> か <literal>1</literal> + の数値リテラルだが、 <literal>0x0</literal> や <literal>0b1</literal> のような値も受け付ける。 今回は、PHP + 8.1 から追加された、<literal>0O</literal> または <literal>0o</literal> から始まる八進数リテラルを使った。</simpara> +</section> +<section xml:id="_url"> + <title>URL</title> + <simpara>ソースコードのライセンスを示したこの部分だが、</simpara> + <programlisting language="php" linenumbering="unnumbered">https://creativecommons.org/publicdomain/zero/1.0/</programlisting> + <simpara>完全に合法な PHP のコードである。 <literal>https:</literal> 部分はラベル、<literal>//</literal> + 以降は行コメントになっている。</simpara> +</section> +<section xml:id="_リテラルなしで数値を生成する"> + <title>リテラルなしで数値を生成する</title> + <simpara>ソースコード中に、ほとんど数値リテラルが書かれていないことにお気づきだろうか。 + PHP では、型変換を利用することで任意の整数を作り出すことができる。</simpara> +<programlisting language="php" linenumbering="unnumbered">assert(0 === +!![]); +assert(1 === +![]); +assert(2 === ![]+![]); +assert(3 === ![]+![]+![]); +assert(10 === +(![].+!![]));</programlisting> +<simpara><literal>[]</literal> に <literal>!</literal> を適用すると <literal>true</literal> が返ってくる。それに <literal>+</literal> + を適用すると、<literal>bool</literal> から <literal>int</literal> ヘの型変換が走り、<literal>1</literal> が生成される。<literal>10</literal> + はさらにトリッキーだ。まず <literal>1</literal> と <literal>0</literal> を作り、<literal>.</literal> で文字列として結合する + (<literal>'10'</literal>)。これに <literal>+</literal> を適用すると、<literal>string</literal> から <literal>int</literal> + への型変換が走り、<literal>10</literal> が生まれる (コード量に頓着しないなら、<literal>1</literal> を 10 + 個足し合わせてももちろん 10 が作れる)。</simpara> +<simpara>また、<literal>error_reporting</literal> に指定しているのは <literal>-1</literal> である。 これは、<literal>!</literal> + によって文字列を <literal>false</literal> にし、<literal>+</literal> によって <literal>false</literal> を <literal>0</literal> + にし、さらにビット反転して <literal>-1</literal> にしている。</simpara> +</section> +<section xml:id="_if_文なしで条件分岐"> + <title><literal>if</literal> 文なしで条件分岐</title> + <simpara>三項演算子ないし <literal>match</literal> 式を使うことで、<literal>if</literal> + を一切書かずに条件分岐ができる。 また、<literal>&&</literal> / <literal>||</literal> も使えることがある。 + 遅延評価が不要なケースでは、<literal>[$t, $f][$cond]</literal> + のような形で分岐することもできる。</simpara> +</section> +<section xml:id="_whilefor_文なしでループ"> + <title><literal>while</literal>、<literal>for</literal> 文なしでループ</title> + <simpara>不動点コンビネータを使って無名再帰する + (詳しい説明は省略する。これらの単語で検索してほしい)。 ここでは、一般に + Z コンビネータとして知られるものを使った (<literal>$z</literal>)。</simpara> +<simpara>実際のところ、<literal>$🤡</literal> や <literal>$🎪</literal>、<literal>$🐘</literal> は、一度 Scheme (Lisp の一種) +で書いてから PHP に翻訳する形で記述した。</simpara> +<simpara>なお、PHP は末尾再帰の最適化をおこなわない (少なくとも今のところは) +ので、 あまりに長い brainf*ck +プログラムを書くとスタックオーバーフローする。</simpara> +</section> +</section> +</section> +<section xml:id="_第2問_riddle_php"> + <title>第2問 riddle.php</title> + <simpara>ソースコードはこちら。実行には PHP 8.0 以上が必要なので注意。</simpara> + <programlisting language="php" linenumbering="unnumbered"><?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"; + }</programlisting> +<simpara>さて、この問題はさきほどのように単純に実行しただけでは、謎のブロックが表示されるだけでトークンは得られない。 +トークンを得るためには、ソースコードを読み、定数 <literal>N</literal> +を特定する必要がある。</simpara> +<simpara>ここでは、私の想定解を解説する。</simpara> +<section xml:id="_読解"> + <title>読解</title> + <simpara>まずはソースコードを読んでいく。</simpara> + <programlisting language="php" linenumbering="unnumbered">$token = [ + // 略 + ];</programlisting> +<simpara>数値からなる <literal>$token</literal> があり、各要素をループしている。</simpara> +<programlisting language="php" linenumbering="unnumbered"> $x = $x ^ N;</programlisting> +<simpara>まずは排他的論理和 (xor) を取り、</simpara> +<programlisting language="php" linenumbering="unnumbered"> $x = sprintf('%025b', $x);</programlisting> +<simpara>二進数に変換して、</simpara> +<programlisting language="php" linenumbering="unnumbered"> $x = str_replace(search: ['0', '1'], replace: [' ', '#'], subject: $x);</programlisting> +<simpara>0 を空白に、1 を <literal>#</literal> にし、</simpara> +<programlisting language="php" linenumbering="unnumbered"> $x = implode("\n", str_split($x, length: 5));</programlisting> +<simpara>5文字ごとに区切ったあと、改行で結合している。</simpara> +</section> +<section xml:id="_ヒント"> + <title>ヒント</title> + <simpara>次に、ソースコードに書いてあるヒントを読んでいく。</simpara> + <itemizedlist> + <listitem> + <simpara><literal>N</literal> それ自体は、42 や 8128 + といったような特別な意味を持たず、ランダムに決められている</simpara> + </listitem> + <listitem> + <simpara><literal>$token</literal> の各要素は、1文字を表す</simpara> + </listitem> + <listitem> + <simpara>1文字は 5x5 のセルからなる</simpara> + </listitem> + <listitem> + <simpara>出力されるのは、完全な PHPer トークンである</simpara> + </listitem> +</itemizedlist> +<simpara>ここで、PHPer トークンは必ず <literal>#</literal> 記号から始まることを思いだすと、 +<literal>$token</literal> の最初の数字 <literal>0x14B499C</literal> は、変換の結果 <literal>#</literal> +になるのではないかと予想される (なお、このことは、リポジトリの README +ファイルに追加ヒントとして書かれている)。</simpara> +</section> +<section xml:id="_解く"> + <title>解く</title> + <simpara>ここまでわかれば、あと一歩で解ける。すなわち、<literal>0x14B499C</literal> が <literal>#</literal> + に変換されるような <literal>N</literal> を見つければよい。</simpara> + <simpara><literal>N</literal> は高々</simpara> + <programlisting language="php" linenumbering="unnumbered">assert(0 <= N && N <= 0b11111_11111_11111_11111_11111);</programlisting> + <simpara>なのでブルートフォースしてもよいが、ここではブルートフォースしない方法を紹介する。</simpara> + <programlisting language="php" linenumbering="unnumbered"><?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" . + " # # ");</programlisting> +<simpara>この一連の変換に対する逆変換を考えると、次のようになる。</simpara> +<programlisting language="php" linenumbering="unnumbered"><?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";</programlisting> +<simpara>これを実行すると、<literal>N</literal> が得られる。</simpara> +</section> +</section> +<section xml:id="_第3問_toquine_php"> + <title>第3問 toquine.php</title> + <simpara>ソースコードはこちら。</simpara> + <programlisting language="php" linenumbering="unnumbered"><?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));</programlisting> +<simpara>コメントにもあるとおり、次のようにして実行すれば答えがでてくる。</simpara> +<programlisting language="shell-session" linenumbering="unnumbered">$ php toquine.php | php | php | php | ...</programlisting> +<simpara>実際にはもう少しパイプで繋げなければならない。</simpara> +<section xml:id="_解説_2"> + <title>解説</title> + <section xml:id="_プログラム全体_2"> + <title>プログラム全体</title> + <simpara>コメントにもあるとおり、これは quine (風) のプログラムになっている。 + Quine + とは、自分のソースコードをそっくりそのまま出力するようなプログラムのことである。</simpara> + <simpara>このプログラムは、実行すると自身とほとんど同じプログラムを出力する。 + 異なるのはトークンになっている部分のみである。</simpara> +</section> +<section xml:id="_トークン"> + <title>トークン</title> + <simpara><literal>$xs</literal> がトークンに対応している。変換のロジックは <literal>riddle.php</literal> + とほぼ同じなので省略する。</simpara> +</section> +<section xml:id="_状態保持"> + <title>状態保持</title> + <simpara>トークンの何文字目まで出力したかを、ソースコードを変えずに (quine + なので) 覚えておく必要がある。 + このプログラムでは、トークンが出力されるとソースコードがだんだんと長くなっていくのを利用して、<literal><emphasis>LINE</emphasis></literal> + から情報を取得している。</simpara> +</section> +<section xml:id="_rot_13"> + <title>ROT 13</title> + <simpara>Quine は、素朴に書くとプログラムの一部が 2回記述されてしまう。 + これがあまり美しくないので、<literal>toquine.php</literal> では、ROT 13 + 変換を使って難読化した。</simpara> +<simpara>それにしてもなぜこんなものが標準ライブラリに……。</simpara> +</section> +</section> +</section> +<section xml:id="_おわりに"> + <title>おわりに</title> + <simpara>解いていただいたみなさん、また、難易度調整につきあっていただいた社内のみなさん、ありがとうございました。</simpara> + <simpara>今回は直前に作りはじめたのもあり、3問だけかつ使い古されたネタばかりになってしまいましたが、 + 来年は 5問、より面白い問題を持っていきます。</simpara> +<simpara>実はもう作りはじめているので、どうか来年もありますように……。</simpara> +</section> +</article> diff --git a/content/posts/2022-04-24/term-banner-write-tool-showing-banner-in-terminal.adoc b/content/posts/2022-04-24/term-banner-write-tool-showing-banner-in-terminal.adoc deleted file mode 100644 index 409f498..0000000 --- a/content/posts/2022-04-24/term-banner-write-tool-showing-banner-in-terminal.adoc +++ /dev/null @@ -1,86 +0,0 @@ -= term-banner: ターミナルにバナーを表示するツールを書いた -:description: ターミナルに任意の文字のバナーを表示するためのツールを Go で書いた。 -:revision-1: 2022-04-24 公開 -:revision-2: 2022-04-27 -f オプションについて追記 - -== はじめに - -こんなものを作った。 - -.... -$ term-banner 'Hello, World!' 'こんにちは、' '世界!' -.... - -image::https://raw.githubusercontent.com/nsfisis/term-banner/main/screenshot.png[term-banner -のスクリーンショット] - -コマンドライン引数として渡した文字列をターミナルに大きく表示する。 - -リポジトリはこちら: https://github.com/nsfisis/term-banner - -== Motivation - -以前、https://github.com/nsfisis/big-clock-mode[big-clock-mode] -という似たようなプログラムを書いた。 これは tmux の `:clock-mode` -コマンドに着想を得たもので、`:clock-mode` -よりも大きく現在時刻を表示する。 - -`big-clock-mode` -を開発したのは、次のようなシチュエーションで使うためである。 -弊社では現在リモートワークが基本だが、web -会議などで画面共有しているときに、休憩を挟んで特定の時刻から再開する、ということがある。 -こういったケースで、画面上に現在の時刻を大きめに表示しておくと、モニタから離れても遠くから時刻がわかるので便利である。 - -それこそタイマアプリか何かを使えばいいのだが、ターミナルに棲むいきものとしては、住処から離れたくないわけだ。 - -しばらく便利に使っていたのだが、ひとつ不満点が出てきた。それは、再開する時刻がいつだったかを覚えておかなければならないということだ。 -どこかにメモしておいてもいいが、せっかくなら現在時刻とともに表示させておきたい。 - -そんなわけで、「任意の文字列をターミナルに表示する」プログラムを書く運びとなった。 -まあ、作らなくても探せばあると思うが、作りたいものは作りたいので知ったことではない。 - -== プログラム - -全体の流れは次のようになっている。 - -[arabic] -. フォントファイルを読み込む -. コマンドライン引数を Shift-JIS に変換する (フォントが Shift-JIS -基準で並んでいるため) -. 1文字ずつレンダリングしていく - -`big-clock-mode` が Go 製なので、今回も Go で書いた。 PNG -が標準ライブラリにあったり、Shift-JIS -のエンコーディングが準標準ライブラリにあったりしたのは助かった。 - -フォントファイルは `go:embed` -で実行ファイルに埋め込んでいるので、ビルド後はワンバイナリで動く。 -仕事ではスクリプト言語ばかり書いているが、やはりコンパイル言語はいい。 - -== フォント - -フリーの 8x8 -ビットマップフォントである、https://littlelimit.net/misaki.htm[美咲フォント -2021-05-05a 版] を使わせていただいた。 - -はじめは自分でポチポチ打っていたのだが、「き」くらいまでやって挫折した。 -同じく 8x8 -で作っていたのだが、平仮名でさえも、この小さなキャンバスにはとても収められない。 - -美咲フォントは、平仮名・片仮名に留まらず、JIS -第一・第二水準の漢字までサポートしている。 -第二水準ともなると一生お目にかかることのない字の方が多いくらいだが、これをこの大きさで書くというのは、もはや芸術の域である。 - -さらに言うと、実のところ美咲フォントは実サイズ 7x7 -で作られており、余白が設けられている。 -これは、単純にそのまま並べても字間・行間を確保できるようにという配慮である。 -おかげでコーディングまで楽になった。 - -ゴシック体と明朝体があったが、私の好みで明朝体の方にした。 -ただ、ゴシック体の方が見やすい気がするので、フォントを選べるように後ほど拡張するかもしれない。 - -2022-04-27 追記: `-f` オプションで選べるようにした。 - -== おわりに - -あなたもターミナルに住んでみませんか? diff --git a/content/posts/2022-04-24/term-banner-write-tool-showing-banner-in-terminal.xml b/content/posts/2022-04-24/term-banner-write-tool-showing-banner-in-terminal.xml new file mode 100644 index 0000000..e05b51f --- /dev/null +++ b/content/posts/2022-04-24/term-banner-write-tool-showing-banner-in-terminal.xml @@ -0,0 +1,90 @@ +<?xml version="1.0" encoding="UTF-8"?> +<article xmlns="http://docbook.org/ns/docbook" xmlns:xl="http://www.w3.org/1999/xlink" version="5.0"> + <info> + <title>term-banner: ターミナルにバナーを表示するツールを書いた</title> + <abstract> + ターミナルに任意の文字のバナーを表示するためのツールを Go で書いた。 + </abstract> + <revhistory> + <revision> + <date>2022-04-24</date> + <revremark>公開</revremark> + </revision> + <revision> + <date>2022-04-27</date> + <revremark>-f オプションについて追記</revremark> + </revision> + </revhistory> + </info> + <section xml:id="_はじめに"> + <title>はじめに</title> + <simpara>こんなものを作った。</simpara> + <literallayout class="monospaced">$ term-banner 'Hello, World!' 'こんにちは、' '世界!'</literallayout> + <simpara>image::https://raw.githubusercontent.com/nsfisis/term-banner/main/screenshot.png[term-banner + のスクリーンショット]</simpara> + <simpara>コマンドライン引数として渡した文字列をターミナルに大きく表示する。</simpara> + <simpara>リポジトリはこちら: <link xl:href="https://github.com/nsfisis/term-banner">https://github.com/nsfisis/term-banner</link></simpara> +</section> +<section xml:id="_motivation"> + <title>Motivation</title> + <simpara>以前、https://github.com/nsfisis/big-clock-mode[big-clock-mode] + という似たようなプログラムを書いた。 これは tmux の <literal>:clock-mode</literal> + コマンドに着想を得たもので、<literal>:clock-mode</literal> + よりも大きく現在時刻を表示する。</simpara> +<simpara><literal>big-clock-mode</literal> + を開発したのは、次のようなシチュエーションで使うためである。 + 弊社では現在リモートワークが基本だが、web + 会議などで画面共有しているときに、休憩を挟んで特定の時刻から再開する、ということがある。 + こういったケースで、画面上に現在の時刻を大きめに表示しておくと、モニタから離れても遠くから時刻がわかるので便利である。</simpara> +<simpara>それこそタイマアプリか何かを使えばいいのだが、ターミナルに棲むいきものとしては、住処から離れたくないわけだ。</simpara> +<simpara>しばらく便利に使っていたのだが、ひとつ不満点が出てきた。それは、再開する時刻がいつだったかを覚えておかなければならないということだ。 +どこかにメモしておいてもいいが、せっかくなら現在時刻とともに表示させておきたい。</simpara> +<simpara>そんなわけで、「任意の文字列をターミナルに表示する」プログラムを書く運びとなった。 +まあ、作らなくても探せばあると思うが、作りたいものは作りたいので知ったことではない。</simpara> +</section> +<section xml:id="_プログラム"> + <title>プログラム</title> + <simpara>全体の流れは次のようになっている。</simpara> + <orderedlist numeration="arabic"> + <listitem> + <simpara>フォントファイルを読み込む</simpara> + </listitem> + <listitem> + <simpara>コマンドライン引数を Shift-JIS に変換する (フォントが Shift-JIS + 基準で並んでいるため)</simpara> + </listitem> + <listitem> + <simpara>1文字ずつレンダリングしていく</simpara> + </listitem> +</orderedlist> +<simpara><literal>big-clock-mode</literal> が Go 製なので、今回も Go で書いた。 PNG +が標準ライブラリにあったり、Shift-JIS +のエンコーディングが準標準ライブラリにあったりしたのは助かった。</simpara> +<simpara>フォントファイルは <literal>go:embed</literal> + で実行ファイルに埋め込んでいるので、ビルド後はワンバイナリで動く。 + 仕事ではスクリプト言語ばかり書いているが、やはりコンパイル言語はいい。</simpara> +</section> +<section xml:id="_フォント"> + <title>フォント</title> + <simpara>フリーの 8x8 + ビットマップフォントである、https://littlelimit.net/misaki.htm[美咲フォント + 2021-05-05a 版] を使わせていただいた。</simpara> +<simpara>はじめは自分でポチポチ打っていたのだが、「き」くらいまでやって挫折した。 +同じく 8x8 +で作っていたのだが、平仮名でさえも、この小さなキャンバスにはとても収められない。</simpara> +<simpara>美咲フォントは、平仮名・片仮名に留まらず、JIS +第一・第二水準の漢字までサポートしている。 +第二水準ともなると一生お目にかかることのない字の方が多いくらいだが、これをこの大きさで書くというのは、もはや芸術の域である。</simpara> +<simpara>さらに言うと、実のところ美咲フォントは実サイズ 7x7 +で作られており、余白が設けられている。 +これは、単純にそのまま並べても字間・行間を確保できるようにという配慮である。 +おかげでコーディングまで楽になった。</simpara> +<simpara>ゴシック体と明朝体があったが、私の好みで明朝体の方にした。 +ただ、ゴシック体の方が見やすい気がするので、フォントを選べるように後ほど拡張するかもしれない。</simpara> +<simpara>2022-04-27 追記: <literal>-f</literal> オプションで選べるようにした。</simpara> +</section> +<section xml:id="_おわりに"> + <title>おわりに</title> + <simpara>あなたもターミナルに住んでみませんか?</simpara> +</section> +</article> diff --git a/content/posts/2022-05-01/phperkaigi-2022.adoc b/content/posts/2022-05-01/phperkaigi-2022.adoc deleted file mode 100644 index 863e30b..0000000 --- a/content/posts/2022-05-01/phperkaigi-2022.adoc +++ /dev/null @@ -1,130 +0,0 @@ -= PHPerKaigi 2022 -:tags: conference, php, phperkaigi -:description: 2022-04-09 から 2022-04-11 にかけて開催された、PHPerKaigi 2022 に参加した。 -:revision-1: 2022-05-01 公開 - -== はじめに - -2022-04-09 から 2022-04-11 -にかけて開催された、https://phperkaigi.jp/2022/[PHPerKaigi 2022] -に、一般参加者として参加した。 -弊社https://www.dgcircus.com/[デジタルサーカス株式会社] -はダイヤモンドスポンサーとなっており、スポンサー枠のチケットを使わせていただいた。 - -昨年のレポートはlink:/posts/2021-03-30/phperkaigi-2021[こちら]。 - -== 感想 - -=== 厳選おすすめトーク - -多くの素晴らしいトークの中から、特におすすめのものを -5つ選んだ。是非聞いてほしい。引用部分は、リンク先プロポーザルから引用している。 - -https://fortee.jp/phperkaigi-2022/proposal/ef8cf4ed-63fe-42f8-8145-b3e70054458b[予防に勝る防御なし -- 堅牢なコードを導く様々な設計のヒント] - -____ -PHP -はバージョンを追う毎に型宣言、例外、表明、列挙型などの機能が大幅に強化され、堅牢なコードを書くための機能が充実してきました。それらの機能はどう使うと効果的なのでしょうか。 - -本講演では PHP 8.1 -をベースにして、誤りを想定してチェックするのではなく、そもそも誤りにくい設計とはどのようなものか、つまり「予防」の観点を軸足に、堅牢なコードを導くための様々な設計のヒントをご紹介します。 -____ - -https://fortee.jp/phperkaigi-2022/proposal/db00d49e-0dd6-453f-b54b-f731d112f10e[PHPのエラーを理解して適切なエラーハンドリングを学ぼう] - -____ -PHPを使ってるとよく遭遇する Fatal error / Parse error / Warning / Notice -理解していますか? + -これらのエラー文を理解することで、すぐにエラーの原因に気付き適切に対象できる様になります! + -またそれらを理解した上でのエラーハンドリングを学びましょう。 -____ - -https://fortee.jp/phperkaigi-2022/proposal/4a7e3ded-9134-4919-955c-ec7bf4491c0d[エラー監視とテスト体制への改善作戦] - -____ -毎日流れてくるエラーに皆さんはどう向き合ってますか? + -エラーを出さない事が一番ですが、完全に塞ぐ事は難しいと考えます。 + -サービス運用の中で本番環境から発生するエラー(サーバー・クライアントサイド・サードパーティ起因のエラー)への監視体制と、 + -エラー・バグ防御のためチームで行っているテストコード文化づくりの話をします。 -____ - -https://fortee.jp/phperkaigi-2022/proposal/6f47daf8-c78f-4fb1-9b99-e9656e6fe7f7[ISUCON11のPHP実装は、何を考え、どのようにして作られていたのか] - -____ -昨年開催されたISUCON11にて問題(参考実装)のPHPへの移植を担当させていただきました。 - -最終的なソースコードこそシンプルなWebアプリケーションではありますが、その裏には + -・「(私の思う)良い設計」を実現するための意思決定 + -・「ISUCONの問題」という位置付けに由来する取捨選択 + -・移植中に遭遇したトラブルとその解決策 + -といった文脈や葛藤が存在しています。 - -本発表はそれらを共有することで + -・PHPアプリケーションの設計、実装事例として役立ててもらう + -・ISUCONの言語移植に興味を持ってもらう + -・ISUCON問題移植の「実装や設計の練習をする教材」としての可能性を知ってもらう + -ことを目的とします。 -____ - -https://fortee.jp/phperkaigi-2022/proposal/5a260e4e-542d-4d82-849d-ef3d6cb7c854[チームの仕事はまわっていたけど、メンバーはそれぞれモヤモヤを抱えていた話──40名の大規模開発チームで1on1ログを公開してみた] - -____ -サイボウズの大企業向けグループウェアのGaroon(ガルーン)は、PHPで開発されている20年目の製品です。ガルーン開発チームは日本で40名、ベトナムで50名の計90名ほどのチームになっています。また、コロナ禍でフルリモートでの活動がこの2年ほど継続してきました。 - -フルリモートになっても仕事はまわっており、継続的にリリースはしていましたが、一方でお互いの考えていることや感じている問題意識が見えづらくなり、モヤモヤを抱えているメンバーが増えていました。 - -このセッションでは、そういう状況で私がチーム外からジョインし、聴き役に徹しながら見える化することで状況を改善していった取り組みを紹介します。同じように大きなチームやリモートワークで難しさを感じている人に、難しさの原因への気づきや取り組みへのヒントがあれば幸いです。 -____ - -=== トークン問題の作成 - -今回は、PHPer チャレンジ用に弊社のトークン問題を -3題作成した。こちらについてはlink:/posts/2022-04-09/phperkaigi-2022-tokens[別途記事にしている]ので、そちらを参照されたい。 - -=== PHPer チャレンジ - -https://fortee.jp/phperkaigi-2022/challenge[1位]になった。 + -また、賞品として https://www.amazon.co.jp/dp/B08MQNJC9Z[Echo Show 15] -をいただいた。 - -=== カンファレンス全体への感想 - -link:/posts/2021-03-30/phperkaigi-2021[去年の参加レポ] -では、こんなことを書いた。 - -____ -1つ個人的な反省点としては、(中略) Discord -しかりアンカンファレンスしかり「このイベントのこの瞬間にしかないコンテンツ」に触れずに、後から見返せる発表やスライドに注力してしまった、ということだ。発表の詳細な見直しはあとからできるのだから、今しかできないことを考えるべきだった。 + -まあ初カンファレンスだし、とお茶を濁しておこう。 -____ - -この反省を踏まえ、今年は積極的にほかの場 (公式の Discord -サーバや、アンカンファレンス) にも参加した。 + -これにより、参加体験の質がはるかに向上した。特に Discord -に関しては、登壇者ご本人による補足や、質問への回答などがおこなわれる -(ことが多い) -ため、特別な理由のない限り、発言はしないまでも参加はしておいたほうが良いと思われる。 - -なお、アンカンファレンスについては、1日目の終わりにhttps://fortee.jp/phperkaigi-2022/unconference/view/d332797a-8921-4706-a7e2-ee72640c9b5e[トークン問題の解説放送]もおこなった。 - -また、今年はオフラインとオンラインのハイブリッド開催であったが、去年の全オンラインと比べて、オンライン参加の体験が落ちていなかったのは、特筆すべきであろう。 -今年は -3回目のワクチン接種が間に合わなかったこともあり現地参加は見送ったのだが、来年は是非オフラインで参加したい。 - -== そして来年へ……? - -PHPerKaigi 2023 があるかどうか存じ上げないが、あるとすれば、次の -4つを目標としたい。 - -* プロポーザルを出す -* PHPer チャレンジのトークン問題を 5題作成する -* 現地に行く -* PHPer チャレンジで圧勝する - -''''' - -最後になりましたが、PHPerKaigi -のスタッフ、スポンサー、スピーカーのみなさん、素敵な時間をありがとうございました。 - -ではまた来年。 diff --git a/content/posts/2022-05-01/phperkaigi-2022.xml b/content/posts/2022-05-01/phperkaigi-2022.xml new file mode 100644 index 0000000..1ef2a29 --- /dev/null +++ b/content/posts/2022-05-01/phperkaigi-2022.xml @@ -0,0 +1,133 @@ +<?xml version="1.0" encoding="UTF-8"?> +<article xmlns="http://docbook.org/ns/docbook" xmlns:xl="http://www.w3.org/1999/xlink" version="5.0"> + <info> + <title>PHPerKaigi 2022</title> + <abstract> + 2022-04-09 から 2022-04-11 にかけて開催された、PHPerKaigi 2022 に参加した。 + </abstract> + <keywordset> + <keyword>conference</keyword> + <keyword>php</keyword> + <keyword>phperkaigi</keyword> + </keywordset> + <revhistory> + <revision> + <date>2022-05-01</date> + <revremark>公開</revremark> + </revision> + </revhistory> + </info> + <section xml:id="_はじめに"> + <title>はじめに</title> + <simpara>2022-04-09 から 2022-04-11 + にかけて開催された、https://phperkaigi.jp/2022/[PHPerKaigi 2022] + に、一般参加者として参加した。 + 弊社https://www.dgcircus.com/[デジタルサーカス株式会社] + はダイヤモンドスポンサーとなっており、スポンサー枠のチケットを使わせていただいた。</simpara> + <simpara>昨年のレポートは<link xl:href="/posts/2021-03-30/phperkaigi-2021">こちら</link>。</simpara> +</section> +<section xml:id="_感想"> + <title>感想</title> + <section xml:id="_厳選おすすめトーク"> + <title>厳選おすすめトーク</title> + <simpara>多くの素晴らしいトークの中から、特におすすめのものを + 5つ選んだ。是非聞いてほしい。引用部分は、リンク先プロポーザルから引用している。</simpara> + <simpara><link xl:href="https://fortee.jp/phperkaigi-2022/proposal/ef8cf4ed-63fe-42f8-8145-b3e70054458b">予防に勝る防御なし + - 堅牢なコードを導く様々な設計のヒント</link></simpara> +<blockquote> + <simpara>PHP + はバージョンを追う毎に型宣言、例外、表明、列挙型などの機能が大幅に強化され、堅牢なコードを書くための機能が充実してきました。それらの機能はどう使うと効果的なのでしょうか。</simpara> +<simpara>本講演では PHP 8.1 +をベースにして、誤りを想定してチェックするのではなく、そもそも誤りにくい設計とはどのようなものか、つまり「予防」の観点を軸足に、堅牢なコードを導くための様々な設計のヒントをご紹介します。</simpara> +</blockquote> +<simpara><link xl:href="https://fortee.jp/phperkaigi-2022/proposal/db00d49e-0dd6-453f-b54b-f731d112f10e">PHPのエラーを理解して適切なエラーハンドリングを学ぼう</link></simpara> +<blockquote> + <simpara>PHPを使ってるとよく遭遇する Fatal error / Parse error / Warning / Notice + 理解していますか?<br/> + これらのエラー文を理解することで、すぐにエラーの原因に気付き適切に対象できる様になります!<br/> + またそれらを理解した上でのエラーハンドリングを学びましょう。</simpara> +</blockquote> +<simpara><link xl:href="https://fortee.jp/phperkaigi-2022/proposal/4a7e3ded-9134-4919-955c-ec7bf4491c0d">エラー監視とテスト体制への改善作戦</link></simpara> +<blockquote> + <simpara>毎日流れてくるエラーに皆さんはどう向き合ってますか?<br/> + エラーを出さない事が一番ですが、完全に塞ぐ事は難しいと考えます。<br/> + サービス運用の中で本番環境から発生するエラー(サーバー・クライアントサイド・サードパーティ起因のエラー)への監視体制と、<br/> + エラー・バグ防御のためチームで行っているテストコード文化づくりの話をします。</simpara> +</blockquote> +<simpara><link xl:href="https://fortee.jp/phperkaigi-2022/proposal/6f47daf8-c78f-4fb1-9b99-e9656e6fe7f7">ISUCON11のPHP実装は、何を考え、どのようにして作られていたのか</link></simpara> +<blockquote> + <simpara>昨年開催されたISUCON11にて問題(参考実装)のPHPへの移植を担当させていただきました。</simpara> + <simpara>最終的なソースコードこそシンプルなWebアプリケーションではありますが、その裏には<br/> + ・「(私の思う)良い設計」を実現するための意思決定<br/> + ・「ISUCONの問題」という位置付けに由来する取捨選択<br/> + ・移植中に遭遇したトラブルとその解決策<br/> + といった文脈や葛藤が存在しています。</simpara> + <simpara>本発表はそれらを共有することで<br/> + ・PHPアプリケーションの設計、実装事例として役立ててもらう<br/> + ・ISUCONの言語移植に興味を持ってもらう<br/> + ・ISUCON問題移植の「実装や設計の練習をする教材」としての可能性を知ってもらう<br/> + ことを目的とします。</simpara> +</blockquote> +<simpara><link xl:href="https://fortee.jp/phperkaigi-2022/proposal/5a260e4e-542d-4d82-849d-ef3d6cb7c854">チームの仕事はまわっていたけど、メンバーはそれぞれモヤモヤを抱えていた話──40名の大規模開発チームで1on1ログを公開してみた</link></simpara> +<blockquote> + <simpara>サイボウズの大企業向けグループウェアのGaroon(ガルーン)は、PHPで開発されている20年目の製品です。ガルーン開発チームは日本で40名、ベトナムで50名の計90名ほどのチームになっています。また、コロナ禍でフルリモートでの活動がこの2年ほど継続してきました。</simpara> + <simpara>フルリモートになっても仕事はまわっており、継続的にリリースはしていましたが、一方でお互いの考えていることや感じている問題意識が見えづらくなり、モヤモヤを抱えているメンバーが増えていました。</simpara> + <simpara>このセッションでは、そういう状況で私がチーム外からジョインし、聴き役に徹しながら見える化することで状況を改善していった取り組みを紹介します。同じように大きなチームやリモートワークで難しさを感じている人に、難しさの原因への気づきや取り組みへのヒントがあれば幸いです。</simpara> +</blockquote> +</section> +<section xml:id="_トークン問題の作成"> + <title>トークン問題の作成</title> + <simpara>今回は、PHPer チャレンジ用に弊社のトークン問題を + 3題作成した。こちらについては<link xl:href="/posts/2022-04-09/phperkaigi-2022-tokens">別途記事にしている</link>ので、そちらを参照されたい。</simpara> +</section> +<section xml:id="_phper_チャレンジ"> + <title>PHPer チャレンジ</title> + <simpara><link xl:href="https://fortee.jp/phperkaigi-2022/challenge">1位</link>になった。<br/> + また、賞品として <link xl:href="https://www.amazon.co.jp/dp/B08MQNJC9Z">Echo Show 15</link> + をいただいた。</simpara> +</section> +<section xml:id="_カンファレンス全体への感想"> + <title>カンファレンス全体への感想</title> + <simpara><link xl:href="/posts/2021-03-30/phperkaigi-2021">去年の参加レポ</link> + では、こんなことを書いた。</simpara> + <blockquote> + <simpara>1つ個人的な反省点としては、(中略) Discord + しかりアンカンファレンスしかり「このイベントのこの瞬間にしかないコンテンツ」に触れずに、後から見返せる発表やスライドに注力してしまった、ということだ。発表の詳細な見直しはあとからできるのだから、今しかできないことを考えるべきだった。<br/> + まあ初カンファレンスだし、とお茶を濁しておこう。</simpara> +</blockquote> +<simpara>この反省を踏まえ、今年は積極的にほかの場 (公式の Discord +サーバや、アンカンファレンス) にも参加した。<br/> +これにより、参加体験の質がはるかに向上した。特に Discord +に関しては、登壇者ご本人による補足や、質問への回答などがおこなわれる +(ことが多い) +ため、特別な理由のない限り、発言はしないまでも参加はしておいたほうが良いと思われる。</simpara> +<simpara>なお、アンカンファレンスについては、1日目の終わりにhttps://fortee.jp/phperkaigi-2022/unconference/view/d332797a-8921-4706-a7e2-ee72640c9b5e[トークン問題の解説放送]もおこなった。</simpara> +<simpara>また、今年はオフラインとオンラインのハイブリッド開催であったが、去年の全オンラインと比べて、オンライン参加の体験が落ちていなかったのは、特筆すべきであろう。 +今年は +3回目のワクチン接種が間に合わなかったこともあり現地参加は見送ったのだが、来年は是非オフラインで参加したい。</simpara> +</section> +</section> +<section xml:id="_そして来年へ"> + <title>そして来年へ……?</title> + <simpara>PHPerKaigi 2023 があるかどうか存じ上げないが、あるとすれば、次の + 4つを目標としたい。</simpara> +<itemizedlist> + <listitem> + <simpara>プロポーザルを出す</simpara> + </listitem> + <listitem> + <simpara>PHPer チャレンジのトークン問題を 5題作成する</simpara> + </listitem> + <listitem> + <simpara>現地に行く</simpara> + </listitem> + <listitem> + <simpara>PHPer チャレンジで圧勝する</simpara> + </listitem> +</itemizedlist> +<simpara><hr/></simpara> +<simpara>最後になりましたが、PHPerKaigi +のスタッフ、スポンサー、スピーカーのみなさん、素敵な時間をありがとうございました。</simpara> +<simpara>ではまた来年。</simpara> +</section> +</article> diff --git a/content/posts/2022-08-27/php-conference-okinawa-code-golf.adoc b/content/posts/2022-08-27/php-conference-okinawa-code-golf.adoc deleted file mode 100644 index 86e3080..0000000 --- a/content/posts/2022-08-27/php-conference-okinawa-code-golf.adoc +++ /dev/null @@ -1,91 +0,0 @@ -= PHP カンファレンス沖縄で出題されたコードゴルフの問題を解いてみた -:tags: conference, php, phpcon -:description: PHP カンファレンス沖縄の懇親会 LT で出題されたコードゴルフの問題を解いてみた。 -:revision-1: 2022-08-27 公開 - -== はじめに - -本日 https://phpcon.okinawa.jp/[PHP カンファレンス沖縄 2022] -が開催された (らしい)。 - -カンファレンスには参加できなかったものの、懇親会の LT -で出題されたコードゴルフの問題が Twitter に流れてきたので、解いてみた。 - -ツイート: https://twitter.com/m3m0r7/status/1563397620231712772 + -スライド: -https://speakerdeck.com/memory1994/php-conference-okinawa-2022-extra?slide=3 - -== 解 - -細かいレギュレーションは不明だったので、勝手に定めた。 - -* コマンドライン引数の第1引数で受けとる -* 結果は標準出力に出す -* コンマの直後にはスペースを1つ置く -* 末尾コンマは禁止 -* 数字でないものは入ってこないものとする -* 負数は入ってこないものとする - -書いたものがこちら: - -[source,php] ----- -[<?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 バイトとなった (末尾改行を含めずにカウント)。 - -こちらは改行とスペースを追加したバージョン: - -[source,php] ----- -[<?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 ?? []); - -?>] ----- - -== 使用したテクニック - -=== 指数表記 - -割と多くの言語のゴルフで使えるテクニック。`e` -を用いた指数表記で、大きな数を短く表す。このコードでは -`10000`、`5000`、`2000`、`1000` を指数表記している。 - -=== foreach や for の中身を1つの文に - -`foreach`、`for`、`if` などの後ろには、通常 `{` -を続けて複数の文を連ねるが、中身の文を1つにしてしまえば、`{` と `}` -を省略できる。C言語などでも使える。 - -=== $r に初期値を入れない - -PHP では、`$r[] = ...` -のような配列の末尾に追加する式を実行したとき、`$r` が未定義だった場合は -`$r` -を勝手に定義して空の配列で初期化してくれる。これを利用すると、`$r = [];` -のような初期化が不要になる。 - -ただし、プログラムに 0 が渡されるとループを一度も回らないので、`$r` -が未定義になってしまい、`implode()` -に渡すところでエラーになる。それを防ぐために `$r ?? []` を使っている。 - -もし 0 が渡されたケースを無視するなら、これが不要になるので 4 -バイト縮む。 - -=== PHP タグの外に文字列を置く - -PHP では、`<?php` `?>` -で囲われた部分の外側にある文字列は、そのまま出力される。今回のケースでは、先頭と末尾に必ず -`[` と `]` を出力するので、そのまま書いてやればよい。 - -== おわりに - -最後になりましたが、https://twitter.com/m3m0r7[めもりー] -さん、楽しい問題をありがとうございました。 diff --git a/content/posts/2022-08-27/php-conference-okinawa-code-golf.xml b/content/posts/2022-08-27/php-conference-okinawa-code-golf.xml new file mode 100644 index 0000000..6c9305b --- /dev/null +++ b/content/posts/2022-08-27/php-conference-okinawa-code-golf.xml @@ -0,0 +1,106 @@ +<?xml version="1.0" encoding="UTF-8"?> +<article xmlns="http://docbook.org/ns/docbook" xmlns:xl="http://www.w3.org/1999/xlink" version="5.0"> + <info> + <title>PHP カンファレンス沖縄で出題されたコードゴルフの問題を解いてみた</title> + <abstract> + PHP カンファレンス沖縄の懇親会 LT で出題されたコードゴルフの問題を解いてみた。 + </abstract> + <keywordset> + <keyword>conference</keyword> + <keyword>php</keyword> + <keyword>phpcon</keyword> + </keywordset> + <revhistory> + <revision> + <date>2022-08-27</date> + <revremark>公開</revremark> + </revision> + </revhistory> + </info> + <section xml:id="_はじめに"> + <title>はじめに</title> + <simpara>本日 <link xl:href="https://phpcon.okinawa.jp/">PHP カンファレンス沖縄 2022</link> + が開催された (らしい)。</simpara> + <simpara>カンファレンスには参加できなかったものの、懇親会の LT + で出題されたコードゴルフの問題が Twitter に流れてきたので、解いてみた。</simpara> + <simpara>ツイート: <link xl:href="https://twitter.com/m3m0r7/status/1563397620231712772">https://twitter.com/m3m0r7/status/1563397620231712772</link><br/> + スライド: + <link xl:href="https://speakerdeck.com/memory1994/php-conference-okinawa-2022-extra?slide=3">https://speakerdeck.com/memory1994/php-conference-okinawa-2022-extra?slide=3</link></simpara> +</section> +<section xml:id="_解"> + <title>解</title> + <simpara>細かいレギュレーションは不明だったので、勝手に定めた。</simpara> + <itemizedlist> + <listitem> + <simpara>コマンドライン引数の第1引数で受けとる</simpara> + </listitem> + <listitem> + <simpara>結果は標準出力に出す</simpara> + </listitem> + <listitem> + <simpara>コンマの直後にはスペースを1つ置く</simpara> + </listitem> + <listitem> + <simpara>末尾コンマは禁止</simpara> + </listitem> + <listitem> + <simpara>数字でないものは入ってこないものとする</simpara> + </listitem> + <listitem> + <simpara>負数は入ってこないものとする</simpara> + </listitem> + </itemizedlist> + <simpara>書いたものがこちら:</simpara> + <programlisting language="php" linenumbering="unnumbered">[<?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??[]);?>]</programlisting> + <simpara>しめて 123 バイトとなった (末尾改行を含めずにカウント)。</simpara> + <simpara>こちらは改行とスペースを追加したバージョン:</simpara> + <programlisting language="php" linenumbering="unnumbered">[<?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 ?? []); + + ?>]</programlisting> +</section> +<section xml:id="_使用したテクニック"> + <title>使用したテクニック</title> + <section xml:id="_指数表記"> + <title>指数表記</title> + <simpara>割と多くの言語のゴルフで使えるテクニック。<literal>e</literal> + を用いた指数表記で、大きな数を短く表す。このコードでは + <literal>10000</literal>、<literal>5000</literal>、<literal>2000</literal>、<literal>1000</literal> を指数表記している。</simpara> + </section> + <section xml:id="_foreach_や_for_の中身を1つの文に"> + <title>foreach や for の中身を1つの文に</title> + <simpara><literal>foreach</literal>、<literal>for</literal>、<literal>if</literal> などの後ろには、通常 <literal>{</literal> + を続けて複数の文を連ねるが、中身の文を1つにしてしまえば、<literal>{</literal> と <literal>}</literal> + を省略できる。C言語などでも使える。</simpara> + </section> + <section xml:id="_r_に初期値を入れない"> + <title>$r に初期値を入れない</title> + <simpara>PHP では、<literal>$r[] = …​</literal> + のような配列の末尾に追加する式を実行したとき、<literal>$r</literal> が未定義だった場合は + <literal>$r</literal> + を勝手に定義して空の配列で初期化してくれる。これを利用すると、<literal>$r = [];</literal> + のような初期化が不要になる。</simpara> + <simpara>ただし、プログラムに 0 が渡されるとループを一度も回らないので、<literal>$r</literal> + が未定義になってしまい、<literal>implode()</literal> + に渡すところでエラーになる。それを防ぐために <literal>$r ?? []</literal> を使っている。</simpara> + <simpara>もし 0 が渡されたケースを無視するなら、これが不要になるので 4 + バイト縮む。</simpara> +</section> +<section xml:id="_php_タグの外に文字列を置く"> + <title>PHP タグの外に文字列を置く</title> + <simpara>PHP では、<literal><?php</literal> <literal>?></literal> + で囲われた部分の外側にある文字列は、そのまま出力される。今回のケースでは、先頭と末尾に必ず + <literal>[</literal> と <literal>]</literal> を出力するので、そのまま書いてやればよい。</simpara> +</section> +</section> +<section xml:id="_おわりに"> + <title>おわりに</title> + <simpara>最後になりましたが、https://twitter.com/m3m0r7[めもりー] + さん、楽しい問題をありがとうございました。</simpara> +</section> +</article> diff --git a/content/posts/2022-08-31/support-for-communty-is-employee-benefits.adoc b/content/posts/2022-08-31/support-for-communty-is-employee-benefits.adoc deleted file mode 100644 index ba24d8c..0000000 --- a/content/posts/2022-08-31/support-for-communty-is-employee-benefits.adoc +++ /dev/null @@ -1,46 +0,0 @@ -= 弊社の PHP Foundation への寄付に寄せて -:description: 先日、私の勤めるデジタルサーカス株式会社が、PHP Foundation へ寄付をおこないました。 \ - 本件を社内でしつこく推進した1人として、推進の理由等を書き残しておきます。 -:revision-1: 2022-08-31 公開 - -== はじめに - -*注: -これは私個人の意見であり、所属する組織を代表するものではありません。* - -先日、私の勤める https://www.dgcircus.com/[デジタルサーカス株式会社] が -https://opencollective.com/phpfoundation[PHP Foundation] へ $2,000 -の寄付をおこないました。 - -記事: https://www.dgcircus.com/news/581 - -本件を社内でしつこく推進した1人として、推進の理由等を書き残しておきます。 - -== なぜ? - -組織としての寄付理由は前掲した記事に譲るとして、ここでは、私が社内でこの件を推進した理由について書くことにします。 - -当時の考えを端的にまとめた社内チャットの投稿があったので、それを引用します: - -____ -結局これを通したい (私の中での) -最大の理由が、「自分の勤める会社が、これをやる会社であってほしい」というのがあり、↑にしても、感情ベースの理由しか出せていないというのが説得力に欠けている理由なのだと思いますが、寄付の報告が流れてきたり、OSS -のフリーライドの話が流れてきたりするたびに、自尊心が毀損される、というか -(これは大袈裟すぎる表現で、実際にはそこまで明確に傷ついているわけではありませんが)。 - -追記: 「肩身が狭くなる」というのがより適切でした。 -____ - -※文中の「↑にしても」は、ここに載せていない別の投稿を指しています。 - -OSS を金銭的に支援したり、技術カンファレンスへ協賛したり (あるいは -https://twitter.com/tomzoh[CTO] がカンファレンスを年2で主催したり: -https://iosdc.jp[iOSDC] https://phperkaigi.jp[PHPerKaigi]) -といった行為は、コミュニティへの貢献であると同時に、社員に対する精神的福利厚生でもあると言えるでしょう -(知らんけど)。これらは、技術や技術者を大切にする組織である、ということの、対外的にも対内的にも強力なメッセージなのです。 - -以上が、私が社内で寄付の件を進めた (かなり私的な) 理由です。 - -== おわりに - -最終的に社としての寄付まで漕ぎ着けられたのは、もちろん私の力ではなく役員の方々の決定によるものです。この場を借りて感謝申し上げます。 diff --git a/content/posts/2022-08-31/support-for-communty-is-employee-benefits.xml b/content/posts/2022-08-31/support-for-communty-is-employee-benefits.xml new file mode 100644 index 0000000..36db0a6 --- /dev/null +++ b/content/posts/2022-08-31/support-for-communty-is-employee-benefits.xml @@ -0,0 +1,48 @@ +<?xml version="1.0" encoding="UTF-8"?> +<article xmlns="http://docbook.org/ns/docbook" xmlns:xl="http://www.w3.org/1999/xlink" version="5.0"> + <info> + <title>弊社の PHP Foundation への寄付に寄せて</title> + <abstract> + 先日、私の勤めるデジタルサーカス株式会社が、PHP Foundation へ寄付をおこないました。本件を社内でしつこく推進した1人として、推進の理由等を書き残しておきます。 + </abstract> + <revhistory> + <revision> + <date>2022-08-31</date> + <revremark>公開</revremark> + </revision> + </revhistory> + </info> + <section xml:id="_はじめに"> + <title>はじめに</title> + <simpara><emphasis role="strong">注: + これは私個人の意見であり、所属する組織を代表するものではありません。</emphasis></simpara> +<simpara>先日、私の勤める <link xl:href="https://www.dgcircus.com/">デジタルサーカス株式会社</link> が +<link xl:href="https://opencollective.com/phpfoundation">PHP Foundation</link> へ $2,000 +の寄付をおこないました。</simpara> +<simpara>記事: <link xl:href="https://www.dgcircus.com/news/581">https://www.dgcircus.com/news/581</link></simpara> +<simpara>本件を社内でしつこく推進した1人として、推進の理由等を書き残しておきます。</simpara> +</section> +<section xml:id="_なぜ"> + <title>なぜ?</title> + <simpara>組織としての寄付理由は前掲した記事に譲るとして、ここでは、私が社内でこの件を推進した理由について書くことにします。</simpara> + <simpara>当時の考えを端的にまとめた社内チャットの投稿があったので、それを引用します:</simpara> + <blockquote> + <simpara>結局これを通したい (私の中での) + 最大の理由が、「自分の勤める会社が、これをやる会社であってほしい」というのがあり、↑にしても、感情ベースの理由しか出せていないというのが説得力に欠けている理由なのだと思いますが、寄付の報告が流れてきたり、OSS + のフリーライドの話が流れてきたりするたびに、自尊心が毀損される、というか + (これは大袈裟すぎる表現で、実際にはそこまで明確に傷ついているわけではありませんが)。</simpara> + <simpara>追記: 「肩身が狭くなる」というのがより適切でした。</simpara> +</blockquote> +<simpara>※文中の「↑にしても」は、ここに載せていない別の投稿を指しています。</simpara> +<simpara>OSS を金銭的に支援したり、技術カンファレンスへ協賛したり (あるいは +<link xl:href="https://twitter.com/tomzoh">CTO</link> がカンファレンスを年2で主催したり: +<link xl:href="https://iosdc.jp">iOSDC</link> <link xl:href="https://phperkaigi.jp">PHPerKaigi</link>) +といった行為は、コミュニティへの貢献であると同時に、社員に対する精神的福利厚生でもあると言えるでしょう +(知らんけど)。これらは、技術や技術者を大切にする組織である、ということの、対外的にも対内的にも強力なメッセージなのです。</simpara> +<simpara>以上が、私が社内で寄付の件を進めた (かなり私的な) 理由です。</simpara> +</section> +<section xml:id="_おわりに"> + <title>おわりに</title> + <simpara>最終的に社としての寄付まで漕ぎ着けられたのは、もちろん私の力ではなく役員の方々の決定によるものです。この場を借りて感謝申し上げます。</simpara> +</section> +</article> diff --git a/content/posts/2022-09-29/write-fizzbuzz-in-php-2-letters-per-line.adoc b/content/posts/2022-09-29/write-fizzbuzz-in-php-2-letters-per-line.adoc deleted file mode 100644 index 247f9c1..0000000 --- a/content/posts/2022-09-29/write-fizzbuzz-in-php-2-letters-per-line.adoc +++ /dev/null @@ -1,624 +0,0 @@ -= [PHP] fizzbuzz を書く。1行あたり2文字で。 -:tags: php -:description: PHP で、fizzbuzz を書いた。ただし、1行あたりに使える文字数は2文字まで。 -:revision-1: 2022-09-28 公開 -:revision-2: 2022-09-29 小さな文言の修正・変更 - -== 記事の構成について - -この記事は、普通の fizzbuzz -を徐々に変形して最終形にしていく、という構成で書かれている。最終形を見てどのような仕組みで動いているのか解読してから解説を読みたい、というかたがいれば、 -https://gist.github.com/nsfisis/04c227d5a419867472a0b23a83ad2919#file-fizzbuzz-php-2-letters-per-line-and-supports-php-8-x-without-warnings[このページ] -にソースコードがあるので、そちらを先に見てほしい。 - -== レギュレーション - -PHP で、次のような制約の下に fizzbuzz を書いた。 - -* 1行あたりの文字数は2文字までに収めること (ただし `<?php` タグは除く) -** 厳密な定義: `<?php` タグ以降のソースコードが、2 byte ごとに -ラインフィード (LF) で区切られること -* スペースやタブを使用しないこと -* ループのアンロールをしないこと -** 100 回ループの代わりに 100 回コードをコピペ、というのは禁止 -* PHP 7.4〜8.1 で動作すること -* 実行時に Notice や Warning が出ないこと -* 標準的なインストール構成の PHP で実現できること -(デフォルトで有効になっていない拡張等を使わないこと) - -備考: PHP には `short_open_tag` -というオプションがあり、これを有効にするとファイル冒頭の `<?php` -の代わりに `<?` -を使うことができ、文字どおり1行2文字で書ける。ただ、このオプションはデフォルト -off になっている環境が多いようなので、今回は使わないことにした。 - -== 主な障害 - -1行あたりの文字数など、適当に改行を挟めばいいだけではないのか? - -特に、C言語でこのような試みをおこなったことがあるかたならそう思うだろう。事実、Cでのこの制約はほとんど無意味に等しい。 - -[source,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 -); - -/* あとは同じように普通のプログラムを変形するだけなので省略 */ ----- - -バックスラッシュを使った行継続がトークンを区切らない、というのがポイントだ。 - -さて、PHP -ではそもそもバックスラッシュを行継続に使うことができない。これにより、「3文字以上からなるトークンが一切使えない」という制約が課される。例えば、`echo` -で出力することや、`for` でループすること、`new` -でインスタンスを生成することができない。特に、出力は fizzbuzz -をどんなアルゴリズムで実装しようとおこなわなければならないので、できないのは致命的である。 - -当然、名前が3文字以上ある関数も使えない。なお、標準 PHP -の範囲内において、名前が 2文字以下の関数は以下のとおりである: - -* `_`: `gettext` のエイリアス -* `dl`: 拡張モジュールをロードする -* `pi`: 円周率を返す - -(環境によって多少は変わるかも) - -2文字の関数を定義しまくった拡張モジュールを用意しておいて `dl()` -で読み込む行為は、レギュレーションで定めた - -____ -* 標準的なインストール構成の PHP で実現できること -(デフォルトで有効になっていない拡張等を使わないこと) -____ - -に反する -(というより、「それだとおもしろくもなんともないので、このルールを足した」というのが正しい)。 - -また、2文字だと文字列がまともに書けないのも辛い。`''` だけで -2文字使うので、「1文字の文字列リテラル」というものを書くことができない。PHP -では文字列リテラル中に生の改行が書けるので - -[source,php] ----- -$a -=' -a' -;; ----- - -とすると `$a` は `"\na"` になるのだが、余計な改行が入ってしまう。 - -これらの障害をどのように乗り越えるのか、次節から見ていく。 - -== 解説 - -=== 普通の (?) fizzbuzz - -まずは普通に書くとしよう。 - -.... -<?php - -for ($i = 1; $i < 100; $i++) { - echo (($i % 3 ? '' : 'Fizz') . ($i % 5 ? '' : 'Buzz') ?: $i) . "\n"; -} -.... - -素直に書いた fizzbuzz -とは言い難いが、このくらいは普通だということにしておかないと、この先がやっていられないので許してほしい。 - -=== `for` の排除 - -`for` -は、3文字もある長いキーワードである。こんなものは使えない。`array_` -系の関数を使って、適当に置き換えるとしよう。 - -[source,php] ----- -<?php - -$s = range(1, 100); -array_walk( - $s, - fn($i) => - printf((($i % 3 ? '' : 'Fizz') . ($i % 5 ? '' : 'Buzz') ?: $i) . "\n"), -); ----- - -`array_walk` や `range`、`printf` といった `for` -よりも長いトークンが現れてしまったが、これは次節で直すことにする。なお、`echo` -は文 (statement) であり式 (expression) ではないので、式である `printf` -に置き換えた。 - -=== 関数呼び出しの短縮 - -`range`、`array_walk`、`printf` -は長すぎるのでどうにかせねばならない。ここで、PHP -の可変関数を使う。可変関数とは、関数名が文字列として入った変数を経由して、関数を呼び出す機能である。 - -[source,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"), -); ----- - -これで関数を呼び出している所は短くなった。では、`$r` や `$w` や -`$p`、また `'Fizz'` や `'Buzz'` はどうやって -1行2文字に収めるのか。次のテクニックへ移ろう。 - -=== 余談: PHP 8.x で動作しなくてもいいなら - -今回使ったテクニックを説明する前に、余談として、文字列リテラルの短縮法として今回採用しなかったものを紹介する。 - -____ -* PHP 7.4〜8.1 で動作すること -____ - -というルールがない場合、「未定義の定数が評価された場合、その定数の名前が値になる」という -PHP 7.x までの仕様が利用できる。例えば、 `Fizz` -という文字列が欲しければ、次のようにする。 - -[source,php] ----- -$f -=F -.i -.z -.z -;; ----- - -こうして簡単に文字列を作れる。なお、この仕様は 7.x -時点でも警告を受けるので、`@` 演算子を使って抑制してやるとよい。 - -[source,php] ----- -$f -=@ -F. -@i -.# -@z -.# -@z -;; ----- - -むしろ、このことがわかっていたからこそ PHP 8.x -での動作を要件に課したところがある。 - -=== 文字列リテラルの短縮 - -実際に使った手法の説明に移る。 - -ずばり、文字列同士のビット演算を使う。PHP では、文字列同士でビット演算 -(`&`、`|`、`^`) -をした場合、文字列の各バイトごとに指定したビット演算がなされ、それを結合したものが演算結果となる。 - -[source,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 ----- - -これを踏まえ、次のコードを見てみよう。 - -[source,php] ----- -$x = "x\nOm\n"; -$y = "\nk!\no"; -$r = $x ^ $y; -echo "$r\n"; ----- - -実行すると、`range` が表示される。さて、PHP -では文字列リテラル中に生の改行を直接書いてもよいのだった -(「主な障害」の節を参照のこと)。書きかえてみよう。 - -[source,php] ----- -$x -='x -Om -'; -$y -=' -k! -o' -; - -$r = $x ^ $y; -echo "$r\n"; ----- - -さらに `#` を使って適当に調整すると、次のようになる。 - -[source,php] ----- -$x -=# -'x -Om -'; -$y -=' -k! -o' -;# -$r -=# -$x -^# -$y -;# - -echo "$r\n"; ----- - -1行あたり2文字で、`range` -という文字列を生成することに成功した。他の必要な文字列にも、同様の処理をほどこす。 - -備考: `Buzz` 中にある小文字の `u` は、このロジックだと non-printable -な文字になってしまう。ここまでのテクニックを駆使すれば回避するのはそう難しくないので、考えてみてほしい。 - -== 完成系 - -完成したものがこちら。 - -[source,php] ----- -<?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 は、スクリプト言語の中だとシンタックスシュガーが少ない -(体感)。この挑戦は不可能に思われたが、PHP -マニュアルとにらめっこしていたらなんとかなった。 - -みんなもプログラムを細長くしよう。 - -== 余談2: 別解 - -PHP では、バッククォートを使ってシェルを呼び出せる。これは `shell_exec` -関数と等価である。さて、PHP -ではバックスラッシュによる行継続が使えないと書いたが、シェルでは使える -(当然だが、呼び出されるシェルに依存する。Bash -なら大丈夫だろう。知らんけど)。 - -[source,php] ----- -<?php - -printf(` -e\ -c\ -h\ -o\ - \ -1\ -2\ -3\ -`); ----- - -なお、ここでは簡単のため出力に `printf` をそのまま使っているが、実際には -`printf` という文字列を合成して可変関数で呼び出す。 - -ただし、これでは - -____ -* スペースやタブを使用しないこと -____ - -に違反してしまう。スペースが使えないと引数とコマンドを区切れない。これは困った。 - -もうこれ以上は不可能だと思っていたのだが、この記事の執筆中に解決する方法を思いついたので載せておく。 - -[source,php] ----- -<?php - -$c = 'chr'; - -${ -'_ -'} -=# -$c -(# -32 -). -$c -(# -92 -); - -printf(` -e\ -c\ -h\ -o\ -${ -'_ -'} -1\ -2\ -3\ -`); ----- - -先程と同じく、`chr` や `printf` を生成する部分は長くなるので省いた。 - -.... -${ -'_ -'} -.... - -は変数で、中にはスペースとエスケープが入っている -(`chr(32) . chr(92)`)。シェルに渡されている文字列は次のようになる。 - -.... -e\ -c\ -h\ -o\ - \ -1\ -2\ -3\ -.... - -これは、前掲したコマンドと同じだ。かくして、スペースを陽に書かずにシェルをおおよそ自由に扱えるようになった。Fizzbuzz -のワンライナーくらいすぐ書けるだろうから、あとはなんとかなるだろう -(試してないけど)。 - -ということでこれは別解ということにしておく。 - -ちなみに、PHP 8.2 からは、この記法で Warning が出るようになるようだ。 - -.... -${ -'_ -'} -.... - -最新版で警告が出るというのも美しくないので、私としては本編の解法を推す。 diff --git a/content/posts/2022-09-29/write-fizzbuzz-in-php-2-letters-per-line.xml b/content/posts/2022-09-29/write-fizzbuzz-in-php-2-letters-per-line.xml new file mode 100644 index 0000000..9ef6654 --- /dev/null +++ b/content/posts/2022-09-29/write-fizzbuzz-in-php-2-letters-per-line.xml @@ -0,0 +1,581 @@ +<?xml version="1.0" encoding="UTF-8"?> +<article xmlns="http://docbook.org/ns/docbook" xmlns:xl="http://www.w3.org/1999/xlink" version="5.0"> + <info> + <title>【PHP】 fizzbuzz を書く。1行あたり2文字で。</title> + <abstract> + PHP で、fizzbuzz を書いた。ただし、1行あたりに使える文字数は2文字まで。 + </abstract> + <keywordset> + <keyword>php</keyword> + </keywordset> + <revhistory> + <revision> + <date>2022-09-28</date> + <revremark>公開</revremark> + </revision> + <revision> + <date>2022-09-29</date> + <revremark>小さな文言の修正・変更</revremark> + </revision> + </revhistory> + </info> + <section xml:id="_記事の構成について"> + <title>記事の構成について</title> + <simpara>この記事は、普通の fizzbuzz + を徐々に変形して最終形にしていく、という構成で書かれている。最終形を見てどのような仕組みで動いているのか解読してから解説を読みたい、というかたがいれば、 + <link xl:href="https://gist.github.com/nsfisis/04c227d5a419867472a0b23a83ad2919#file-fizzbuzz-php-2-letters-per-line-and-supports-php-8-x-without-warnings">このページ</link> + にソースコードがあるので、そちらを先に見てほしい。</simpara> +</section> +<section xml:id="_レギュレーション"> + <title>レギュレーション</title> + <simpara>PHP で、次のような制約の下に fizzbuzz を書いた。</simpara> + <itemizedlist> + <listitem> + <simpara>1行あたりの文字数は2文字までに収めること (ただし <literal><?php</literal> タグは除く)</simpara> + <itemizedlist> + <listitem> + <simpara>厳密な定義: <literal><?php</literal> タグ以降のソースコードが、2 byte ごとに + ラインフィード (LF) で区切られること</simpara> + </listitem> + </itemizedlist> + </listitem> + <listitem> + <simpara>スペースやタブを使用しないこと</simpara> + </listitem> + <listitem> + <simpara>ループのアンロールをしないこと</simpara> + <itemizedlist> + <listitem> + <simpara>100 回ループの代わりに 100 回コードをコピペ、というのは禁止</simpara> + </listitem> + </itemizedlist> + </listitem> + <listitem> + <simpara>PHP 7.4〜8.1 で動作すること</simpara> + </listitem> + <listitem> + <simpara>実行時に Notice や Warning が出ないこと</simpara> + </listitem> + <listitem> + <simpara>標準的なインストール構成の PHP で実現できること + (デフォルトで有効になっていない拡張等を使わないこと)</simpara> +</listitem> +</itemizedlist> +<simpara>備考: PHP には <literal>short_open_tag</literal> + というオプションがあり、これを有効にするとファイル冒頭の <literal><?php</literal> + の代わりに <literal><?</literal> + を使うことができ、文字どおり1行2文字で書ける。ただ、このオプションはデフォルト + off になっている環境が多いようなので、今回は使わないことにした。</simpara> +</section> +<section xml:id="_主な障害"> + <title>主な障害</title> + <simpara>1行あたりの文字数など、適当に改行を挟めばいいだけではないのか?</simpara> + <simpara>特に、C言語でこのような試みをおこなったことがあるかたならそう思うだろう。事実、Cでのこの制約はほとんど無意味に等しい。</simpara> + <programlisting language="c" linenumbering="unnumbered">#\ + 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 + ); + + /* あとは同じように普通のプログラムを変形するだけなので省略 */</programlisting> +<simpara>バックスラッシュを使った行継続がトークンを区切らない、というのがポイントだ。</simpara> +<simpara>さて、PHP +ではそもそもバックスラッシュを行継続に使うことができない。これにより、「3文字以上からなるトークンが一切使えない」という制約が課される。例えば、<literal>echo</literal> +で出力することや、<literal>for</literal> でループすること、<literal>new</literal> +でインスタンスを生成することができない。特に、出力は fizzbuzz +をどんなアルゴリズムで実装しようとおこなわなければならないので、できないのは致命的である。</simpara> +<simpara>当然、名前が3文字以上ある関数も使えない。なお、標準 PHP +の範囲内において、名前が 2文字以下の関数は以下のとおりである:</simpara> +<itemizedlist> + <listitem> + <simpara><literal>_</literal>: <literal>gettext</literal> のエイリアス</simpara> + </listitem> + <listitem> + <simpara><literal>dl</literal>: 拡張モジュールをロードする</simpara> + </listitem> + <listitem> + <simpara><literal>pi</literal>: 円周率を返す</simpara> + </listitem> +</itemizedlist> +<simpara>(環境によって多少は変わるかも)</simpara> +<simpara>2文字の関数を定義しまくった拡張モジュールを用意しておいて <literal>dl()</literal> + で読み込む行為は、レギュレーションで定めた</simpara> +<blockquote> + <itemizedlist> + <listitem> + <simpara>標準的なインストール構成の PHP で実現できること + (デフォルトで有効になっていない拡張等を使わないこと)</simpara> + </listitem> +</itemizedlist> +</blockquote> +<simpara>に反する +(というより、「それだとおもしろくもなんともないので、このルールを足した」というのが正しい)。</simpara> +<simpara>また、2文字だと文字列がまともに書けないのも辛い。<literal>''</literal> だけで +2文字使うので、「1文字の文字列リテラル」というものを書くことができない。PHP +では文字列リテラル中に生の改行が書けるので</simpara> +<programlisting language="php" linenumbering="unnumbered">$a +=' +a' +;;</programlisting> +<simpara>とすると <literal>$a</literal> は <literal>"\na"</literal> になるのだが、余計な改行が入ってしまう。</simpara> +<simpara>これらの障害をどのように乗り越えるのか、次節から見ていく。</simpara> +</section> +<section xml:id="_解説"> + <title>解説</title> + <section xml:id="_普通の_fizzbuzz"> + <title>普通の (?) fizzbuzz</title> + <simpara>まずは普通に書くとしよう。</simpara> + <literallayout class="monospaced"><?php + + for ($i = 1; $i < 100; $i++) { + echo (($i % 3 ? '' : 'Fizz') . ($i % 5 ? '' : 'Buzz') ?: $i) . "\n"; + }</literallayout> + <simpara>素直に書いた fizzbuzz + とは言い難いが、このくらいは普通だということにしておかないと、この先がやっていられないので許してほしい。</simpara> +</section> +<section xml:id="_for_の排除"> + <title><literal>for</literal> の排除</title> + <simpara><literal>for</literal> + は、3文字もある長いキーワードである。こんなものは使えない。<literal>array_</literal> + 系の関数を使って、適当に置き換えるとしよう。</simpara> + <programlisting language="php" linenumbering="unnumbered"><?php + + $s = range(1, 100); + array_walk( + $s, + fn($i) => + printf((($i % 3 ? '' : 'Fizz') . ($i % 5 ? '' : 'Buzz') ?: $i) . "\n"), + );</programlisting> +<simpara><literal>array_walk</literal> や <literal>range</literal>、<literal>printf</literal> といった <literal>for</literal> + よりも長いトークンが現れてしまったが、これは次節で直すことにする。なお、<literal>echo</literal> + は文 (statement) であり式 (expression) ではないので、式である <literal>printf</literal> + に置き換えた。</simpara> +</section> +<section xml:id="_関数呼び出しの短縮"> + <title>関数呼び出しの短縮</title> + <simpara><literal>range</literal>、<literal>array_walk</literal>、<literal>printf</literal> + は長すぎるのでどうにかせねばならない。ここで、PHP + の可変関数を使う。可変関数とは、関数名が文字列として入った変数を経由して、関数を呼び出す機能である。</simpara> + <programlisting language="php" linenumbering="unnumbered"><?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"), + );</programlisting> +<simpara>これで関数を呼び出している所は短くなった。では、<literal>$r</literal> や <literal>$w</literal> や +<literal>$p</literal>、また <literal>'Fizz'</literal> や <literal>'Buzz'</literal> はどうやって +1行2文字に収めるのか。次のテクニックへ移ろう。</simpara> +</section> +<section xml:id="_余談_php_8_x_で動作しなくてもいいなら"> + <title>余談: PHP 8.x で動作しなくてもいいなら</title> + <simpara>今回使ったテクニックを説明する前に、余談として、文字列リテラルの短縮法として今回採用しなかったものを紹介する。</simpara> + <blockquote> + <itemizedlist> + <listitem> + <simpara>PHP 7.4〜8.1 で動作すること</simpara> + </listitem> + </itemizedlist> + </blockquote> + <simpara>というルールがない場合、「未定義の定数が評価された場合、その定数の名前が値になる」という + PHP 7.x までの仕様が利用できる。例えば、 <literal>Fizz</literal> + という文字列が欲しければ、次のようにする。</simpara> +<programlisting language="php" linenumbering="unnumbered">$f +=F +.i +.z +.z +;;</programlisting> +<simpara>こうして簡単に文字列を作れる。なお、この仕様は 7.x +時点でも警告を受けるので、<literal>@</literal> 演算子を使って抑制してやるとよい。</simpara> +<programlisting language="php" linenumbering="unnumbered">$f +=@ +F. +@i +.# +@z +.# +@z +;;</programlisting> +<simpara>むしろ、このことがわかっていたからこそ PHP 8.x +での動作を要件に課したところがある。</simpara> +</section> +<section xml:id="_文字列リテラルの短縮"> + <title>文字列リテラルの短縮</title> + <simpara>実際に使った手法の説明に移る。</simpara> + <simpara>ずばり、文字列同士のビット演算を使う。PHP では、文字列同士でビット演算 + (<literal>&</literal>、<literal>|</literal>、<literal>^</literal>) + をした場合、文字列の各バイトごとに指定したビット演算がなされ、それを結合したものが演算結果となる。</simpara> +<programlisting language="php" linenumbering="unnumbered">$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</programlisting> +<simpara>これを踏まえ、次のコードを見てみよう。</simpara> +<programlisting language="php" linenumbering="unnumbered">$x = "x\nOm\n"; +$y = "\nk!\no"; +$r = $x ^ $y; +echo "$r\n";</programlisting> +<simpara>実行すると、<literal>range</literal> が表示される。さて、PHP +では文字列リテラル中に生の改行を直接書いてもよいのだった +(「主な障害」の節を参照のこと)。書きかえてみよう。</simpara> +<programlisting language="php" linenumbering="unnumbered">$x +='x +Om +'; +$y +=' +k! +o' +; + +$r = $x ^ $y; +echo "$r\n";</programlisting> +<simpara>さらに <literal>#</literal> を使って適当に調整すると、次のようになる。</simpara> +<programlisting language="php" linenumbering="unnumbered">$x +=# +'x +Om +'; +$y +=' +k! +o' +;# +$r +=# +$x +^# +$y +;# + +echo "$r\n";</programlisting> +<simpara>1行あたり2文字で、<literal>range</literal> + という文字列を生成することに成功した。他の必要な文字列にも、同様の処理をほどこす。</simpara> +<simpara>備考: <literal>Buzz</literal> 中にある小文字の <literal>u</literal> は、このロジックだと non-printable +な文字になってしまう。ここまでのテクニックを駆使すれば回避するのはそう難しくないので、考えてみてほしい。</simpara> +</section> +</section> +<section xml:id="_完成系"> + <title>完成系</title> + <simpara>完成したものがこちら。</simpara> + <programlisting language="php" linenumbering="unnumbered"><?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 + )# + .' + ') + );</programlisting> +</section> +<section xml:id="_感想など"> + <title>感想など</title> + <simpara>PHP は、スクリプト言語の中だとシンタックスシュガーが少ない + (体感)。この挑戦は不可能に思われたが、PHP + マニュアルとにらめっこしていたらなんとかなった。</simpara> +<simpara>みんなもプログラムを細長くしよう。</simpara> +</section> +<section xml:id="_余談2_別解"> + <title>余談2: 別解</title> + <simpara>PHP では、バッククォートを使ってシェルを呼び出せる。これは <literal>shell_exec</literal> + 関数と等価である。さて、PHP + ではバックスラッシュによる行継続が使えないと書いたが、シェルでは使える + (当然だが、呼び出されるシェルに依存する。Bash + なら大丈夫だろう。知らんけど)。</simpara> + <programlisting language="php" linenumbering="unnumbered"><?php + + printf(` + e\ + c\ + h\ + o\ + \ + 1\ + 2\ + 3\ + `);</programlisting> +<simpara>なお、ここでは簡単のため出力に <literal>printf</literal> をそのまま使っているが、実際には +<literal>printf</literal> という文字列を合成して可変関数で呼び出す。</simpara> +<simpara>ただし、これでは</simpara> +<blockquote> + <itemizedlist> + <listitem> + <simpara>スペースやタブを使用しないこと</simpara> + </listitem> + </itemizedlist> +</blockquote> +<simpara>に違反してしまう。スペースが使えないと引数とコマンドを区切れない。これは困った。</simpara> +<simpara>もうこれ以上は不可能だと思っていたのだが、この記事の執筆中に解決する方法を思いついたので載せておく。</simpara> +<programlisting language="php" linenumbering="unnumbered"><?php + +$c = 'chr'; + +${ +'_ +'} +=# +$c +(# +32 +). +$c +(# +92 +); + +printf(` +e\ +c\ +h\ +o\ +${ +'_ +'} +1\ +2\ +3\ +`);</programlisting> +<simpara>先程と同じく、<literal>chr</literal> や <literal>printf</literal> を生成する部分は長くなるので省いた。</simpara> +<literallayout class="monospaced">${ +'_ +'}</literallayout> +<simpara>は変数で、中にはスペースとエスケープが入っている +(<literal>chr(32) . chr(92)</literal>)。シェルに渡されている文字列は次のようになる。</simpara> +<literallayout class="monospaced">e\ +c\ +h\ +o\ +\ +1\ +2\ +3\</literallayout> +<simpara>これは、前掲したコマンドと同じだ。かくして、スペースを陽に書かずにシェルをおおよそ自由に扱えるようになった。Fizzbuzz +のワンライナーくらいすぐ書けるだろうから、あとはなんとかなるだろう +(試してないけど)。</simpara> +<simpara>ということでこれは別解ということにしておく。</simpara> +<simpara>ちなみに、PHP 8.2 からは、この記法で Warning が出るようになるようだ。</simpara> +<literallayout class="monospaced">${ +'_ +'}</literallayout> +<simpara>最新版で警告が出るというのも美しくないので、私としては本編の解法を推す。</simpara> +</section> +</article> diff --git a/content/posts/2022-10-23/phperkaigi-2023-unused-token-quiz-1.adoc b/content/posts/2022-10-23/phperkaigi-2023-unused-token-quiz-1.adoc deleted file mode 100644 index d9be116..0000000 --- a/content/posts/2022-10-23/phperkaigi-2023-unused-token-quiz-1.adoc +++ /dev/null @@ -1,157 +0,0 @@ -= PHPerKaigi 2023: ボツになったトークン問題 その 1 -:tags: php, phperkaigi -:description: 来年の PHPerKaigi 2023 でデジタルサーカス株式会社から出題予定のトークン問題のうち、 \ - ボツになった問題を公開する (その 1)。 -:revision-1: 2022-10-23 公開 - -== はじめに - -2023 年 3 月 23 日から 25 日にかけて開催予定 (記事執筆時点) -の、 https://phperkaigi.jp/2023/[PHPerKaigi 2023] -において、昨年と同様に、弊社 https://www.dgcircus.com/[デジタルサーカス株式会社] -から、トークン問題を出題予定である。 - -昨年のトークン問題の記事はこちら: -link:/posts/2022-04-09/phperkaigi-2022-tokens[PHPerKaigi 2022 -トークン問題の解説] - -すでに 2023 -年用の問題は作成済みであるが、その制作過程の中でいくつかボツ問ができた。せっかくなので、PHPerKaigi -開催を待つ間に紹介しようと思う。 - -10 月から 2 月まで、毎月 1 記事ずつ公開していく予定 (忘れていなければ)。 - -== 問題 - -注意: これはボツ問なので、得られたトークンを PHPerKaigi -で入力してもポイントにはならない。 - -[source,php] ----- -<?php - -$π = $argv[1] ?? null; -if ($π === null) { - exit('No input.'); -} -$π = trim($π); -if (!is_numeric($π)) { - exit('Invalid input.'); -} - -$s = implode(array_map(chr(...), str_split($π, 2))); - -preg_match('/(\x23.+?) /', $s, $m); -$t = $m[1] ?? ''; - -if (md5($t) === '056e831a4146bf123e8ea16613303d2e') { - echo "Token: {$t}\n"; -} else { - echo "Failed.\n"; -} ----- - -== トークン入手方法 - -ソースを見るとわかるとおり、`$argv[1]` を参照している。それを `$π` -なる変数に代入しているので、円周率を渡してみる。 - -[source,shell-session] ----- -$ php Q.php 3.14 -Failed. ----- - -失敗してしまった。精度を上げてみる。 - -[source,shell-session] ----- -$ php Q.php 3.1415 -Failed. ----- - -だめだった。これを成功するまで繰り返す。 - -最初にトークンが得られるのは、小数点以下 16 -桁目まで入力したときで、こうなる。 - -[source,shell-session] ----- -$ php Q.php 3.1415926535897932 -Token: #YO ----- - -めでたくトークン「#YO」が手に入った。 - -== 解説 - -短いので頭から追っていく。 - -[source,php] ----- -$π = $argv[1] ?? null; -if ($π === null) { - exit('No input.'); -} -$π = trim($π); -if (!is_numeric($π)) { - exit('Invalid input.'); -} ----- - -入力のバリデーション部分。数値のみ受け付ける。 - -[source,php] ----- -$s = implode(array_map(chr(...), str_split($π, 2))); ----- - -`$π` を 2 文字ごとに区切り (`str_split`)、数値を ASCII -コードと見做して文字に変換 (`chr`) して結合 (`implode`) している。 - -例えば、`$π` が `'656667'` だったとすると、`65`、`66`、`67` に対応した -`'A'`、`'B'`、`'C'` へと変換され、`'ABC'` になる。 - -[source,php] ----- -$π = '656667'; -$s = implode(array_map(chr(...), str_split($π, 2))); -echo $s; -// => ABC ----- - -[source,php] ----- -preg_match('/(\x23.+?) /', $s, $m); -$t = $m[1] ?? ''; ----- - -正規表現でマッチングしている。`\x23` は `\#` -と同じであることに留意すると、この正規表現は「`#` から始まる 2 -以上の長さ (含 `#`) -の文字列で、最初に現れるスペースまで」にマッチする。つまりこれは、PHPerKaigi -におけるトークンである。 - -なお、`\#` を直接書いていないのは、`/#.+?) /` と書くと、`#.+?)` -という意図せぬトークンが登録されてしまうからである。 - -[source,php] ----- -if (md5($t) === '056e831a4146bf123e8ea16613303d2e') { - echo "Token: {$t}\n"; -} else { - echo "Failed.\n"; -} ----- - -最後にトークンのハッシュ値を見て、想定解かどうかを確認する。 - -== おわりに - -円周率を何桁も計算して ASCII -コード経由で文字列化すれば、トークンっぽいものがどこかで出てくるのではないか、と考えて生まれた作品。 - -最初は真面目に円周率の計算プログラムを組んでいたのだが、いざ動かしてみるとやけに浅いところにあったので驚いた -(ちなみに、それでも `M_PI` や `pi()` -では精度が足りない)。見つけたときは狂喜したものの、冷静になってみると大して面白くなかったのでボツになった。むしろ、100 -万桁目くらいに埋まっていてくれたほうがよかったかもしれない。 diff --git a/content/posts/2022-10-23/phperkaigi-2023-unused-token-quiz-1.xml b/content/posts/2022-10-23/phperkaigi-2023-unused-token-quiz-1.xml new file mode 100644 index 0000000..760582d --- /dev/null +++ b/content/posts/2022-10-23/phperkaigi-2023-unused-token-quiz-1.xml @@ -0,0 +1,121 @@ +<?xml version="1.0" encoding="UTF-8"?> +<article xmlns="http://docbook.org/ns/docbook" xmlns:xl="http://www.w3.org/1999/xlink" version="5.0"> + <info> + <title>PHPerKaigi 2023: ボツになったトークン問題 その 1</title> + <abstract> + 来年の PHPerKaigi 2023 でデジタルサーカス株式会社から出題予定のトークン問題のうち、ボツになった問題を公開する (その 1)。 + </abstract> + <keywordset> + <keyword>php</keyword> + <keyword>phperkaigi</keyword> + </keywordset> + <revhistory> + <revision> + <date>2022-10-23</date> + <revremark>公開</revremark> + </revision> + </revhistory> + </info> + <section xml:id="_はじめに"> + <title>はじめに</title> + <simpara>2023 年 3 月 23 日から 25 日にかけて開催予定 (記事執筆時点) + の、 <link xl:href="https://phperkaigi.jp/2023/">PHPerKaigi 2023</link> + において、昨年と同様に、弊社 <link xl:href="https://www.dgcircus.com/">デジタルサーカス株式会社</link> + から、トークン問題を出題予定である。</simpara> + <simpara>昨年のトークン問題の記事はこちら: + <link xl:href="/posts/2022-04-09/phperkaigi-2022-tokens">PHPerKaigi 2022 + トークン問題の解説</link></simpara> +<simpara>すでに 2023 +年用の問題は作成済みであるが、その制作過程の中でいくつかボツ問ができた。せっかくなので、PHPerKaigi +開催を待つ間に紹介しようと思う。</simpara> +<simpara>10 月から 2 月まで、毎月 1 記事ずつ公開していく予定 (忘れていなければ)。</simpara> +</section> +<section xml:id="_問題"> + <title>問題</title> + <simpara>注意: これはボツ問なので、得られたトークンを PHPerKaigi + で入力してもポイントにはならない。</simpara> +<programlisting language="php" linenumbering="unnumbered"><?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"; +}</programlisting> +</section> +<section xml:id="_トークン入手方法"> + <title>トークン入手方法</title> + <simpara>ソースを見るとわかるとおり、<literal>$argv[1]</literal> を参照している。それを <literal>$π</literal> + なる変数に代入しているので、円周率を渡してみる。</simpara> + <programlisting language="shell-session" linenumbering="unnumbered">$ php Q.php 3.14 + Failed.</programlisting> +<simpara>失敗してしまった。精度を上げてみる。</simpara> +<programlisting language="shell-session" linenumbering="unnumbered">$ php Q.php 3.1415 +Failed.</programlisting> +<simpara>だめだった。これを成功するまで繰り返す。</simpara> +<simpara>最初にトークンが得られるのは、小数点以下 16 +桁目まで入力したときで、こうなる。</simpara> +<programlisting language="shell-session" linenumbering="unnumbered">$ php Q.php 3.1415926535897932 +Token: #YO</programlisting> +<simpara>めでたくトークン「#YO」が手に入った。</simpara> +</section> +<section xml:id="_解説"> + <title>解説</title> + <simpara>短いので頭から追っていく。</simpara> + <programlisting language="php" linenumbering="unnumbered">$π = $argv[1] ?? null; + if ($π === null) { + exit('No input.'); + } + $π = trim($π); + if (!is_numeric($π)) { + exit('Invalid input.'); + }</programlisting> +<simpara>入力のバリデーション部分。数値のみ受け付ける。</simpara> +<programlisting language="php" linenumbering="unnumbered">$s = implode(array_map(chr(...), str_split($π, 2)));</programlisting> +<simpara><literal>$π</literal> を 2 文字ごとに区切り (<literal>str_split</literal>)、数値を ASCII +コードと見做して文字に変換 (<literal>chr</literal>) して結合 (<literal>implode</literal>) している。</simpara> +<simpara>例えば、<literal>$π</literal> が <literal>'656667'</literal> だったとすると、<literal>65</literal>、<literal>66</literal>、<literal>67</literal> に対応した +<literal>'A'</literal>、<literal>'B'</literal>、<literal>'C'</literal> へと変換され、<literal>'ABC'</literal> になる。</simpara> +<programlisting language="php" linenumbering="unnumbered">$π = '656667'; +$s = implode(array_map(chr(...), str_split($π, 2))); +echo $s; +// => ABC</programlisting> +<programlisting language="php" linenumbering="unnumbered">preg_match('/(\x23.+?) /', $s, $m); +$t = $m[1] ?? '';</programlisting> +<simpara>正規表現でマッチングしている。<literal>\x23</literal> は <literal>#</literal> + と同じであることに留意すると、この正規表現は「<literal>#</literal> から始まる 2 + 以上の長さ (含 <literal>#</literal>) + の文字列で、最初に現れるスペースまで」にマッチする。つまりこれは、PHPerKaigi + におけるトークンである。</simpara> +<simpara>なお、<literal>#</literal> を直接書いていないのは、<literal>/#.+?) /</literal> と書くと、<literal>#.+?)</literal> + という意図せぬトークンが登録されてしまうからである。</simpara> +<programlisting language="php" linenumbering="unnumbered">if (md5($t) === '056e831a4146bf123e8ea16613303d2e') { +echo "Token: {$t}\n"; +} else { +echo "Failed.\n"; +}</programlisting> +<simpara>最後にトークンのハッシュ値を見て、想定解かどうかを確認する。</simpara> +</section> +<section xml:id="_おわりに"> + <title>おわりに</title> + <simpara>円周率を何桁も計算して ASCII + コード経由で文字列化すれば、トークンっぽいものがどこかで出てくるのではないか、と考えて生まれた作品。</simpara> +<simpara>最初は真面目に円周率の計算プログラムを組んでいたのだが、いざ動かしてみるとやけに浅いところにあったので驚いた +(ちなみに、それでも <literal>M_PI</literal> や <literal>pi()</literal> +では精度が足りない)。見つけたときは狂喜したものの、冷静になってみると大して面白くなかったのでボツになった。むしろ、100 +万桁目くらいに埋まっていてくれたほうがよかったかもしれない。</simpara> +</section> +</article> diff --git a/content/posts/2022-10-28/setup-server-for-this-site.adoc b/content/posts/2022-10-28/setup-server-for-this-site.adoc deleted file mode 100644 index 8d2dad3..0000000 --- a/content/posts/2022-10-28/setup-server-for-this-site.adoc +++ /dev/null @@ -1,248 +0,0 @@ -= [備忘録] このサイト用の VPS をセットアップしたときのメモ -:tags: note-to-self -:description: GitHub Pages でホストしていたこのサイトを VPS へ移行したので、 \ - そのときにやったことのメモ。99 % 自分用。 -:revision-1: 2022-10-28 公開 - -== はじめに - -これまでこの blog は GitHub Pages でホストしていたのだが、先日 VPS -に移行した。そのときにおこなったサーバのセットアップ作業を書き残しておく。99 -% 自分用の備忘録。別のベンダに移したりしたくなったら見に来る。 - -未来の自分へ: 特に自動化してないので、せいぜい苦しんでくれ。 - -== VPS - -https://vps.sakura.ad.jp/[さくらの VPS] の 2 GB -プラン。そこまで真面目に選定していないので、困ったら移動するかも。 - -== 事前準備 - -=== サーバのホスト名を決める - -モチベーションが上がるという効能がある。今回は藤原定家から取って -``teika'' -にした。たいていいつも源氏物語の帖か小倉百人一首の歌人から選んでいる。 - -=== SSH の鍵生成 - -ローカルマシンで鍵を生成する。 - -[source,shell-session] ----- -$ 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 からサーバへのデプロイ用。 - -=== SSH の設定 - -`.ssh/config` に設定しておく。 - -[source,ssh_config] ----- -Host teika - HostName ********** - User ********** - Port ********** - IdentityFile ~/.ssh/teika.key ----- - -== 基本のセットアップ - -=== SSH 接続 - -VPS 契約時に設定した管理者ユーザとパスワードを使ってログインする。 - -=== ユーザを作成する - -管理者ユーザで作業すると危ないので、メインで使うユーザを作成する。`sudo` -グループに追加して `sudo` できるようにし、`su` で切り替え。 - -[source,shell-session] ----- -$ sudo adduser ********** -$ sudo adduser ********** sudo -$ su ********** -$ cd ----- - -=== ホスト名を変える - -[source,shell-session] ----- -$ sudo hostname teika ----- - -=== 公開鍵を置く - -[source,shell-session] ----- -$ mkdir ~/.ssh -$ chmod 700 ~/.ssh -$ vi ~/.ssh/authorized_keys ----- - -`authorized_keys` には、ローカルで生成した `~/.ssh/teika.key.pub` と -`~/.ssh/github2teika.key.pub` の内容をコピーする。 - -=== SSH の設定 - -SSH の設定を変更し、少しでも安全にしておく。 - -[source,shell-session] ----- -$ sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak -$ sudo vi /etc/ssh/sshd_config ----- - -* `Port` を変更 -* `PermitRootLogin` を `no` に -* `PasswordAuthentication` を `no` に - -そして設定を反映。 - -[source,shell-session] ----- -$ sudo systemctl restart sshd -$ sudo systemctl status sshd ----- - -=== SSH で接続確認 - -今の SSH -セッションは閉じずに、ターミナルを別途開いて疎通確認する。セッションを閉じてしまうと、SSH -の設定に不備があった場合に締め出しをくらう。 - -[source,shell-session] ----- -$ ssh teika ----- - -=== ポートの遮断 - -デフォルトの 22 番を閉じ、設定したポートだけ空ける。 - -[source,shell-session] ----- -$ sudo ufw deny ssh -$ sudo ufw allow ******* -$ sudo ufw enable -$ sudo ufw reload -$ sudo ufw status ----- - -ここでもう一度 SSH の接続確認を挟む。 - -=== GitHub 用の SSH 鍵 - -GitHub に置いてある private リポジトリをサーバから clone したいので、SSH -鍵を生成して置いておく。 - -[source,shell-session] ----- -$ ssh-keygen -t ed25519 -b 521 -f ~/.ssh/github.key -$ cat ~/.ssh/github.key.pub ----- - -https://github.com/settings/ssh[GitHub の設定画面] -から、この公開鍵を追加する。 - -[source,shell-session] ----- -$ vi ~/.ssh/config ----- - -設定はこう。 - -[source,ssh_config] ----- -Host github.com - HostName github.com - User git - IdentityFile ~/.ssh/github.key ----- - -最後に接続できるか確認しておく。 - -[source,shell-session] ----- -ssh -T github.com ----- - -=== パッケージの更新 - -[source,shell-session] ----- -$ sudo apt update -$ sudo apt upgrade -$ sudo apt update -$ sudo apt upgrade -$ sudo apt autoremove ----- - -== サイトホスティング用のセットアップ - -=== DNS に IP アドレスを登録する - -このサーバは固定の IP アドレスがあるので、`A` -レコードに直接入れるだけで済んだ。 - -=== 使うソフトウェアのインストール - -[source,shell-session] ----- -$ sudo apt install docker docker-compose git make ----- - -=== メインユーザが Docker を使えるように - -[source,shell-session] ----- -sudo adduser ********** docker ----- - -=== HTTP/HTTPS を通す - -80 番と 443 番を空ける。 - -[source,shell-session] ----- -$ sudo ufw allow 80/tcp -$ sudo ufw allow 443/tcp -$ sudo ufw reload -$ sudo ufw status ----- - -=== リポジトリのクローン - -[source,shell-session] ----- -$ cd -$ git clone git@github.com:nsfisis/nsfisis.dev.git -$ cd nsfisis.dev -$ git submodule update --init ----- - -=== certbot で証明書取得 - -[source,shell-session] ----- -$ docker-compose up -d acme-challenge -$ make setup ----- - -=== サーバを稼動させる - -[source,shell-session] ----- -$ make serve ----- - -== 感想 - -(業務でなく) -個人だと数年ぶりのサーバセットアップで、これだけでも割と時間を食ってしまった。とはいえ式年遷宮は楽しいので、これからも定期的にやっていきたい。コンテナデプロイにしたい気持ちもあるのだが、色々実験したい関係上、本物のサーバも欲しくはある。次の式年遷宮では、手順の一部だけでも自動化したいところ。 diff --git a/content/posts/2022-10-28/setup-server-for-this-site.xml b/content/posts/2022-10-28/setup-server-for-this-site.xml new file mode 100644 index 0000000..6521029 --- /dev/null +++ b/content/posts/2022-10-28/setup-server-for-this-site.xml @@ -0,0 +1,191 @@ +<?xml version="1.0" encoding="UTF-8"?> +<article xmlns="http://docbook.org/ns/docbook" xmlns:xl="http://www.w3.org/1999/xlink" version="5.0"> + <info> + <title>【備忘録】 このサイト用の VPS をセットアップしたときのメモ</title> + <abstract> + GitHub Pages でホストしていたこのサイトを VPS へ移行したので、そのときにやったことのメモ。99 % 自分用。 + </abstract> + <keywordset> + <keyword>note-to-self</keyword> + </keywordset> + <revhistory> + <revision> + <date>2022-10-28</date> + <revremark>公開</revremark> + </revision> + </revhistory> + </info> + <section xml:id="_はじめに"> + <title>はじめに</title> + <simpara>これまでこの blog は GitHub Pages でホストしていたのだが、先日 VPS + に移行した。そのときにおこなったサーバのセットアップ作業を書き残しておく。99 + % 自分用の備忘録。別のベンダに移したりしたくなったら見に来る。</simpara> + <simpara>未来の自分へ: 特に自動化してないので、せいぜい苦しんでくれ。</simpara> +</section> +<section xml:id="_vps"> + <title>VPS</title> + <simpara><link xl:href="https://vps.sakura.ad.jp/">さくらの VPS</link> の 2 GB + プラン。そこまで真面目に選定していないので、困ったら移動するかも。</simpara> +</section> +<section xml:id="_事前準備"> + <title>事前準備</title> + <section xml:id="_サーバのホスト名を決める"> + <title>サーバのホスト名を決める</title> + <simpara>モチベーションが上がるという効能がある。今回は藤原定家から取って + ``teika'' + にした。たいていいつも源氏物語の帖か小倉百人一首の歌人から選んでいる。</simpara> +</section> +<section xml:id="_ssh_の鍵生成"> + <title>SSH の鍵生成</title> + <simpara>ローカルマシンで鍵を生成する。</simpara> + <programlisting language="shell-session" linenumbering="unnumbered">$ ssh-keygen -t ed25519 -b 521 -f ~/.ssh/teika.key + $ ssh-keygen -t ed25519 -b 521 -f ~/.ssh/github2teika.key</programlisting> +<simpara><literal>teika.key</literal> はローカルからサーバへの接続用、<literal>github2teika.key</literal> + は、GitHub Actions からサーバへのデプロイ用。</simpara> +</section> +<section xml:id="_ssh_の設定"> + <title>SSH の設定</title> + <simpara><literal>.ssh/config</literal> に設定しておく。</simpara> + <programlisting language="ssh_config" linenumbering="unnumbered">Host teika + HostName ********** + User ********** + Port ********** + IdentityFile ~/.ssh/teika.key</programlisting> +</section> +</section> +<section xml:id="_基本のセットアップ"> + <title>基本のセットアップ</title> + <section xml:id="_ssh_接続"> + <title>SSH 接続</title> + <simpara>VPS 契約時に設定した管理者ユーザとパスワードを使ってログインする。</simpara> + </section> + <section xml:id="_ユーザを作成する"> + <title>ユーザを作成する</title> + <simpara>管理者ユーザで作業すると危ないので、メインで使うユーザを作成する。<literal>sudo</literal> + グループに追加して <literal>sudo</literal> できるようにし、<literal>su</literal> で切り替え。</simpara> + <programlisting language="shell-session" linenumbering="unnumbered">$ sudo adduser ********** + $ sudo adduser ********** sudo + $ su ********** + $ cd</programlisting> +</section> +<section xml:id="_ホスト名を変える"> + <title>ホスト名を変える</title> + <programlisting language="shell-session" linenumbering="unnumbered">$ sudo hostname teika</programlisting> +</section> +<section xml:id="_公開鍵を置く"> + <title>公開鍵を置く</title> + <programlisting language="shell-session" linenumbering="unnumbered">$ mkdir ~/.ssh + $ chmod 700 ~/.ssh + $ vi ~/.ssh/authorized_keys</programlisting> +<simpara><literal>authorized_keys</literal> には、ローカルで生成した <literal>~/.ssh/teika.key.pub</literal> と +<literal>~/.ssh/github2teika.key.pub</literal> の内容をコピーする。</simpara> +</section> +<section xml:id="_ssh_の設定_2"> + <title>SSH の設定</title> + <simpara>SSH の設定を変更し、少しでも安全にしておく。</simpara> + <programlisting language="shell-session" linenumbering="unnumbered">$ sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak + $ sudo vi /etc/ssh/sshd_config</programlisting> +<itemizedlist> + <listitem> + <simpara><literal>Port</literal> を変更</simpara> + </listitem> + <listitem> + <simpara><literal>PermitRootLogin</literal> を <literal>no</literal> に</simpara> + </listitem> + <listitem> + <simpara><literal>PasswordAuthentication</literal> を <literal>no</literal> に</simpara> + </listitem> +</itemizedlist> +<simpara>そして設定を反映。</simpara> +<programlisting language="shell-session" linenumbering="unnumbered">$ sudo systemctl restart sshd +$ sudo systemctl status sshd</programlisting> +</section> +<section xml:id="_ssh_で接続確認"> + <title>SSH で接続確認</title> + <simpara>今の SSH + セッションは閉じずに、ターミナルを別途開いて疎通確認する。セッションを閉じてしまうと、SSH + の設定に不備があった場合に締め出しをくらう。</simpara> +<programlisting language="shell-session" linenumbering="unnumbered">$ ssh teika</programlisting> +</section> +<section xml:id="_ポートの遮断"> + <title>ポートの遮断</title> + <simpara>デフォルトの 22 番を閉じ、設定したポートだけ空ける。</simpara> + <programlisting language="shell-session" linenumbering="unnumbered">$ sudo ufw deny ssh + $ sudo ufw allow ******* + $ sudo ufw enable + $ sudo ufw reload + $ sudo ufw status</programlisting> +<simpara>ここでもう一度 SSH の接続確認を挟む。</simpara> +</section> +<section xml:id="_github_用の_ssh_鍵"> + <title>GitHub 用の SSH 鍵</title> + <simpara>GitHub に置いてある private リポジトリをサーバから clone したいので、SSH + 鍵を生成して置いておく。</simpara> +<programlisting language="shell-session" linenumbering="unnumbered">$ ssh-keygen -t ed25519 -b 521 -f ~/.ssh/github.key +$ cat ~/.ssh/github.key.pub</programlisting> +<simpara><link xl:href="https://github.com/settings/ssh">GitHub の設定画面</link> + から、この公開鍵を追加する。</simpara> +<programlisting language="shell-session" linenumbering="unnumbered">$ vi ~/.ssh/config</programlisting> +<simpara>設定はこう。</simpara> +<programlisting language="ssh_config" linenumbering="unnumbered">Host github.com +HostName github.com +User git +IdentityFile ~/.ssh/github.key</programlisting> +<simpara>最後に接続できるか確認しておく。</simpara> +<programlisting language="shell-session" linenumbering="unnumbered">ssh -T github.com</programlisting> +</section> +<section xml:id="_パッケージの更新"> + <title>パッケージの更新</title> + <programlisting language="shell-session" linenumbering="unnumbered">$ sudo apt update + $ sudo apt upgrade + $ sudo apt update + $ sudo apt upgrade + $ sudo apt autoremove</programlisting> +</section> +</section> +<section xml:id="_サイトホスティング用のセットアップ"> + <title>サイトホスティング用のセットアップ</title> + <section xml:id="_dns_に_ip_アドレスを登録する"> + <title>DNS に IP アドレスを登録する</title> + <simpara>このサーバは固定の IP アドレスがあるので、<literal>A</literal> + レコードに直接入れるだけで済んだ。</simpara> + </section> + <section xml:id="_使うソフトウェアのインストール"> + <title>使うソフトウェアのインストール</title> + <programlisting language="shell-session" linenumbering="unnumbered">$ sudo apt install docker docker-compose git make</programlisting> + </section> + <section xml:id="_メインユーザが_docker_を使えるように"> + <title>メインユーザが Docker を使えるように</title> + <programlisting language="shell-session" linenumbering="unnumbered">sudo adduser ********** docker</programlisting> + </section> + <section xml:id="_httphttps_を通す"> + <title>HTTP/HTTPS を通す</title> + <simpara>80 番と 443 番を空ける。</simpara> + <programlisting language="shell-session" linenumbering="unnumbered">$ sudo ufw allow 80/tcp + $ sudo ufw allow 443/tcp + $ sudo ufw reload + $ sudo ufw status</programlisting> +</section> +<section xml:id="_リポジトリのクローン"> + <title>リポジトリのクローン</title> + <programlisting language="shell-session" linenumbering="unnumbered">$ cd + $ git clone git@github.com:nsfisis/nsfisis.dev.git + $ cd nsfisis.dev + $ git submodule update --init</programlisting> +</section> +<section xml:id="_certbot_で証明書取得"> + <title>certbot で証明書取得</title> + <programlisting language="shell-session" linenumbering="unnumbered">$ docker-compose up -d acme-challenge + $ make setup</programlisting> +</section> +<section xml:id="_サーバを稼動させる"> + <title>サーバを稼動させる</title> + <programlisting language="shell-session" linenumbering="unnumbered">$ make serve</programlisting> +</section> +</section> +<section xml:id="_感想"> + <title>感想</title> + <simpara>(業務でなく) + 個人だと数年ぶりのサーバセットアップで、これだけでも割と時間を食ってしまった。とはいえ式年遷宮は楽しいので、これからも定期的にやっていきたい。コンテナデプロイにしたい気持ちもあるのだが、色々実験したい関係上、本物のサーバも欲しくはある。次の式年遷宮では、手順の一部だけでも自動化したいところ。</simpara> +</section> +</article> diff --git a/content/posts/2022-11-19/phperkaigi-2023-unused-token-quiz-2.adoc b/content/posts/2022-11-19/phperkaigi-2023-unused-token-quiz-2.adoc deleted file mode 100644 index 859ab74..0000000 --- a/content/posts/2022-11-19/phperkaigi-2023-unused-token-quiz-2.adoc +++ /dev/null @@ -1,132 +0,0 @@ -= PHPerKaigi 2023: ボツになったトークン問題 その 2 -:tags: php, phperkaigi -:description: 来年の PHPerKaigi 2023 でデジタルサーカス株式会社から出題予定のトークン問題のうち、 \ - ボツになった問題を公開する (その 2)。 -:revision-1: 2022-11-19 公開 - -== はじめに - -2023 年 3 月 23 日から 25 日にかけて開催予定 (記事執筆時点) の https://phperkaigi.jp/2023/[PHPerKaigi 2023] において、 -昨年と同様に、弊社 https://www.dgcircus.com/[デジタルサーカス株式会社] からトークン問題を出題予定である。 - -昨年のトークン問題の記事はこちら: link:/posts/2022-04-09/phperkaigi-2022-tokens/[PHPerKaigi 2022 トークン問題の解説] - -すでに 2023 年用の問題は作成済みであるが、その制作過程の中でいくつかボツ問ができた。せっかくなので、PHPerKaigi 開催を待つ間に紹介しようと思う。 - -10 月から 2 月まで、毎月 1 記事ずつ公開していく予定 (忘れていなければ)。 - -その 1 はこちら: link:/posts/2022-10-23/phperkaigi-2023-unused-token-quiz-1/[PHPerKaigi 2023: ボツになったトークン問題 その 1] - -== 問題 - -注意: これはボツ問なので、得られたトークンを PHPerKaigi で入力してもポイントにはならない。 - -[source,php] ----- -<?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 (自分自身と同じソースコードを出力するプログラム) になっている。 - -== トークン入手方法 - -実行してみると、次のような出力が得られる。 - -[source,php] ----- -# -<?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 行目を除き、先ほどのコードとほぼ同じものが出てきた。もう一度実行してみる。 - -[source,php] ----- -# -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 行目が書き換えられた。すべての行が変化するまで繰り返すと次のようになる。 - -[source,php] ----- -# -W -E -L -O -V -E -P -H -P ----- - -トークン「#WELOVEPHP」が手に入った。 - -== 解説 - -一見すると同じ行が 10 行並んでいるだけなのにも関わらず、なぜそれぞれの行で出力が変わるのか。ソースコードをコピーして、適当なエディタに貼り付けるとわかりやすい。 - -Vim で開くと次のようになる (1 行目を抜粋)。 - -[source,php] ----- -<?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 で、ゼロ幅スペースである。 - -NOTE: エディタによっては、ゼロ幅スペースが見えないことがある。VSCode ではブラウザと同様に不可視だった。 - -文字列リテラルの中にゼロ幅スペースを仕込むことで、見た目を変えずに情報をエンコードすることが可能となる。 - -続いて、トークンへの変換ロジックを解析する。注目すべきはこの部分だ。以下、ゼロ幅スペースは Vim での表示に合わせて `<200b>` と記載する。 - -[source,php] ----- -fn($s)=>chr(strlen($s)/3) ----- - -PHP の `strlen()` は文字列のバイト数を返す。1 行目の `$s` は以下の内容となっており、 - -[source,php] ----- -$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 コードと一致する。他の行も、同様にしてゼロ幅スペースを詰めることで文字列長を調整し、トークンをエンコードしている。 - -デコード部以外の部分は、quine のための記述である。 - - -== おわりに - -https://blog.rust-lang.org/2021/11/01/cve-2021-42574.html[CVE-2021-42574] に着想を得た作品。この脆弱性は、Unicode の制御文字である left-to-right mark と right-to-left mark を利用し、ソースコードの実際の内容を欺く、というもの。簡単のためゼロ幅スペースを用いることとし、ついでに quine にもするとこうなった。 - -ボツになった理由は、ゼロ幅スペースを表示してくるエディタが想像以上に多かったため。「同じ行が並んでいるだけなのに出力が異なる」というアイデアの根幹を崩されてしまうので、この問題は不採用となった。 diff --git a/content/posts/2022-11-19/phperkaigi-2023-unused-token-quiz-2.xml b/content/posts/2022-11-19/phperkaigi-2023-unused-token-quiz-2.xml new file mode 100644 index 0000000..bc93868 --- /dev/null +++ b/content/posts/2022-11-19/phperkaigi-2023-unused-token-quiz-2.xml @@ -0,0 +1,102 @@ +<?xml version="1.0" encoding="UTF-8"?> +<article xmlns="http://docbook.org/ns/docbook" xmlns:xl="http://www.w3.org/1999/xlink" version="5.0"> + <info> + <title>PHPerKaigi 2023: ボツになったトークン問題 その 2</title> + <abstract> + 来年の PHPerKaigi 2023 でデジタルサーカス株式会社から出題予定のトークン問題のうち、ボツになった問題を公開する (その 2)。 + </abstract> + <keywordset> + <keyword>php</keyword> + <keyword>phperkaigi</keyword> + </keywordset> + <revhistory> + <revision> + <date>2022-11-19</date> + <revremark>公開</revremark> + </revision> + </revhistory> + </info> + <section xml:id="_はじめに"> + <title>はじめに</title> + <simpara>2023 年 3 月 23 日から 25 日にかけて開催予定 (記事執筆時点) の <link xl:href="https://phperkaigi.jp/2023/">PHPerKaigi 2023</link> において、 + 昨年と同様に、弊社 <link xl:href="https://www.dgcircus.com/">デジタルサーカス株式会社</link> からトークン問題を出題予定である。</simpara> + <simpara>昨年のトークン問題の記事はこちら: <link xl:href="/posts/2022-04-09/phperkaigi-2022-tokens/">PHPerKaigi 2022 トークン問題の解説</link></simpara> + <simpara>すでに 2023 年用の問題は作成済みであるが、その制作過程の中でいくつかボツ問ができた。せっかくなので、PHPerKaigi 開催を待つ間に紹介しようと思う。</simpara> + <simpara>10 月から 2 月まで、毎月 1 記事ずつ公開していく予定 (忘れていなければ)。</simpara> + <simpara>その 1 はこちら: <link xl:href="/posts/2022-10-23/phperkaigi-2023-unused-token-quiz-1/">PHPerKaigi 2023: ボツになったトークン問題 その 1</link></simpara> +</section> +<section xml:id="_問題"> + <title>問題</title> + <simpara>注意: これはボツ問なので、得られたトークンを PHPerKaigi で入力してもポイントにはならない。</simpara> + <programlisting language="php" linenumbering="unnumbered"><?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");?></programlisting> +<simpara>"And Then There Were None" (そして誰もいなくなった) と名付けた作品。変則 quine (自分自身と同じソースコードを出力するプログラム) になっている。</simpara> +</section> +<section xml:id="_トークン入手方法"> + <title>トークン入手方法</title> + <simpara>実行してみると、次のような出力が得られる。</simpara> + <programlisting language="php" linenumbering="unnumbered"># + <?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");?></programlisting> +<simpara>1 行目を除き、先ほどのコードとほぼ同じものが出てきた。もう一度実行してみる。</simpara> +<programlisting language="php" linenumbering="unnumbered"># +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");?></programlisting> +<simpara>今度は 2 行目が書き換えられた。すべての行が変化するまで繰り返すと次のようになる。</simpara> +<programlisting language="php" linenumbering="unnumbered"># +W +E +L +O +V +E +P +H +P</programlisting> +<simpara>トークン「#WELOVEPHP」が手に入った。</simpara> +</section> +<section xml:id="_解説"> + <title>解説</title> + <simpara>一見すると同じ行が 10 行並んでいるだけなのにも関わらず、なぜそれぞれの行で出力が変わるのか。ソースコードをコピーして、適当なエディタに貼り付けるとわかりやすい。</simpara> + <simpara>Vim で開くと次のようになる (1 行目を抜粋)。</simpara> + <programlisting language="php" linenumbering="unnumbered"><?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");?></programlisting> + <simpara><literal><200b></literal> と表示されているのは、Unicode の U+200b で、ゼロ幅スペースである。</simpara> + <note> + <simpara>エディタによっては、ゼロ幅スペースが見えないことがある。VSCode ではブラウザと同様に不可視だった。</simpara> + </note> + <simpara>文字列リテラルの中にゼロ幅スペースを仕込むことで、見た目を変えずに情報をエンコードすることが可能となる。</simpara> + <simpara>続いて、トークンへの変換ロジックを解析する。注目すべきはこの部分だ。以下、ゼロ幅スペースは Vim での表示に合わせて <literal><200b></literal> と記載する。</simpara> + <programlisting language="php" linenumbering="unnumbered">fn($s)=>chr(strlen($s)/3)</programlisting> + <simpara>PHP の <literal>strlen()</literal> は文字列のバイト数を返す。1 行目の <literal>$s</literal> は以下の内容となっており、</simpara> + <programlisting language="php" linenumbering="unnumbered">$s='<200b><?php printf((isset($s)?fn($s)=>trim($s,"<200b>"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>'</programlisting> + <simpara>このソースコードは UTF-8 で書かれているので、105 バイトになる。それを 3 で割ると 35 となり、これは <literal>#</literal> の ASCII コードと一致する。他の行も、同様にしてゼロ幅スペースを詰めることで文字列長を調整し、トークンをエンコードしている。</simpara> + <simpara>デコード部以外の部分は、quine のための記述である。</simpara> +</section> +<section xml:id="_おわりに"> + <title>おわりに</title> + <simpara><link xl:href="https://blog.rust-lang.org/2021/11/01/cve-2021-42574.html">CVE-2021-42574</link> に着想を得た作品。この脆弱性は、Unicode の制御文字である left-to-right mark と right-to-left mark を利用し、ソースコードの実際の内容を欺く、というもの。簡単のためゼロ幅スペースを用いることとし、ついでに quine にもするとこうなった。</simpara> + <simpara>ボツになった理由は、ゼロ幅スペースを表示してくるエディタが想像以上に多かったため。「同じ行が並んでいるだけなのに出力が異なる」というアイデアの根幹を崩されてしまうので、この問題は不採用となった。</simpara> +</section> +</article> diff --git a/content/posts/2023-01-10/phperkaigi-2023-unused-token-quiz-3.adoc b/content/posts/2023-01-10/phperkaigi-2023-unused-token-quiz-3.adoc deleted file mode 100644 index 34fdcb3..0000000 --- a/content/posts/2023-01-10/phperkaigi-2023-unused-token-quiz-3.adoc +++ /dev/null @@ -1,282 +0,0 @@ -= PHPerKaigi 2023: ボツになったトークン問題 その 3 -:tags: php, phperkaigi -:description: 来年の PHPerKaigi 2023 でデジタルサーカス株式会社から出題予定のトークン問題のうち、 \ - ボツになった問題を公開する (その 3)。 -:revision-1: 2023-01-10 公開 -:revision-2: 2023-01-10 本シリーズの今後について追記 - - -== はじめに - -2023 年 3 月 23 日から 25 日にかけて開催予定 (記事執筆時点) の https://phperkaigi.jp/2023/[PHPerKaigi 2023] において、 -昨年と同様に、弊社 https://www.dgcircus.com/[デジタルサーカス株式会社] からトークン問題を出題予定である。 - -昨年のトークン問題の記事はこちら: link:/posts/2022-04-09/phperkaigi-2022-tokens/[PHPerKaigi 2022 トークン問題の解説] - -すでに 2023 年用の問題は作成済みであるが、その制作過程の中でいくつかボツ問ができた。せっかくなので、PHPerKaigi 開催を待つ間に紹介しようと思う。 - -10 月から 2 月まで、毎月 1 記事ずつ公開していく予定 (忘れていなければ → 忘れていたので 12 月公開予定だった記事を今書いている)。 - -* その 1 はこちら: link:/posts/2022-10-23/phperkaigi-2023-unused-token-quiz-1/[PHPerKaigi 2023: ボツになったトークン問題 その 1] -* その 2 はこちら: link:/posts/2022-11-19/phperkaigi-2023-unused-token-quiz-2/[PHPerKaigi 2023: ボツになったトークン問題 その 2] - -追記: 元々は 10月から 2月にかけて 5つのボツ問を公開予定だったのですが、光栄なことに PHPerKaigi 2023 での登壇が決まったので、1、2月の分は書かない/書けないと思います。 - - -== 問題 - -注意: これはボツ問なので、得られたトークンを PHPerKaigi で入力してもポイントにはならない。 - -[source,php] ----- -<?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==')` が得られる。 - -トークンは PHP の式になっていて、評価すると `Hello, World!` という文字列になる。PHPer チャレンジのトークンには空白を含められないという制約があるが、こういった形でトークンにすれば回避できる。 - -== 解説 - -=== 概要 - -例外が発生した行数にデータをエンコードし、それを `catch` で捕まえて表示している。 - -=== 例外オブジェクトの連鎖 - -https://www.php.net/class.Exception[`Exception`] や https://www.php.net/class.Error[`Error`] には `$previous` というプロパティがあり、コンストラクタの第3引数から渡すことができる。主に 2つの用法がある: - -* エラーを処理している途中に起こった別のエラーに、元のエラー情報を含める -* 内部エラーをラップして作られたエラーに、内部エラーの情報を含める - -このうち 1つ目のケースは、 `finally` 節の中でエラーを投げると PHP 処理系が勝手に `$previous` を設定してくれる。 - -[source,php] ----- -<?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 -} ----- - -この知識を元に、トークンの出力部を解析してみる。 - -=== 出力部の解析 - -出力部をコメントや改行を追加して再掲する: - -[source,php] ----- -<?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` で出力している。 - -フォーマット指定子 `%c` は、整数を ASCII コードfootnote:[RAS syndrome] と見做して印字する。トークン `#base64_decode('SGVsbG8sIFdvcmxkIQ==')` の `b` であれば、ASCII コード `98` なので、75 行目で発生したエラー、 - -``` - 1, 20 => 0 / 0, -``` - -によって表現されている。エラーを起こす方法はいろいろと考えられるが、今回はゼロ除算を使った。 - -それでは、エラーチェインを作る箇所、関数 `f()` を見ていく。 - -=== データ構成部の解析 - -`f()` の定義を再掲する (エラーオブジェクトの行数を利用しているので、一部分だけ抜き出すと値が変わることに注意): - -[source,php] ----- -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()` を呼び出している箇所を確認すると、 - -[source,php] ----- -<?php -try { - f(g() / __LINE__); // 3 行目 ----- - -[source,php] ----- -function g() { - return __LINE__; // 111 行目 -} ----- - -`f()` には `111 / 3` で `37` が渡されることがわかる。そこから 1 ずつ減らして再帰呼び出ししていき、0 より小さくなったら `f()` を引数なしで呼び出す。引数の数が足りないと呼び出しに失敗するので、再帰はここで止まる。 - -エラーチェインは、最後に発生したエラーを先頭とした単方向連結リストになっているので、順に - -. `f()` の引数が足りないことによる呼び出し失敗 -. `f(0)` の呼び出しで発生したゼロ除算 -. `f(1)` の呼び出しで発生したゼロ除算 -. ... -. `f(37)` の呼び出しで発生したゼロ除算 - -となっている。出力の際は `catch` したエラーの `getPrevious()` から処理を始めるので、1 番目の `f()` によるエラーは無視され、 `f(0)` によるエラー、 `f(1)` によるエラー、 `f(2)` によるエラー、と出力が進む。 - -`f()` に `0` を渡したときは 12 行目にある `match` の `0` でゼロ除算が起こるので、行数が 12 となったエラーが投げられる。出力部ではこれに 23 を足した数を ASCII コードとして表示しているのだった。 `12 + 23` は `35`、ASCII コードでは `#` である。これがトークンの 1文字目にあたる。 - - -== おわりに - -「行数」というのはトークン文字列をデコードする対象として優れている。 - -* トークンの一部や全部が陽に現れない -* `pass:[__LINE__]` で容易に取得できる - -しかし、こういった「変な」プログラムを何度も読んだり書いたりしていると、 `pass:[__LINE__]` を使うのはあまりにありきたりで退屈になる。では、他に行数を取得する手段はないか。こうして `Throwable` を思いつき、続けてエラーオブジェクトには `$previous` があることを思い出した。 - -今回エラーを投げるのにゼロ除算を用いたのは、それがエラーを投げる最も短いコードだと考えたからである。もし 3バイト未満で `Throwable` なオブジェクトを投げる手段をご存じのかたがいらっしゃれば、ぜひご教示いただきたい。……と締める予定だったのだが、`0/0` のところを存在しない定数にすれば、簡単に 1バイトを達成できた。ゼロ除算している箇所はちょうど 26 箇所あるので、アルファベットにでもしておけば意味ありげで良かったかもしれない。 diff --git a/content/posts/2023-01-10/phperkaigi-2023-unused-token-quiz-3.xml b/content/posts/2023-01-10/phperkaigi-2023-unused-token-quiz-3.xml new file mode 100644 index 0000000..2a7be46 --- /dev/null +++ b/content/posts/2023-01-10/phperkaigi-2023-unused-token-quiz-3.xml @@ -0,0 +1,272 @@ +<?xml version="1.0" encoding="UTF-8"?> +<article xmlns="http://docbook.org/ns/docbook" xmlns:xl="http://www.w3.org/1999/xlink" version="5.0"> + <info> + <title>PHPerKaigi 2023: ボツになったトークン問題 その 3</title> + <abstract> + 来年の PHPerKaigi 2023 でデジタルサーカス株式会社から出題予定のトークン問題のうち、ボツになった問題を公開する (その 3)。 + </abstract> + <keywordset> + <keyword>php</keyword> + <keyword>phperkaigi</keyword> + </keywordset> + <revhistory> + <revision> + <date>2023-01-10</date> + <revremark>公開</revremark> + </revision> + </revhistory> + </info> + <section xml:id="_はじめに"> + <title>はじめに</title> + <simpara>2023 年 3 月 23 日から 25 日にかけて開催予定 (記事執筆時点) の <link xl:href="https://phperkaigi.jp/2023/">PHPerKaigi 2023</link> において、 + 昨年と同様に、弊社 <link xl:href="https://www.dgcircus.com/">デジタルサーカス株式会社</link> からトークン問題を出題予定である。</simpara> + <simpara>昨年のトークン問題の記事はこちら: <link xl:href="/posts/2022-04-09/phperkaigi-2022-tokens/">PHPerKaigi 2022 トークン問題の解説</link></simpara> + <simpara>すでに 2023 年用の問題は作成済みであるが、その制作過程の中でいくつかボツ問ができた。せっかくなので、PHPerKaigi 開催を待つ間に紹介しようと思う。</simpara> + <simpara>10 月から 2 月まで、毎月 1 記事ずつ公開していく予定 (忘れていなければ → 忘れていたので 12 月公開予定だった記事を今書いている)。</simpara> + <itemizedlist> + <listitem> + <simpara>その 1 はこちら: <link xl:href="/posts/2022-10-23/phperkaigi-2023-unused-token-quiz-1/">PHPerKaigi 2023: ボツになったトークン問題 その 1</link></simpara> + </listitem> + <listitem> + <simpara>その 2 はこちら: <link xl:href="/posts/2022-11-19/phperkaigi-2023-unused-token-quiz-2/">PHPerKaigi 2023: ボツになったトークン問題 その 2</link></simpara> + </listitem> + </itemizedlist> + </section> + <section xml:id="_問題"> + <title>問題</title> + <simpara>注意: これはボツ問なので、得られたトークンを PHPerKaigi で入力してもポイントにはならない。</simpara> + <programlisting language="php" linenumbering="unnumbered"><?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__; + }</programlisting> + <simpara>"Catchline" と名付けた作品。実行するとトークン <literal>#base64_decode('SGVsbG8sIFdvcmxkIQ==')</literal> が得られる。</simpara> + <simpara>トークンは PHP の式になっていて、評価すると <literal>Hello, World!</literal> という文字列になる。PHPer チャレンジのトークンには空白を含められないという制約があるが、こういった形でトークンにすれば回避できる。</simpara> + </section> + <section xml:id="_解説"> + <title>解説</title> + <section xml:id="_概要"> + <title>概要</title> + <simpara>例外が発生した行数にデータをエンコードし、それを <literal>catch</literal> で捕まえて表示している。</simpara> + </section> + <section xml:id="_例外オブジェクトの連鎖"> + <title>例外オブジェクトの連鎖</title> + <simpara><link xl:href="https://www.php.net/class.Exception"><literal>Exception</literal></link> や <link xl:href="https://www.php.net/class.Error"><literal>Error</literal></link> には <literal>$previous</literal> というプロパティがあり、コンストラクタの第3引数から渡すことができる。主に 2つの用法がある:</simpara> + <itemizedlist> + <listitem> + <simpara>エラーを処理している途中に起こった別のエラーに、元のエラー情報を含める</simpara> + </listitem> + <listitem> + <simpara>内部エラーをラップして作られたエラーに、内部エラーの情報を含める</simpara> + </listitem> + </itemizedlist> + <simpara>このうち 1つ目のケースは、 <literal>finally</literal> 節の中でエラーを投げると PHP 処理系が勝手に <literal>$previous</literal> を設定してくれる。</simpara> + <programlisting language="php" linenumbering="unnumbered"><?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 + }</programlisting> + <simpara>この知識を元に、トークンの出力部を解析してみる。</simpara> + </section> + <section xml:id="_出力部の解析"> + <title>出力部の解析</title> + <simpara>出力部をコメントや改行を追加して再掲する:</simpara> + <programlisting language="php" linenumbering="unnumbered"><?php + try { + f(g() / __LINE__); + } catch (Throwable $e) { + while ($e = $e->getPrevious()) { + printf('%c', $e->getLine() + 23); + } + echo "\n"; + }</programlisting> + <simpara>出力をおこなう <literal>catch</literal> 節を見てみると、 <literal>Throwable::getPrevious()</literal> を呼び出してエラーチェインを辿り、 <literal>Throwable::getLine()</literal> でエラーが発生した行数を取得している。その行数に <literal>23</literal> なるマジックナンバーを足し、フォーマット指定子 <literal>%c</literal> で出力している。</simpara> + <simpara>フォーマット指定子 <literal>%c</literal> は、整数を ASCII コード<footnote>RAS syndrome</footnote> と見做して印字する。トークン <literal>#base64_decode('SGVsbG8sIFdvcmxkIQ==')</literal> の <literal>b</literal> であれば、ASCII コード <literal>98</literal> なので、75 行目で発生したエラー、</simpara> + <programlisting language="php" linenumbering="unnumbered"> 1, 20 => 0 / 0,</programlisting> + <simpara>によって表現されている。エラーを起こす方法はいろいろと考えられるが、今回はゼロ除算を使った。</simpara> + <simpara>それでは、エラーチェインを作る箇所、関数 <literal>f()</literal> を見ていく。</simpara> + </section> + <section xml:id="_データ構成部の解析"> + <title>データ構成部の解析</title> + <simpara><literal>f()</literal> の定義を再掲する (エラーオブジェクトの行数を利用しているので、一部分だけ抜き出すと値が変わることに注意):</simpara> + <programlisting language="php" linenumbering="unnumbered">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); + } + }</programlisting> + <simpara>前述のように、 <literal>finally</literal> 節でエラーを投げると PHP 処理系が <literal>$previous</literal> を設定する。ここでは、エラーを繋げるために <literal>f()</literal> を再帰呼び出ししている。最初に <literal>f()</literal> を呼び出している箇所を確認すると、</simpara> + <programlisting language="php" linenumbering="unnumbered"><?php + try { + f(g() / __LINE__); // 3 行目</programlisting> + <programlisting language="php" linenumbering="unnumbered">function g() { + return __LINE__; // 111 行目 + }</programlisting> + <simpara><literal>f()</literal> には <literal>111 / 3</literal> で <literal>37</literal> が渡されることがわかる。そこから 1 ずつ減らして再帰呼び出ししていき、0 より小さくなったら <literal>f()</literal> を引数なしで呼び出す。引数の数が足りないと呼び出しに失敗するので、再帰はここで止まる。</simpara> + <simpara>エラーチェインは、最後に発生したエラーを先頭とした単方向連結リストになっているので、順に</simpara> + <orderedlist numeration="arabic"> + <listitem> + <simpara><literal>f()</literal> の引数が足りないことによる呼び出し失敗</simpara> + </listitem> + <listitem> + <simpara><literal>f(0)</literal> の呼び出しで発生したゼロ除算</simpara> + </listitem> + <listitem> + <simpara><literal>f(1)</literal> の呼び出しで発生したゼロ除算</simpara> + </listitem> + <listitem> + <simpara>…​</simpara> + </listitem> + <listitem> + <simpara><literal>f(37)</literal> の呼び出しで発生したゼロ除算</simpara> + </listitem> + </orderedlist> + <simpara>となっている。出力の際は <literal>catch</literal> したエラーの <literal>getPrevious()</literal> から処理を始めるので、1 番目の <literal>f()</literal> によるエラーは無視され、 <literal>f(0)</literal> によるエラー、 <literal>f(1)</literal> によるエラー、 <literal>f(2)</literal> によるエラー、と出力が進む。</simpara> + <simpara><literal>f()</literal> に <literal>0</literal> を渡したときは 12 行目にある <literal>match</literal> の <literal>0</literal> でゼロ除算が起こるので、行数が 12 となったエラーが投げられる。出力部ではこれに 23 を足した数を ASCII コードとして表示しているのだった。 <literal>12 + 23</literal> は <literal>35</literal>、ASCII コードでは <literal>#</literal> である。これがトークンの 1文字目にあたる。</simpara> + </section> + </section> + <section xml:id="_おわりに"> + <title>おわりに</title> + <simpara>「行数」というのはトークン文字列をデコードする対象として優れている。</simpara> + <itemizedlist> + <listitem> + <simpara>トークンの一部や全部が陽に現れない</simpara> + </listitem> + <listitem> + <simpara><literal>__LINE__</literal> で容易に取得できる</simpara> + </listitem> + </itemizedlist> + <simpara>しかし、こういった「変な」プログラムを何度も読んだり書いたりしていると、 <literal>__LINE__</literal> を使うのはあまりにありきたりで退屈になる。では、他に行数を取得する手段はないか。こうして <literal>Throwable</literal> を思いつき、続けてエラーオブジェクトには <literal>$previous</literal> があることを思い出した。</simpara> + <simpara>今回エラーを投げるのにゼロ除算を用いたのは、それがエラーを投げる最も短いコードだと考えたからである。もし 3バイト未満で <literal>Throwable</literal> なオブジェクトを投げる手段をご存じのかたがいらっしゃれば、ぜひご教示いただきたい。……と締める予定だったのだが、<literal>0/0</literal> のところを存在しない定数にすれば、簡単に 1バイトを達成できた。ゼロ除算している箇所はちょうど 26 箇所あるので、アルファベットにでもしておけば意味ありげで良かったかもしれない。</simpara> + </section> +</article> diff --git a/deno.jsonc b/deno.jsonc new file mode 100644 index 0000000..d44e901 --- /dev/null +++ b/deno.jsonc @@ -0,0 +1,7 @@ +{ + "importMap": "import_map.json", + "tasks": { + "check": "deno check nuldoc-src/main.ts && deno lint -- nuldoc-src/ && deno fmt --check -- nuldoc-src/", + "fmt": "deno fmt -- nuldoc-src", + } +} diff --git a/deno.lock b/deno.lock new file mode 100644 index 0000000..a1adf64 --- /dev/null +++ b/deno.lock @@ -0,0 +1,42 @@ +{ + "version": "2", + "remote": { + "https://deno.land/std@0.170.0/_util/asserts.ts": "d0844e9b62510f89ce1f9878b046f6a57bf88f208a10304aab50efcb48365272", + "https://deno.land/std@0.170.0/_util/os.ts": "8a33345f74990e627b9dfe2de9b040004b08ea5146c7c9e8fe9a29070d193934", + "https://deno.land/std@0.170.0/crypto/_fnv/fnv32.ts": "aa9bddead8c6345087d3abd4ef35fb9655622afc333fc41fff382b36e64280b5", + "https://deno.land/std@0.170.0/crypto/_fnv/fnv64.ts": "625d7e7505b6cb2e9801b5fd6ed0a89256bac12b2bbb3e4664b85a88b0ec5bef", + "https://deno.land/std@0.170.0/crypto/_fnv/index.ts": "a8f6a361b4c6d54e5e89c16098f99b6962a1dd6ad1307dbc97fa1ecac5d7060a", + "https://deno.land/std@0.170.0/crypto/_fnv/util.ts": "4848313bed7f00f55be3cb080aa0583fc007812ba965b03e4009665bde614ce3", + "https://deno.land/std@0.170.0/crypto/_wasm/lib/deno_std_wasm_crypto.generated.mjs": "71c1ac20f32fdbdc9b31a14917779c7fa392dbc8b050059cbb2c35b400b975b1", + "https://deno.land/std@0.170.0/crypto/_wasm/mod.ts": "b49ec171049bbbaaed3c5a5a71dfcb3d09f880607c8d9c517638d0443bd0f874", + "https://deno.land/std@0.170.0/crypto/keystack.ts": "ee8ee58ca876f619cef56f40127405f77f9738f50a459b6ba44234b3a2cb6cc8", + "https://deno.land/std@0.170.0/crypto/mod.ts": "f953406904aa4257a6c76c40eb74b47d8dad83af8a77f33a4639cbac6d311baf", + "https://deno.land/std@0.170.0/crypto/timing_safe_equal.ts": "3784958e40a5fe10429a68b75cc5f8d34356bf0bc2eb93c80c3033e2a6f17821", + "https://deno.land/std@0.170.0/crypto/util.ts": "93fc9dbaa62421538ad60ee65337cbf1d8a0298fac3c4f70b37e5627e7e129eb", + "https://deno.land/std@0.170.0/encoding/base64.ts": "8605e018e49211efc767686f6f687827d7f5fd5217163e981d8d693105640d7a", + "https://deno.land/std@0.170.0/encoding/base64url.ts": "0283b12fcd306c11e3cf26fc022fecc800c6acc19704ea8bdb3908898fcd06d6", + "https://deno.land/std@0.170.0/encoding/hex.ts": "b51e99b684486a3ad2406807a8be953f5ef8bac95af202774a759f9fcf0d87a6", + "https://deno.land/std@0.170.0/fs/_util.ts": "fdc156f897197f261a1c096dcf8ff9267ed0ff42bd5b31f55053a4763a4bae3b", + "https://deno.land/std@0.170.0/fs/copy.ts": "c6303e52f544c81271c929931f5b59c9cfa4f81930719d2d3f777188c38aac9f", + "https://deno.land/std@0.170.0/fs/empty_dir.ts": "453d6232ff109f2afb5e57ec14c3228e399205c1b408d85536aed7230290c414", + "https://deno.land/std@0.170.0/fs/ensure_dir.ts": "5e9e3d7da7fc5b5e391e6d9ccead17086d76e82fb46ccc7cc9b9ee3491bab6e0", + "https://deno.land/std@0.170.0/fs/ensure_file.ts": "76ef3a8ebef60d8da1fc4316fcb8e20c1b6f52b1baed3a9692ad3b0d1a9a1b03", + "https://deno.land/std@0.170.0/fs/ensure_link.ts": "adc8919063e26819f5971a0010fedc1bfd71d6350a24db1a36dff432bc35c7d7", + "https://deno.land/std@0.170.0/fs/ensure_symlink.ts": "5273557b8c50be69477aa9cb003b54ff2240a336db52a40851c97abce76b96ab", + "https://deno.land/std@0.170.0/fs/eol.ts": "6e784ff8120c8d5589cb258e56dc39bc5b408ac9827a2e914163cbf9f2e3ce92", + "https://deno.land/std@0.170.0/fs/exists.ts": "6a447912e49eb79cc640adacfbf4b0baf8e17ede6d5bed057062ce33c4fa0d68", + "https://deno.land/std@0.170.0/fs/expand_glob.ts": "3a92ee4921d2b063b8dfefd1d87c35bf81126f0f1cb16e5a0f4e9ecb88ec6fe3", + "https://deno.land/std@0.170.0/fs/mod.ts": "79c209c6e66903b3426f9245a4f216380a0ed47ffe9d253f5a61a0bc9ad1f314", + "https://deno.land/std@0.170.0/fs/move.ts": "02ab1fc9b744da8b496f406e9fc77b0bf7960b6faaa7ec9f5fb0a129e5bef215", + "https://deno.land/std@0.170.0/fs/walk.ts": "677eac2e5386217a7a4e7526769ae28b41ff4ae7a3cd0389f3aa4eb662545edd", + "https://deno.land/std@0.170.0/path/_constants.ts": "df1db3ffa6dd6d1252cc9617e5d72165cd2483df90e93833e13580687b6083c3", + "https://deno.land/std@0.170.0/path/_interface.ts": "ee3b431a336b80cf445441109d089b70d87d5e248f4f90ff906820889ecf8d09", + "https://deno.land/std@0.170.0/path/_util.ts": "d16be2a16e1204b65f9d0dfc54a9bc472cafe5f4a190b3c8471ec2016ccd1677", + "https://deno.land/std@0.170.0/path/common.ts": "bee563630abd2d97f99d83c96c2fa0cca7cee103e8cb4e7699ec4d5db7bd2633", + "https://deno.land/std@0.170.0/path/glob.ts": "81cc6c72be002cd546c7a22d1f263f82f63f37fe0035d9726aa96fc8f6e4afa1", + "https://deno.land/std@0.170.0/path/mod.ts": "cf7cec7ac11b7048bb66af8ae03513e66595c279c65cfa12bfc07d9599608b78", + "https://deno.land/std@0.170.0/path/posix.ts": "b859684bc4d80edfd4cad0a82371b50c716330bed51143d6dcdbe59e6278b30c", + "https://deno.land/std@0.170.0/path/separator.ts": "fe1816cb765a8068afb3e8f13ad272351c85cbc739af56dacfc7d93d710fe0f9", + "https://deno.land/std@0.170.0/path/win32.ts": "7cebd2bda6657371adc00061a1d23fdd87bcdf64b4843bb148b0b24c11b40f69" + } +} diff --git a/import_map.json b/import_map.json new file mode 100644 index 0000000..0bbc3d3 --- /dev/null +++ b/import_map.json @@ -0,0 +1,5 @@ +{ + "imports": { + "std/": "https://deno.land/std@0.170.0/" + } +} diff --git a/lib/command.rb b/lib/command.rb deleted file mode 100644 index 3a47669..0000000 --- a/lib/command.rb +++ /dev/null @@ -1,188 +0,0 @@ -module NulDoc - class Command - def initialize(config) - @config = config - @content_dir = @config[:content_dir] - @dest_dir = @config[:dest_dir] - @static_dir = @config[:static_dir] - @template_dir = @config[:template_dir] - @parser = NulDoc::Parser.new( - { - 'stylesheets' => stylesheets, - 'author' => @config[:author], - 'site-copyright-year' => @config[:site_copyright_year], - 'site-name' => @config[:site_name], - }, - @content_dir, - @template_dir, - ) - end - - def run - posts = generate_posts(@content_dir + '/posts') - generate_tags(posts) - generate_post_list(posts) - end - - private - - def generate_posts(source_dir) - post_files = collect_post_files(source_dir) - posts = parse_posts(post_files) - output_posts(posts) - posts - end - - def collect_post_files(source_dir) - file_paths = [] - Dir.glob('**/*.adoc', base: source_dir, sort: true) do |path| - file_paths << "#{source_dir}/#{path}" - end - file_paths - end - - def parse_posts(post_file_paths) - post_file_paths.map { @parser.parse_file(_1, 'post') } - end - - def output_posts(posts) - posts.each do |post| - destination_file_path = post.attributes['source-file-path'] - .sub(@content_dir, @dest_dir) - .sub('.adoc', '/index.html') - destination_dir = File.dirname(destination_file_path) - unless Dir.exist?(destination_dir) - FileUtils.makedirs(destination_dir) - end - open(destination_file_path, 'w') do |f| - f.puts(post.convert) - end - end - end - - def generate_tags(posts) - tags_and_posts = collect_tags(posts) - tag_docs = build_tag_docs(tags_and_posts) - output_tags(tag_docs) - end - - def collect_tags(posts) - tags_and_posts = {} - posts.each do |post| - post.attributes['tags'].each do |tag| - tags_and_posts[tag] ||= [] - tags_and_posts[tag] << post - end - end - tags_and_posts - .transform_values {|posts| - posts.sort_by {|post| - post.attributes['revision-history'].first.date # created_at - }.reverse - } - .sort_by {|tag, _| tag.slug } - .to_h - end - - def build_tag_docs(tags_and_posts) - tags_and_posts.map do |tag, posts| - [tag, build_tag_doc(tag, posts)] - end - end - - def build_tag_doc(tag, posts) - converter = NulDoc::HTMLConverter.new(nil, { template_dirs: [@template_dir] }) - converter.convert_document( - (Class.new do - def initialize(config, tag, posts, stylesheets) - @config = config - @tag = tag - @posts = posts - @stylesheets = stylesheets - end - def attr(name) - case name - when 'document-type'; 'tag' - when 'stylesheets'; @stylesheets - when 'author'; @config[:author] - when 'site-copyright-year'; @config[:site_copyright_year] - when 'site-name'; @config[:site_name] - when 'lang'; 'ja-JP' # TODO - when 'copyright-year'; @posts.last.attributes['revision-history'].first.date.year - when 'description'; "タグ「#{@tag.label}」のついた記事一覧" - else raise "Unknown attr: #{name}" - end - end - def title; @tag.label; end - def posts; @posts; end - end).new(@config, tag, posts, stylesheets) - ) - end - - def output_tags(tag_docs) - tag_docs.each do |tag, html| - destination_file_path = "#{@dest_dir}/tags/#{tag.slug}/index.html" - destination_dir = File.dirname(destination_file_path) - unless Dir.exist?(destination_dir) - FileUtils.makedirs(destination_dir) - end - open(destination_file_path, 'w') do |f| - f.puts(html) - end - end - end - - def generate_post_list(posts) - html = build_post_list_doc(posts) - output_post_list(html) - end - - def build_post_list_doc(posts) - converter = NulDoc::HTMLConverter.new(nil, { template_dirs: [@template_dir] }) - converter.convert_document( - (Class.new do - def initialize(config, posts, stylesheets) - @config = config - @posts = posts - @stylesheets = stylesheets - end - def attr(name) - case name - when 'document-type'; 'post_list' - when 'stylesheets'; @stylesheets - when 'author'; @config[:author] - when 'site-copyright-year'; @config[:site_copyright_year] - when 'site-name'; @config[:site_name] - when 'lang'; 'ja-JP' # TODO - when 'copyright-year'; @config[:site_copyright_year] - when 'description'; '投稿した記事の一覧' - else raise "Unknown attr: #{name}" - end - end - def title; '投稿一覧'; end - def posts; @posts; end - end).new(@config, posts.reverse, stylesheets) - ) - end - - def output_post_list(html) - destination_file_path = "#{@dest_dir}/posts/index.html" - destination_dir = File.dirname(destination_file_path) - unless Dir.exist?(destination_dir) - FileUtils.makedirs(destination_dir) - end - open(destination_file_path, 'w') do |f| - f.puts(html) - end - end - - def stylesheets - stylesheet_file_names = %w[hl.css style.css custom.css] - stylesheet_file_names.map {|ss_file_name| - ss_file_path = "#{@static_dir}/#{ss_file_name}" - hash = Digest::MD5.file(ss_file_path).hexdigest - "/#{ss_file_name}?#{hash}" - } - end - end -end diff --git a/lib/extensions/document_title_processor.rb b/lib/extensions/document_title_processor.rb deleted file mode 100644 index fd25844..0000000 --- a/lib/extensions/document_title_processor.rb +++ /dev/null @@ -1,15 +0,0 @@ -module Nuldoc - module Extensions - class DocumentTitleProcessor < Asciidoctor::Extensions::TreeProcessor - def process(doc) - doc.title = substitute_document_title(doc.title) - end - - private - - def substitute_document_title(title) - title.sub(/\A\[(.+?)\] /, '【\1】') - end - end - end -end diff --git a/lib/extensions/lang_attribute_processor.rb b/lib/extensions/lang_attribute_processor.rb deleted file mode 100644 index 65511bc..0000000 --- a/lib/extensions/lang_attribute_processor.rb +++ /dev/null @@ -1,9 +0,0 @@ -module Nuldoc - module Extensions - class LangAttributeProcessor < Asciidoctor::Extensions::TreeProcessor - def process(doc) - doc.attributes['lang'] ||= 'ja-JP' - end - end - end -end diff --git a/lib/extensions/revision_history_processor.rb b/lib/extensions/revision_history_processor.rb deleted file mode 100644 index f416de0..0000000 --- a/lib/extensions/revision_history_processor.rb +++ /dev/null @@ -1,27 +0,0 @@ -module Nuldoc - module Extensions - class RevisionHistoryProcessor < Asciidoctor::Extensions::TreeProcessor - def process(doc) - revisions = [] - i = 1 - loop do - break unless (rev = doc.attributes["revision-#{i}"]) - revisions << parse_revision(rev) - i += 1 - end - doc.attributes['revision-history'] = revisions - end - - private - - def parse_revision(rev) - m = rev.match(/\A(\d\d\d\d-\d\d-\d\d) (.*)\z/) - raise unless m - Revision.new( - date: Date.parse(m[1], '%Y-%m-%d'), - remark: m[2], - ) - end - end - end -end diff --git a/lib/extensions/section_id_validator.rb b/lib/extensions/section_id_validator.rb deleted file mode 100644 index 2ad496c..0000000 --- a/lib/extensions/section_id_validator.rb +++ /dev/null @@ -1,29 +0,0 @@ -module Nuldoc - module Extensions - class SectionIdValidator < Asciidoctor::Extensions::TreeProcessor - def process(doc) - errors = [] - (doc.find_by(context: :section) {_1.level > 0}).each do |section| - errors << validate_section(section) - end - error_message = errors.compact.join("\n") - unless error_message.empty? - raise "SectionIdValidator (#{doc.attributes['source-file-path']}):\n#{error_message}" - end - end - - private - - def validate_section(section) - id = section.id - unless id - return "Section '#{section.title}': each section MUST have an id." - end - unless id.match?(/\A[-0-9a-z]+\z/) - return "Section '#{section.title}' (##{id}): section id MUST consist of either hyphen, digits or lowercases." - end - nil - end - end - end -end diff --git a/lib/extensions/source_id_processor.rb b/lib/extensions/source_id_processor.rb deleted file mode 100644 index 13813e0..0000000 --- a/lib/extensions/source_id_processor.rb +++ /dev/null @@ -1,12 +0,0 @@ -module Nuldoc - module Extensions - class SourceIdProcessor < Asciidoctor::Extensions::TreeProcessor - def process(doc) - errors = [] - (doc.find_by(context: :listing) {_1.style == 'source'}).each do |source| - source.id = "source.#{source.id}" - end - end - end - end -end diff --git a/lib/extensions/source_id_validator.rb b/lib/extensions/source_id_validator.rb deleted file mode 100644 index 6e04deb..0000000 --- a/lib/extensions/source_id_validator.rb +++ /dev/null @@ -1,32 +0,0 @@ -module Nuldoc - module Extensions - class SourceIdValidator < Asciidoctor::Extensions::TreeProcessor - def process(doc) - errors = [] - (doc.find_by(context: :listing) {_1.style == 'source'}).each do |source| - errors << validate_section(source) - end - error_message = errors.compact.join("\n") - unless error_message.empty? - raise "SourceIdValidator (#{doc.attributes['source-file-path']}):\n#{error_message}" - end - end - - private - - def validate_section(source) - id = source.id - unless id - return "Each source MUST have an id." - end - if id.start_with?('source.') - return "Source id (##{id}) MUST NOT start with 'source.', which is appended by `nul`." - end - unless id.match?(/\A[-0-9a-z]+\z/) - return "Source id (##{id}) MUST consist of either hypen, digits or lowercases." - end - nil - end - end - end -end diff --git a/lib/extensions/tags_processor.rb b/lib/extensions/tags_processor.rb deleted file mode 100644 index efbd2a8..0000000 --- a/lib/extensions/tags_processor.rb +++ /dev/null @@ -1,20 +0,0 @@ -module Nuldoc - module Extensions - class TagsProcessor < Asciidoctor::Extensions::TreeProcessor - def process(doc) - doc.attributes['tags'] = convert_tags(doc.attributes['tags']) - end - - private - - def convert_tags(tags) - return [] unless tags - - tags - .split(',') - .map(&:strip) - .map { Tag.from_slug(_1) } - end - end - end -end diff --git a/lib/html_converter.rb b/lib/html_converter.rb deleted file mode 100644 index 126d72a..0000000 --- a/lib/html_converter.rb +++ /dev/null @@ -1,16 +0,0 @@ -module NulDoc - class HTMLConverter < (Asciidoctor::Converter.for 'html5') - register_for 'html5' - - def initialize(backend, opts) - super - @template_dir = opts[:template_dirs].first - end - - def convert_document(node) - template_file_name = "document__#{node.attr('document-type')}.html.erb" - erb = Tilt::ERBTemplate.new("#{@template_dir}/#{template_file_name}") - erb.render(node, {}) - end - end -end diff --git a/lib/parser.rb b/lib/parser.rb deleted file mode 100644 index f241c4d..0000000 --- a/lib/parser.rb +++ /dev/null @@ -1,59 +0,0 @@ -module NulDoc - class Parser - def initialize(common_attributes, content_dir, template_dir) - @common_attributes = common_attributes - @content_dir = content_dir - @template_dir = template_dir - end - - def parse_file(file_path, document_type) - Asciidoctor.load_file( - file_path, - backend: :html5, - doctype: :article, - standalone: true, - safe: :unsafe, - template_dirs: [@template_dir], - template_engine: 'erb', - template_engine_options: { erb: { trim: '<>' } }, - attributes: @common_attributes.merge({ - 'document-type' => document_type, - 'source-file-path' => file_path, - 'href' => file_path.sub(@content_dir, '').sub('.adoc', '/'), - 'source-highlighter' => 'rouge', - 'reproducible' => true, - 'sectids' => false, - }), - extension_registry: Asciidoctor::Extensions.create do - tree_processor Nuldoc::Extensions::RevisionHistoryProcessor - tree_processor Nuldoc::Extensions::DocumentTitleProcessor - tree_processor Nuldoc::Extensions::LangAttributeProcessor - # tree_processor Nuldoc::Extensions::SectionIdValidator - # tree_processor Nuldoc::Extensions::SourceIdValidator - tree_processor Nuldoc::Extensions::TagsProcessor - - # MUST BE AT THE END - tree_processor Nuldoc::Extensions::SourceIdProcessor - end, - ) - end - - def parse_string(s, copyright_year) - Asciidoctor.convert( - s, - backend: :html5, - doctype: :article, - safe: :unsafe, - template_dirs: [@template_dir], - template_engine: 'erb', - attributes: @common_attributes.merge({ - 'copyright-year' => copyright_year, - }), - extension_registry: Asciidoctor::Extensions.create do - tree_processor Nuldoc::Extensions::LangAttributeProcessor - tree_processor Nuldoc::Extensions::TagsProcessor - end, - ) - end - end -end diff --git a/lib/revision.rb b/lib/revision.rb deleted file mode 100644 index b986a14..0000000 --- a/lib/revision.rb +++ /dev/null @@ -1,3 +0,0 @@ -module Nuldoc - Revision = Struct.new(:date, :remark, keyword_init: true) -end diff --git a/lib/tag.rb b/lib/tag.rb deleted file mode 100644 index a7c13b2..0000000 --- a/lib/tag.rb +++ /dev/null @@ -1,26 +0,0 @@ -module Nuldoc - Tag = Struct.new(:slug, :label, keyword_init: true) do - LABELS = { - 'conference' => 'カンファレンス', - 'cpp' => 'C++', - 'cpp17' => 'C++ 17', - 'note-to-self' => '備忘録', - 'php' => 'PHP', - 'phpcon' => 'PHP カンファレンス', - 'phperkaigi' => 'PHPerKaigi', - 'python' => 'Python', - 'python3' => 'Python 3', - 'ruby' => 'Ruby', - 'ruby3' => 'Ruby 3', - 'rust' => 'Rust', - 'vim' => 'Vim', - } - - def self.from_slug(slug) - Tag.new( - slug: slug, - label: (LABELS[slug] || raise("No label for tag '#{slug}'")), - ) - end - end -end @@ -0,0 +1,8 @@ +#!/bin/bash + +base_dir="$(dirname "$BASH_SOURCE")" + +deno run \ + --allow-read="$base_dir" \ + --allow-write="$base_dir/public" \ + "$base_dir/nuldoc-src/main.ts" diff --git a/nuldoc-src/command.ts b/nuldoc-src/command.ts new file mode 100644 index 0000000..7495427 --- /dev/null +++ b/nuldoc-src/command.ts @@ -0,0 +1,142 @@ +import { dirname, join, joinGlobs } from "std/path/mod.ts"; +import { ensureDir } from "std/fs/mod.ts"; +import { expandGlob } from "std/fs/expand_glob.ts"; +import { Config } from "./config.ts"; +import { parseDocBookFile } from "./docbook/parse.ts"; +import { writeHtmlFile } from "./html.ts"; +import { Document } from "./docbook/document.ts"; +import convertPost from "./templates/post.ts"; +import convertPostList from "./templates/post_list.ts"; +import convertTag from "./templates/tag.ts"; + +export async function run(config: Config) { + const posts = await generatePosts(config); + await generateTags(posts, config); + await generatePostList(posts, config); +} + +async function generatePosts(config: Config) { + const sourceDir = join(Deno.cwd(), config.locations.contentDir, "posts"); + const postFiles = await collectPostFiles(sourceDir); + const posts = await parsePosts(postFiles, config); + await outputPosts(posts, config); + return posts; +} + +async function collectPostFiles(sourceDir: string): Promise<string[]> { + const filePaths = []; + const globPattern = joinGlobs([sourceDir, "**", "*.xml"]); + for await (const entry of expandGlob(globPattern)) { + filePaths.push(entry.path); + } + return filePaths; +} + +async function parsePosts( + postFiles: string[], + config: Config, +): Promise<Document[]> { + const posts = []; + for (const postFile of postFiles) { + posts.push( + await convertPost(await parseDocBookFile(postFile, config), config), + ); + } + return posts; +} + +async function outputPosts(posts: Document[], config: Config) { + const cwd = Deno.cwd(); + const contentDir = join(cwd, config.locations.contentDir); + const destDir = join(cwd, config.locations.destDir); + for (const post of posts) { + const destFilePath = join( + post.sourceFilePath.replace(contentDir, destDir).replace(".xml", ""), + "index.html", + ); + ensureDir(dirname(destFilePath)); + await writeHtmlFile(post, destFilePath); + } +} + +async function generatePostList(posts: Document[], config: Config) { + const postList = await buildPostListDoc(posts, config); + await outputPostList(postList, config); +} + +async function buildPostListDoc( + posts: Document[], + config: Config, +): Promise<Document> { + return await convertPostList(posts, config); +} + +async function outputPostList(postList: Document, config: Config) { + const cwd = Deno.cwd(); + const destDir = join(cwd, config.locations.destDir); + const destFilePath = join(destDir, "posts", "index.html"); + ensureDir(dirname(destFilePath)); + await writeHtmlFile(postList, destFilePath); +} + +async function generateTags(posts: Document[], config: Config) { + const tagsAndPosts = collectTags(posts); + const tagDocs = await buildTagDocs(tagsAndPosts, config); + await outputTags(tagDocs, config); +} + +function collectTags(posts: Document[]): [string, Document[]][] { + const tagsAndPosts = new Map(); + for (const post of posts) { + for (const tag of post.tags) { + if (!tagsAndPosts.has(tag)) { + tagsAndPosts.set(tag, []); + } + tagsAndPosts.get(tag).push(post); + } + } + + const result: [string, Document[]][] = []; + for (const tag of Array.from(tagsAndPosts.keys()).sort()) { + result.push([ + tag, + tagsAndPosts.get(tag).sort((a: Document, b: Document) => { + const ta = a.revisions[0].date; + const tb = b.revisions[0].date; + if (ta > tb) return -1; + if (ta < tb) return 1; + return 0; + }), + ]); + } + return result; +} + +async function buildTagDocs( + tagsAndPosts: [string, Document[]][], + config: Config, +): Promise<[string, Document][]> { + const docs: [string, Document][] = []; + for (const [tag, posts] of tagsAndPosts) { + docs.push([tag, await buildTagDoc(tag, posts, config)]); + } + return docs; +} + +async function buildTagDoc( + tag: string, + posts: Document[], + config: Config, +): Promise<Document> { + return await convertTag(tag, posts, config); +} + +async function outputTags(tagDocs: [string, Document][], config: Config) { + const cwd = Deno.cwd(); + const destDir = join(cwd, config.locations.destDir); + for (const [tag, tagDoc] of tagDocs) { + const destFilePath = join(destDir, "tags", tag, "index.html"); + ensureDir(dirname(destFilePath)); + await writeHtmlFile(tagDoc, destFilePath); + } +} diff --git a/nuldoc-src/config.ts b/nuldoc-src/config.ts new file mode 100644 index 0000000..74521b3 --- /dev/null +++ b/nuldoc-src/config.ts @@ -0,0 +1,35 @@ +export const config = { + locations: { + contentDir: "/content", + destDir: "/public", + staticDir: "/static", + templateDir: "/templates", + }, + rendering: { + html: { + indentWidth: 2, + }, + }, + blog: { + author: "nsfisis", + siteName: "REPL: Rest-Eat-Program Loop", + siteCopyrightYear: 2021, + tagLabels: { + "conference": "カンファレンス", + "cpp": "C++", + "cpp17": "C++ 17", + "note-to-self": "備忘録", + "php": "PHP", + "phpcon": "PHP カンファレンス", + "phperkaigi": "PHPerKaigi", + "python": "Python", + "python3": "Python 3", + "ruby": "Ruby", + "ruby3": "Ruby 3", + "rust": "Rust", + "vim": "Vim", + }, + }, +}; + +export type Config = typeof config; diff --git a/nuldoc-src/docbook/document.ts b/nuldoc-src/docbook/document.ts new file mode 100644 index 0000000..b779ac7 --- /dev/null +++ b/nuldoc-src/docbook/document.ts @@ -0,0 +1,108 @@ +import { join } from "std/path/mod.ts"; +import { Config } from "../config.ts"; +import { DocBookError } from "../errors.ts"; +import { Revision } from "../revision.ts"; +import { + Element, + findChildElements, + findFirstChildElement, + innerText, +} from "../dom.ts"; + +export type Document = { + root: Element; + sourceFilePath: string; + link: string; + title: string; + summary: string; // TODO: should it be markup text? + tags: string[]; + revisions: Revision[]; +}; + +export function createNewDocumentFromRootElement( + root: Element, + sourceFilePath: string, + config: Config, +): Document { + const article = findFirstChildElement(root, "article"); + if (!article) { + throw new DocBookError( + `[docbook.new] <article> element not found`, + ); + } + const info = findFirstChildElement(article, "info"); + if (!info) { + throw new DocBookError( + `[docbook.new] <info> element not found`, + ); + } + + const titleElement = findFirstChildElement(info, "title"); + if (!titleElement) { + throw new DocBookError( + `[docbook.new] <title> element not found`, + ); + } + const title = innerText(titleElement).trim(); + const abstractElement = findFirstChildElement(info, "abstract"); + if (!abstractElement) { + throw new DocBookError( + `[docbook.new] <abstract> element not found`, + ); + } + const summary = innerText(abstractElement).trim(); + const keywordsetElement = findFirstChildElement(info, "keywordset"); + let tags: string[]; + if (!keywordsetElement) { + tags = []; + } else { + tags = findChildElements(keywordsetElement, "keyword").map((x) => + innerText(x).trim() + ); + } + const revhistoryElement = findFirstChildElement(info, "revhistory"); + if (!revhistoryElement) { + throw new DocBookError( + `[docbook.new] <revhistory> element not found`, + ); + } + const revisions = findChildElements(revhistoryElement, "revision").map( + (x, i) => { + const dateElement = findFirstChildElement(x, "date"); + if (!dateElement) { + throw new DocBookError( + `[docbook.new] <date> element not found`, + ); + } + const revremarkElement = findFirstChildElement(x, "revremark"); + if (!revremarkElement) { + throw new DocBookError( + `[docbook.new] <revremark> element not found`, + ); + } + return { + number: i + 1, + date: innerText(dateElement).trim(), + remark: innerText(revremarkElement).trim(), + }; + }, + ); + if (revisions.length === 0) { + throw new DocBookError( + `[docbook.new] <revision> element not found`, + ); + } + + const cwd = Deno.cwd(); + const contentDir = join(cwd, config.locations.contentDir); + const link = sourceFilePath.replace(contentDir, "").replace(".xml", "/"); + return { + root: root, + title: title, + summary: summary, + tags: tags, + revisions: revisions, + sourceFilePath: sourceFilePath, + link: link, + }; +} diff --git a/nuldoc-src/docbook/parse.ts b/nuldoc-src/docbook/parse.ts new file mode 100644 index 0000000..bce317e --- /dev/null +++ b/nuldoc-src/docbook/parse.ts @@ -0,0 +1,21 @@ +import { Config } from "../config.ts"; +import { parseXmlFile } from "../xml.ts"; +import { DocBookError, XmlParseError } from "../errors.ts"; +import { createNewDocumentFromRootElement, Document } from "./document.ts"; +import toHtml from "./to_html.ts"; + +export async function parseDocBookFile( + filePath: string, + config: Config, +): Promise<Document> { + try { + const root = await parseXmlFile(filePath); + const doc = createNewDocumentFromRootElement(root, filePath, config); + return toHtml(doc); + } catch (e) { + if (e instanceof DocBookError || e instanceof XmlParseError) { + e.message = `${e.message} in ${filePath}`; + } + throw e; + } +} diff --git a/nuldoc-src/docbook/to_html.ts b/nuldoc-src/docbook/to_html.ts new file mode 100644 index 0000000..31c419d --- /dev/null +++ b/nuldoc-src/docbook/to_html.ts @@ -0,0 +1,275 @@ +import { Document } from "./document.ts"; +import { DocBookError } from "../errors.ts"; +import { + Element, + findFirstChildElement, + forEachChild, + forEachChildRecursively, + Node, + removeChildElements, +} from "../dom.ts"; + +export default function toHtml(doc: Document): Document { + removeArticleInfo(doc); + removeArticleAttributes(doc); + removeUnnecessaryTextNode(doc); + transformElementNames(doc, "emphasis", "em"); + transformElementNames(doc, "informaltable", "table"); + transformElementNames(doc, "itemizedlist", "ul"); + transformElementNames(doc, "link", "a"); + transformElementNames(doc, "listitem", "li"); + transformElementNames(doc, "literal", "code"); + transformElementNames(doc, "orderedlist", "ol"); + transformElementNames(doc, "simpara", "p"); + transformAttributeNames(doc, "xml:id", "id"); + transformSectionIdAttribute(doc); + setSectionTitleAnchor(doc); + transformSectionTitleElement(doc); + transformProgramListingElement(doc); + transformLiteralLayoutElement(doc); + transformNoteElement(doc); + setDefaultLangAttribute(doc); + traverseFootnotes(doc); + return doc; +} + +function removeArticleInfo(doc: Document) { + const article = findFirstChildElement(doc.root, "article"); + if (!article) { + throw new DocBookError( + `[docbook.tohtml] <article> element not found`, + ); + } + removeChildElements(article, "info"); +} + +function removeArticleAttributes(doc: Document) { + const article = findFirstChildElement(doc.root, "article"); + if (!article) { + throw new DocBookError( + `[docbook.tohtml] <article> element not found`, + ); + } + article.attributes.delete("xmlns"); + article.attributes.delete("xmlns:xl"); + article.attributes.delete("version"); +} + +function removeUnnecessaryTextNode(doc: Document) { + const g = (n: Node) => { + if (n.kind !== "element") { + return; + } + + let changed = true; + while (changed) { + changed = false; + if (n.children.length === 0) { + break; + } + const firstChild = n.children[0]; + if (firstChild.kind === "text" && firstChild.content.trim() === "") { + n.children.shift(); + changed = true; + } + if (n.children.length === 0) { + break; + } + const lastChild = n.children[n.children.length - 1]; + if (lastChild.kind === "text" && lastChild.content.trim() === "") { + n.children.pop(); + changed = true; + } + } + + forEachChild(n, g); + }; + forEachChild(doc.root, g); +} + +function transformElementNames( + doc: Document, + from: string, + to: string, +) { + forEachChildRecursively(doc.root, (n) => { + if (n.kind === "element" && n.name === from) { + n.name = to; + } + }); +} + +function transformAttributeNames( + doc: Document, + from: string, + to: string, +) { + forEachChildRecursively(doc.root, (n) => { + if (n.kind !== "element") { + return; + } + const value = n.attributes.get(from) as string; + if (value !== undefined) { + n.attributes.delete(from); + n.attributes.set(to, value); + } + }); +} + +function transformSectionIdAttribute(doc: Document) { + forEachChildRecursively(doc.root, (n) => { + if (n.kind !== "element" || n.name !== "section") { + return; + } + + const idAttr = n.attributes.get("id"); + n.attributes.set("id", `section--${idAttr}`); + }); +} + +function setSectionTitleAnchor(doc: Document) { + const sectionStack: Element[] = []; + const g = (c: Node) => { + if (c.kind !== "element") { + return; + } + + if (c.name === "section") { + sectionStack.push(c); + } + forEachChild(c, g); + if (c.name === "section") { + sectionStack.pop(); + } + if (c.name === "title") { + const currentSection = sectionStack[sectionStack.length - 1]; + if (!currentSection) { + throw new DocBookError( + "[docbook.tohtml] <title> element must be inside <section>", + ); + } + const sectionId = currentSection.attributes.get("id"); + const aElement: Element = { + kind: "element", + name: "a", + attributes: new Map(), + children: c.children, + }; + aElement.attributes.set("href", `#${sectionId}`); + c.children = [aElement]; + } + }; + forEachChild(doc.root, g); +} + +function transformSectionTitleElement(doc: Document) { + let sectionLevel = 1; + const g = (c: Node) => { + if (c.kind !== "element") { + return; + } + + if (c.name === "section") { + sectionLevel += 1; + c.attributes.set("--section-level", sectionLevel.toString()); + } + forEachChild(c, g); + if (c.name === "section") { + sectionLevel -= 1; + } + if (c.name === "title") { + c.name = `h${sectionLevel}`; + } + }; + forEachChild(doc.root, g); +} + +function transformProgramListingElement(doc: Document) { + forEachChildRecursively(doc.root, (n) => { + if (n.kind !== "element" || n.name !== "programlisting") { + return; + } + + n.name = "pre"; + const codeElement: Element = { + kind: "element", + name: "code", + attributes: new Map(), + children: n.children, + }; + n.children = [codeElement]; + }); +} + +function transformLiteralLayoutElement(doc: Document) { + forEachChildRecursively(doc.root, (n) => { + if (n.kind !== "element" || n.name !== "literallayout") { + return; + } + + n.name = "pre"; + const children = n.children; + const codeElement: Element = { + kind: "element", + name: "code", + attributes: new Map(), + children: children, + }; + n.children = [codeElement]; + }); +} + +function transformNoteElement(doc: Document) { + forEachChildRecursively(doc.root, (n) => { + if (n.kind !== "element" || n.name !== "note") { + return; + } + + const labelElement: Element = { + kind: "element", + name: "div", + attributes: new Map(), + children: [{ + kind: "text", + content: "Note", + }], + }; + const contentElement: Element = { + kind: "element", + name: "div", + attributes: new Map(), + children: n.children, + }; + n.name = "div"; + n.children = [ + labelElement, + contentElement, + ]; + }); +} + +function setDefaultLangAttribute(_doc: Document) { + // TODO + // if (!e.attributes.has("lang")) { + // e.attributes.set("lang", "ja-JP"); + // } +} + +function traverseFootnotes(doc: Document) { + forEachChildRecursively(doc.root, (n) => { + if (n.kind !== "element" || n.name !== "footnote") { + return; + } + + // TODO + // <footnote>x</footnote> + // + // <sup class="footnote">[<a id="_footnoteref_1" class="footnote" href="#_footnotedef_1">1</a>]</sup> + // + // <div class="footnote" id="_footnotedef_1"> + // <a href="#_footnoteref_1">1</a>. RAS syndrome + // </div> + n.name = "span"; + n.children = []; + }); +} diff --git a/nuldoc-src/dom.ts b/nuldoc-src/dom.ts new file mode 100644 index 0000000..51ef25a --- /dev/null +++ b/nuldoc-src/dom.ts @@ -0,0 +1,79 @@ +export type Text = { + kind: "text"; + content: string; +}; + +export type Element = { + kind: "element"; + name: string; + attributes: Map<string, string>; + children: Node[]; +}; + +export type Node = Element | Text; + +export function addClass(e: Element, klass: string) { + const classes = e.attributes.get("class"); + if (classes === undefined) { + e.attributes.set("class", klass); + } else { + const classList = classes.split(" "); + classList.push(klass); + classList.sort(); + e.attributes.set("class", classList.join(" ")); + } +} + +export function findFirstChildElement( + e: Element, + name: string, +): Element | null { + for (const c of e.children) { + if (c.kind === "element" && c.name === name) { + return c; + } + } + return null; +} + +export function findChildElements(e: Element, name: string): Element[] { + const cs = []; + for (const c of e.children) { + if (c.kind === "element" && c.name === name) { + cs.push(c); + } + } + return cs; +} + +export function removeChildElements(e: Element, name: string) { + e.children = e.children.filter((c) => + c.kind !== "element" || c.name !== name + ); +} + +export function innerText(e: Element): string { + let t = ""; + forEachChild(e, (c) => { + if (c.kind === "text") { + t += c.content; + } + }); + return t; +} + +export function forEachChild(e: Element, f: (n: Node) => void) { + for (const c of e.children) { + f(c); + } +} + +export function forEachChildRecursively(e: Element, f: (n: Node) => void) { + const g = (c: Node) => { + f(c); + if (c.kind === "element") { + forEachChild(c, g); + } + }; + forEachChild(e, g); +} diff --git a/nuldoc-src/errors.ts b/nuldoc-src/errors.ts new file mode 100644 index 0000000..4d107aa --- /dev/null +++ b/nuldoc-src/errors.ts @@ -0,0 +1,2 @@ +export class DocBookError extends Error {} +export class XmlParseError extends Error {} diff --git a/nuldoc-src/html.ts b/nuldoc-src/html.ts new file mode 100644 index 0000000..d127d29 --- /dev/null +++ b/nuldoc-src/html.ts @@ -0,0 +1,254 @@ +import { Document } from "./docbook/document.ts"; +import { Element, forEachChild, Node, Text } from "./dom.ts"; +import { DocBookError } from "./errors.ts"; + +export async function writeHtmlFile(dom: Document, filePath: string) { + await Deno.writeTextFile(filePath, toHtmlText(dom)); +} + +type Context = { + indentLevel: number; + isInPre: boolean; +}; + +type Dtd = { type: "block" | "inline"; auto_closing?: boolean }; + +function getDtd(name: string): Dtd { + switch (name) { + case "__root__": + return { type: "block" }; + case "a": + return { type: "inline" }; + case "article": + return { type: "block" }; + case "blockquote": + return { type: "block" }; + case "body": + return { type: "block" }; + case "br": + return { type: "block", auto_closing: true }; + case "code": + return { type: "inline" }; + case "div": + return { type: "block" }; + case "em": + return { type: "inline" }; + case "footer": + return { type: "block" }; + case "h1": + return { type: "inline" }; + case "h2": + return { type: "inline" }; + case "h3": + return { type: "inline" }; + case "h4": + return { type: "inline" }; + case "h5": + return { type: "inline" }; + case "h6": + return { type: "inline" }; + case "head": + return { type: "block" }; + case "header": + return { type: "block" }; + case "hr": + return { type: "block" }; + case "html": + return { type: "block" }; + case "li": + return { type: "block" }; + case "link": + return { type: "block", auto_closing: true }; + case "main": + return { type: "block" }; + case "meta": + return { type: "block", auto_closing: true }; + case "nav": + return { type: "block" }; + case "ol": + return { type: "block" }; + case "p": + return { type: "block" }; + case "pre": + return { type: "block" }; + case "section": + return { type: "block" }; + case "span": + return { type: "inline" }; + case "table": + return { type: "block" }; + case "tbody": + return { type: "block" }; + case "td": // TODO + return { type: "block" }; + case "tfoot": + return { type: "block" }; + case "thead": + return { type: "block" }; + case "time": + return { type: "inline" }; + case "title": // TODO + return { type: "inline" }; + case "tr": + return { type: "block" }; + case "ul": + return { type: "block" }; + default: + throw new DocBookError(`[html.write] Unknown element name: ${name}`); + } +} + +function isInlineNode(n: Node): boolean { + return n.kind === "text" || getDtd(n.name).type === "inline"; +} + +function isBlockNode(n: Node): boolean { + return !isInlineNode(n); +} + +function toHtmlText(dom: Document): string { + return `<!DOCTYPE html>\n` + nodeToHtmlText(dom.root, { + indentLevel: -1, + isInPre: false, + }); +} + +function nodeToHtmlText(n: Node, ctx: Context): string { + if (n.kind === "text") { + return textNodeToHtmlText(n, ctx); + } else { + return elementNodeToHtmlText(n, ctx); + } +} + +function textNodeToHtmlText(t: Text, ctx: Context): string { + const s = encodeSpecialCharacters(t.content); + if (ctx.isInPre) return s; + + // TODO: 日本語で改行するときはスペースを入れない + return s + .trim() + .replaceAll(/\n */g, " "); +} + +function encodeSpecialCharacters(s: string): string { + return s.replaceAll(/&(?!\w+;)/g, '&') + .replaceAll(/</g, '<') + .replaceAll(/>/g, '>') + .replaceAll(/'/g, ''') + .replaceAll(/"/g, '"'); +} + +function elementNodeToHtmlText(e: Element, ctx: Context): string { + const dtd = getDtd(e.name); + + let s = ""; + if (e.name !== "__root__") { + if (dtd.type === "block") { + s += indent(ctx); + } + s += `<${e.name}`; + const attributes = getElementAttributes(e); + if (attributes.length > 0) { + s += " "; + for (let i = 0; i < attributes.length; i++) { + const [name, value] = attributes[i]; + s += `${name}="${value}"`; + if (i !== attributes.length - 1) { + s += " "; + } + } + } + s += ">"; + if (dtd.type === "block") { + s += "\n"; + } + } + ctx.indentLevel += 1; + + let prevChild: Node | null = null; + if (e.name === "pre") { + ctx.isInPre = true; + } + forEachChild(e, (c) => { + if (dtd.type === "block") { + if (isInlineNode(c)) { + if (needsIndent(prevChild)) { + s += indent(ctx); + } + } else { + if (needsLineBreak(prevChild)) { + s += "\n"; + } + } + } + s += nodeToHtmlText(c, ctx); + prevChild = c; + }); + if (e.name === "pre") { + ctx.isInPre = false; + } + + ctx.indentLevel -= 1; + if (e.name !== "__root__" && !dtd.auto_closing) { + if (dtd.type === "block") { + if (needsLineBreak(prevChild)) { + s += "\n"; + } + s += indent(ctx); + } + s += `</${e.name}>`; + if (dtd.type === "block") { + s += "\n"; + } + } + return s; +} + +function indent(ctx: Context): string { + return " ".repeat(ctx.indentLevel); +} + +function getElementAttributes(e: Element): [string, string][] { + return [...e.attributes.entries()] + .filter((a) => !a[0].startsWith("--")) + .sort( + (a, b) => { + // Special rules: + if (e.name === "meta") { + if (a[0] === "content" && b[0] === "name") { + return 1; + } + if (a[0] === "name" && b[0] === "content") { + return -1; + } + } + if (e.name === "link") { + if (a[0] === "href" && b[0] === "rel") { + return 1; + } + if (a[0] === "rel" && b[0] === "href") { + return -1; + } + if (a[0] === "href" && b[0] === "type") { + return 1; + } + if (a[0] === "type" && b[0] === "href") { + return -1; + } + } + // General rules: + if (a[0] > b[0]) return 1; + else if (a[0] < b[0]) return -1; + else return 0; + }, + ); +} + +function needsIndent(prevChild: Node | null): boolean { + return !prevChild || isBlockNode(prevChild); +} + +function needsLineBreak(prevChild: Node | null): boolean { + return !needsIndent(prevChild); +} diff --git a/nuldoc-src/main.ts b/nuldoc-src/main.ts new file mode 100644 index 0000000..12a4ca9 --- /dev/null +++ b/nuldoc-src/main.ts @@ -0,0 +1,6 @@ +import { config } from "./config.ts"; +import { run } from "./command.ts"; + +if (import.meta.main) { + await run(config); +} diff --git a/nuldoc-src/revision.ts b/nuldoc-src/revision.ts new file mode 100644 index 0000000..1757694 --- /dev/null +++ b/nuldoc-src/revision.ts @@ -0,0 +1,5 @@ +export type Revision = { + number: number; + date: string; // TODO + remark: string; // TODO: should it be markup text? +}; diff --git a/nuldoc-src/templates/post.ts b/nuldoc-src/templates/post.ts new file mode 100644 index 0000000..9e1097d --- /dev/null +++ b/nuldoc-src/templates/post.ts @@ -0,0 +1,162 @@ +import { Element } from "../dom.ts"; +import { Document } from "../docbook/document.ts"; +import { Config } from "../config.ts"; +import { el, stylesheetLinkElement, text } from "./utils.ts"; + +function metaElement(attrs: [string, string][]): Element { + return el("meta", attrs); +} + +function linkElement(rel: string, href: string, type: string | null): Element { + const attrs: [string, string][] = [["rel", rel], ["href", href]]; + if (type !== null) { + attrs.push(["type", type]); + } + return el("link", attrs); +} + +export default async function convertPost( + doc: Document, + config: Config, +): Promise<Document> { + const headChildren = [ + metaElement([["charset", "UTF-8"]]), + metaElement([["name", "viewport"], [ + "content", + "width=device-width, initial-scale=1.0", + ]]), + metaElement([["name", "author"], ["content", config.blog.author]]), + metaElement([["name", "copyright"], [ + "content", + `© ${doc.revisions[0].date.substring(0, 4)} ${config.blog.author}`, + ]]), + metaElement([["name", "description"], ["content", doc.summary]]), + ]; + if (doc.tags.length !== 0) { + headChildren.push( + metaElement([["name", "keywords"], [ + "content", + doc.tags.map((slug) => + (config.blog.tagLabels as { [key: string]: string })[slug] + ).join(","), + ]]), + ); + } + headChildren.push(linkElement("icon", "/favicon.svg", "image/svg+xml")); + headChildren.push( + el("title", [], text(`${doc.title} | ${config.blog.siteName}`)), + ); + headChildren.push(await stylesheetLinkElement("/hl.css", config)); + headChildren.push(await stylesheetLinkElement("/style.css", config)); + headChildren.push(await stylesheetLinkElement("/custom.css", config)); + const head = el("head", [], ...headChildren); + const body = el( + "body", + [["class", "single"]], + el( + "header", + [["class", "header"]], + el( + "nav", + [["class", "nav"]], + el( + "p", + [["class", "logo"]], + el("a", [["href", "/"]], text(config.blog.siteName)), + ), + ), + ), + el( + "main", + [["class", "main"]], + el( + "article", + [["class", "post-single"]], + el( + "header", + [["class", "post-header"]], + el( + "h1", + [["class", "post-title"]], + text(doc.title), + ), + ...(doc.tags.length === 0 ? [] : [ + el( + "ul", + [["class", "post-tags"]], + ...doc.tags.map((slug) => + el( + "li", + [["class", "tag"]], + el( + "a", + [["href", `/tags/${slug}`]], + text( + (config.blog.tagLabels as { + [key: string]: string; + })[slug], + ), + ), + ) + ), + ), + ]), + ), + el( + "div", + [["class", "post-content"]], + el( + "section", + [], + el( + "h2", + [["id", "changelog"]], + text("更新履歴"), + ), + el( + "ol", + [], + ...doc.revisions.map((rev) => + el( + "li", + [["class", "revision"]], + el( + "time", + [["datetime", rev.date]], + text(rev.date), + ), + text(`: ${rev.remark}`), + ) + ), + ), + ), + // TODO: refactor + ...(doc.root.children[0] as Element).children, + // TODO: footnotes + // <div id="footnotes"> + // <% for footnote in footnotes %> + // <div class="footnote" id="_footnotedef_<%= footnote.index %>"> + // <a href="#_footnoteref_<%= footnote.index %>"><%= footnote.index %></a>. <%= footnote.text %> + // </div> + // <% end %> + // </div> + ), + ), + ), + el( + "footer", + [["class", "footer"]], + text( + `© ${config.blog.siteCopyrightYear} ${config.blog.author}`, + ), + ), + ); + const html = el( + "html", + [["lang", "ja-JP"]], + head, + body, + ); + doc.root = el("__root__", [], html); + return doc; +} diff --git a/nuldoc-src/templates/post_list.ts b/nuldoc-src/templates/post_list.ts new file mode 100644 index 0000000..bd91840 --- /dev/null +++ b/nuldoc-src/templates/post_list.ts @@ -0,0 +1,143 @@ +import { Element } from "../dom.ts"; +import { Document } from "../docbook/document.ts"; +import { Config } from "../config.ts"; +import { el, stylesheetLinkElement, text } from "./utils.ts"; + +function metaElement(attrs: [string, string][]): Element { + return el("meta", attrs); +} + +function linkElement(rel: string, href: string, type: string | null): Element { + const attrs: [string, string][] = [["rel", rel], ["href", href]]; + if (type !== null) { + attrs.push(["type", type]); + } + return el("link", attrs); +} + +export default async function convertPostList( + posts: Document[], + config: Config, +): Promise<Document> { + const doc = { + root: el("__root__", []), + sourceFilePath: "<postList>", + link: "/posts/", + title: "投稿一覧", + summary: "投稿した記事の一覧", + tags: [], + revisions: [], + }; + + const head = el( + "head", + [], + metaElement([["charset", "UTF-8"]]), + metaElement([["name", "viewport"], [ + "content", + "width=device-width, initial-scale=1.0", + ]]), + metaElement([["name", "author"], ["content", config.blog.author]]), + metaElement([["name", "copyright"], [ + "content", + `© ${config.blog.siteCopyrightYear} ${config.blog.author}`, + ]]), + metaElement([["name", "description"], ["content", doc.summary]]), + linkElement("icon", "/favicon.svg", "image/svg+xml"), + el("title", [], text(`${doc.title} | ${config.blog.siteName}`)), + await stylesheetLinkElement("/hl.css", config), + await stylesheetLinkElement("/style.css", config), + await stylesheetLinkElement("/custom.css", config), + ); + const body = el( + "body", + [["class", "list"]], + el( + "header", + [["class", "header"]], + el( + "nav", + [["class", "nav"]], + el( + "p", + [["class", "logo"]], + el("a", [["href", "/"]], text(config.blog.siteName)), + ), + ), + ), + el( + "main", + [["class", "main"]], + el( + "header", + [["class", "page-header"]], + el( + "h1", + [], + text(doc.title), + ), + ), + ...Array.from(posts).sort((a, b) => { + const ta = a.revisions[0].date; + const tb = b.revisions[0].date; + if (ta > tb) return -1; + if (ta < tb) return 1; + return 0; + }).map((post) => + el( + "article", + [["class", "post-entry"]], + el( + "a", + [["href", post.link]], + el( + "header", + [["class", "entry-header"]], + el("h2", [], text(post.title)), + ), + el( + "section", + [["class", "entry-content"]], + el("p", [], text(post.summary)), + ), + el( + "footer", + [["class", "entry-footer"]], + text("Posted on"), + el( + "time", + [["datetime", post.revisions[0].date]], + text(post.revisions[0].date), + ), + ...(post.revisions.length > 1 + ? [ + text(", updated on "), + el("time", [[ + "datetime", + post.revisions[post.revisions.length - 1].date, + ]], text(post.revisions[post.revisions.length - 1].date)), + ] + : []), + ), + ), + ) + ), + ), + el( + "footer", + [["class", "footer"]], + text( + `© ${config.blog.siteCopyrightYear} ${config.blog.author}`, + ), + ), + ); + const html = el( + "html", + [["lang", "ja-JP"]], + head, + body, + ); + + doc.root.children = [html]; + return doc; +} diff --git a/nuldoc-src/templates/tag.ts b/nuldoc-src/templates/tag.ts new file mode 100644 index 0000000..c3080e8 --- /dev/null +++ b/nuldoc-src/templates/tag.ts @@ -0,0 +1,137 @@ +import { Element } from "../dom.ts"; +import { Document } from "../docbook/document.ts"; +import { Config } from "../config.ts"; +import { el, stylesheetLinkElement, text } from "./utils.ts"; + +function metaElement(attrs: [string, string][]): Element { + return el("meta", attrs); +} + +function linkElement(rel: string, href: string, type: string | null): Element { + const attrs: [string, string][] = [["rel", rel], ["href", href]]; + if (type !== null) { + attrs.push(["type", type]); + } + return el("link", attrs); +} + +export default async function convertTag( + tag: string, + posts: Document[], + config: Config, +): Promise<Document> { + const tagLabel = (config.blog.tagLabels as { [key: string]: string })[tag]; + + const doc = { + root: el("__root__", []), + sourceFilePath: `<tag:${tag}>`, + link: `/tags/${tag}/`, + title: tagLabel, + summary: `タグ「${tagLabel}」のついた記事一覧`, + tags: [], + revisions: [], + }; + + const headChildren = [ + metaElement([["charset", "UTF-8"]]), + metaElement([["name", "viewport"], [ + "content", + "width=device-width, initial-scale=1.0", + ]]), + metaElement([["name", "author"], ["content", config.blog.author]]), + metaElement([["name", "copyright"], [ + "content", + `© ${ + posts[posts.length - 1].revisions[0].date.substring(0, 4) + } ${config.blog.author}`, + ]]), + metaElement([["name", "description"], [ + "content", + doc.summary, + ]]), + metaElement([["name", "keywords"], ["content", tagLabel]]), + linkElement("icon", "/favicon.svg", "image/svg+xml"), + el("title", [], text(`${doc.title} | ${config.blog.siteName}`)), + await stylesheetLinkElement("/hl.css", config), + await stylesheetLinkElement("/style.css", config), + await stylesheetLinkElement("/custom.css", config), + ]; + const head = el("head", [], ...headChildren); + const body = el( + "body", + [["class", "list"]], + el( + "header", + [["class", "header"]], + el( + "nav", + [["class", "nav"]], + el( + "p", + [["class", "logo"]], + el("a", [["href", "/"]], text(config.blog.siteName)), + ), + ), + ), + el( + "main", + [["class", "main"]], + el("header", [["class", "page-header"]], el("h1", [], text(tagLabel))), + ...posts.map((post) => + el( + "article", + [["class", "post-entry"]], + el( + "a", + [["href", post.link]], + el( + "header", + [["class", "entry-header"]], + el("h2", [], text(post.title)), + ), + el( + "section", + [["class", "entry-content"]], + el("p", [], text(post.summary)), + ), + el( + "footer", + [["class", "entry-footer"]], + text("Posted on"), + el( + "time", + [["datetime", post.revisions[0].date]], + text(post.revisions[0].date), + ), + ...(post.revisions.length > 1 + ? [ + text(", updated on "), + el("time", [[ + "datetime", + post.revisions[post.revisions.length - 1].date, + ]], text(post.revisions[post.revisions.length - 1].date)), + ] + : []), + ), + ), + ) + ), + ), + el( + "footer", + [["class", "footer"]], + text( + `© ${config.blog.siteCopyrightYear} ${config.blog.author}`, + ), + ), + ); + const html = el( + "html", + [["lang", "ja-JP"]], + head, + body, + ); + + doc.root.children = [html]; + return doc; +} diff --git a/nuldoc-src/templates/utils.ts b/nuldoc-src/templates/utils.ts new file mode 100644 index 0000000..a86803d --- /dev/null +++ b/nuldoc-src/templates/utils.ts @@ -0,0 +1,34 @@ +import { join } from "std/path/mod.ts"; +import { crypto, toHashString } from "std/crypto/mod.ts"; +import { Element, Node, Text } from "../dom.ts"; +import { Config } from "../config.ts"; + +export function text(content: string): Text { + return { + kind: "text", + content: content, + }; +} + +export function el( + name: string, + attrs: [string, string][], + ...children: Node[] +): Element { + return { + kind: "element", + name: name, + attributes: new Map(attrs), + children: children, + }; +} + +export async function stylesheetLinkElement( + fileName: string, + config: Config, +): Promise<Element> { + const filePath = join(Deno.cwd(), config.locations.staticDir, fileName); + const content = (await Deno.readFile(filePath)).buffer; + const hash = toHashString(await crypto.subtle.digest("MD5", content), "hex"); + return el("link", [["rel", "stylesheet"], ["href", `${fileName}?h=${hash}`]]); +} diff --git a/nuldoc-src/xml.ts b/nuldoc-src/xml.ts new file mode 100644 index 0000000..0bfbd8d --- /dev/null +++ b/nuldoc-src/xml.ts @@ -0,0 +1,211 @@ +import { Element, Node, Text } from "./dom.ts"; +import { XmlParseError } from "./errors.ts"; + +// TODO +// Support comment? <!-- --> +// Support CDATA + +export async function parseXmlFile(filePath: string): Promise<Element> { + const source = await Deno.readTextFile(filePath); + return parse({ source: source, index: 0 }); +} + +type Parser = { + source: string; + index: number; +}; + +function parse(p: Parser): Element { + parseXmlDeclaration(p); + skipWhitespaces(p); + const e = parseXmlElement(p); + const root: Element = { + kind: "element", + name: "__root__", + attributes: new Map(), + children: [e], + }; + return root; +} + +function parseXmlDeclaration(p: Parser) { + expect(p, "<?xml "); + skipTo(p, "?>"); + next(p, 2); +} + +function parseXmlElement(p: Parser): Element { + const { name, attributes, closed } = parseStartTag(p); + if (closed) { + return { + kind: "element", + name: name, + attributes: attributes, + children: [], + }; + } + const children = parseChildNodes(p); + parseEndTag(p, name); + + const thisElement: Element = { + kind: "element", + name: name, + attributes: attributes, + children: children, + }; + return thisElement; +} + +function parseChildNodes(p: Parser): Node[] { + const nodes = []; + while (true) { + const c = peek(p); + const c2 = peek2(p); + if (c === "<") { + if (c2 === "/") { + break; + } + nodes.push(parseXmlElement(p)); + } else { + nodes.push(parseTextNode(p)); + } + } + return nodes; +} + +function parseTextNode(p: Parser): Text { + const content = skipTo(p, "<"); + return { + kind: "text", + content: replaceEntityReferences(content), + }; +} + +function parseStartTag( + p: Parser, +): { name: string; attributes: Map<string, string>; closed: boolean } { + expect(p, "<"); + const name = parseIdentifier(p); + skipWhitespaces(p); + if (peek(p) === "/") { + expect(p, "/>"); + return { name: name, attributes: new Map(), closed: true }; + } + if (peek(p) === ">") { + next(p); + return { name: name, attributes: new Map(), closed: false }; + } + const attributes = new Map(); + while (peek(p) !== ">" && peek(p) !== "/") { + const { name, value } = parseAttribute(p); + attributes.set(name, value); + } + let closed = false; + if (peek(p) === "/") { + next(p); + closed = true; + } + expect(p, ">"); + return { name: name, attributes: attributes, closed: closed }; +} + +function parseEndTag(p: Parser, name: string) { + expect(p, `</${name}>`); +} + +function parseAttribute(p: Parser): { name: string; value: string } { + skipWhitespaces(p); + let name = parseIdentifier(p); + if (peek(p) === ":") { + next(p); + const name2 = parseIdentifier(p); + name += ":" + name2; + } + expect(p, "="); + const value = parseQuotedString(p); + skipWhitespaces(p); + return { name: name, value: replaceEntityReferences(value) }; +} + +function parseQuotedString(p: Parser): string { + expect(p, '"'); + const content = skipTo(p, '"'); + next(p); + return content; +} + +function parseIdentifier(p: Parser): string { + let id = ""; + while (p.index < p.source.length) { + const c = peek(p); + if (!c || !/[A-Za-z]/.test(c)) { + break; + } + id += c; + next(p); + } + return id; +} + +function expect(p: Parser, expected: string) { + let actual = ""; + for (let i = 0; i < expected.length; i++) { + actual += peek(p); + next(p); + } + if (actual !== expected) { + throw new XmlParseError( + `[parse.expect] expected ${expected}, but actually got ${actual}`, + ); + } +} + +function skipTo(p: Parser, delimiter: string): string { + const indexStart = p.index; + let i = 0; + while (i < delimiter.length) { + if (peek(p) === delimiter[i]) { + i++; + } else { + i = 0; + } + next(p); + } + back(p, delimiter.length); + return p.source.substring(indexStart, p.index); +} + +function skipWhitespaces(p: Parser) { + while (p.index < p.source.length) { + const c = peek(p); + if (!c || !/[ \n\t]/.test(c)) { + break; + } + next(p); + } +} + +function peek(p: Parser): string | null { + return (p.index < p.source.length) ? p.source[p.index] : null; +} + +function peek2(p: Parser): string | null { + return (p.index + 1 < p.source.length) ? p.source[p.index + 1] : null; +} + +function next(p: Parser, n = 1) { + p.index += n; +} + +function back(p: Parser, n = 1) { + p.index -= n; +} + +function replaceEntityReferences(s: string): string { + return s + .replaceAll(/&/g, "&") + .replaceAll(/</g, "<") + .replaceAll(/>/g, ">") + .replaceAll(/'/g, "'") + .replaceAll(/"/g, '"'); +} diff --git a/nuldoc.rb b/nuldoc.rb deleted file mode 100644 index c702981..0000000 --- a/nuldoc.rb +++ /dev/null @@ -1,28 +0,0 @@ -require 'date' -require 'digest/md5' -require 'fileutils' - -require 'asciidoctor' - -require_relative 'lib/command' -require_relative 'lib/html_converter' -require_relative 'lib/parser' -require_relative 'lib/extensions/document_title_processor' -require_relative 'lib/extensions/lang_attribute_processor' -require_relative 'lib/extensions/revision_history_processor' -require_relative 'lib/extensions/section_id_validator' -require_relative 'lib/extensions/source_id_processor' -require_relative 'lib/extensions/source_id_validator' -require_relative 'lib/extensions/tags_processor' -require_relative 'lib/revision' -require_relative 'lib/tag' - -NulDoc::Command.new({ - author: 'nsfisis', - site_name: 'REPL: Rest-Eat-Program Loop', - site_copyright_year: 2021, - content_dir: __dir__ + '/content', - dest_dir: __dir__ + '/public', - static_dir: __dir__ + '/static', - template_dir: __dir__ + '/templates', -}).run diff --git a/public/posts/2021-03-05/my-first-post/index.html b/public/posts/2021-03-05/my-first-post/index.html index 8f55fed..7d7744c 100644 --- a/public/posts/2021-03-05/my-first-post/index.html +++ b/public/posts/2021-03-05/my-first-post/index.html @@ -4,18 +4,13 @@ <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="author" content="nsfisis"> - <meta name="copyright" content="© nsfisis"> + <meta name="copyright" content="© 2021 nsfisis"> <meta name="description" content="これはテスト投稿です。これはテスト投稿です。これはテスト投稿です。"> - <meta name="keywords" content=""> <link rel="icon" type="image/svg+xml" href="/favicon.svg"> <title>My First Post | REPL: Rest-Eat-Program Loop</title> - - <link rel="stylesheet" href="/hl.css?208c52e3b7c9db1cad782c5d30b4698f"> - - <link rel="stylesheet" href="/style.css?779b1a3debcaeba619f6fe500e93d525"> - - <link rel="stylesheet" href="/custom.css?a649ea3528d4b626fb636505d94c1144"> - + <link rel="stylesheet" href="/hl.css?h=208c52e3b7c9db1cad782c5d30b4698f"> + <link rel="stylesheet" href="/style.css?h=779b1a3debcaeba619f6fe500e93d525"> + <link rel="stylesheet" href="/custom.css?h=a649ea3528d4b626fb636505d94c1144"> </head> <body class="single"> <header class="header"> @@ -29,39 +24,23 @@ <article class="post-single"> <header class="post-header"> <h1 class="post-title">My First Post</h1> - </header> <div class="post-content"> <section> <h2 id="changelog">更新履歴</h2> <ol> - - <li class="revision"> - <time datetime="2021-03-05">2021-03-05</time>: 公開 - </li> - + <li class="revision"> + <time datetime="2021-03-05">2021-03-05</time>: 公開 + </li> </ol> </section> - <section class="section-1"> - <h2 id="" class="section-header"> - - Test - - </h2> - <div class="section-body"> - <div class="paragraph"> -<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod -tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim -veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea -commodo consequat. Duis aute irure dolor in reprehenderit in voluptate -velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint -occaecat cupidatat non proident, sunt in culpa qui officia deserunt -mollit anim id est laborum.</p> -</div> - </div> -</section> + <section id="section--test"> + <h2><a href="#section--test">Test</a></h2> + <p> + Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. + </p> + </section> </div> - </article> </main> <footer class="footer"> diff --git a/public/posts/2021-03-30/phperkaigi-2021/index.html b/public/posts/2021-03-30/phperkaigi-2021/index.html index a16b49e..43267d5 100644 --- a/public/posts/2021-03-30/phperkaigi-2021/index.html +++ b/public/posts/2021-03-30/phperkaigi-2021/index.html @@ -4,18 +4,14 @@ <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="author" content="nsfisis"> - <meta name="copyright" content="© nsfisis"> + <meta name="copyright" content="© 2021 nsfisis"> <meta name="description" content="2021-03-26 から 2021-03-28 にかけて開催された、PHPerKaigi 2021 に参加した。"> <meta name="keywords" content="カンファレンス,PHP,PHPerKaigi"> <link rel="icon" type="image/svg+xml" href="/favicon.svg"> <title>PHPerKaigi 2021 | REPL: Rest-Eat-Program Loop</title> - - <link rel="stylesheet" href="/hl.css?208c52e3b7c9db1cad782c5d30b4698f"> - - <link rel="stylesheet" href="/style.css?779b1a3debcaeba619f6fe500e93d525"> - - <link rel="stylesheet" href="/custom.css?a649ea3528d4b626fb636505d94c1144"> - + <link rel="stylesheet" href="/hl.css?h=208c52e3b7c9db1cad782c5d30b4698f"> + <link rel="stylesheet" href="/style.css?h=779b1a3debcaeba619f6fe500e93d525"> + <link rel="stylesheet" href="/custom.css?h=a649ea3528d4b626fb636505d94c1144"> </head> <body class="single"> <header class="header"> @@ -29,1073 +25,1112 @@ <article class="post-single"> <header class="post-header"> <h1 class="post-title">PHPerKaigi 2021</h1> - - <ul class="post-tags"> - - <li class="tag"> - <a href="/tags/conference/">カンファレンス</a> - </li> - - <li class="tag"> - <a href="/tags/php/">PHP</a> - </li> - - <li class="tag"> - <a href="/tags/phperkaigi/">PHPerKaigi</a> - </li> - - </ul> - + <ul class="post-tags"> + <li class="tag"> + <a href="/tags/conference">カンファレンス</a> + </li> + <li class="tag"> + <a href="/tags/php">PHP</a> + </li> + <li class="tag"> + <a href="/tags/phperkaigi">PHPerKaigi</a> + </li> + </ul> </header> <div class="post-content"> <section> <h2 id="changelog">更新履歴</h2> <ol> + <li class="revision"> + <time datetime="2021-03-30">2021-03-30</time>: 公開 + </li> + </ol> + </section> + <section id="section--_phperkaigi_2021_参加レポ"> + <h2><a href="#section--_phperkaigi_2021_参加レポ">PHPerKaigi 2021 参加レポ</a></h2> + <p> + 2021-03-26 から 2021-03-28 にかけて開催された、<a xl:href="https://phperkaigi.jp/2021/">PHPerKaigi 2021</a>に一般参加者として参加した。 弊社<a xl:href="https://www.dgcircus.com/">デジタルサーカス株式会社</a>(今年1月から勤務) はダイヤモンドスポンサーとなっており、スポンサー枠のチケットを使わせていただいた。 + </p> + + <p> + このようなカンファレンスには初めて参加するのでかねてより心待ちにしていたのだが、生憎2日目から体調を崩してしまい、この記事も途中までとなっている。まだ見ていないセッションも多いが、ひとまず現時点での参加レポを書いておく。 + </p> + + <p> + 発表はトラック A、B に分かれていたのだが、今回はすべて A トラックを視聴している (切り替えるのが面倒だっただけ)。 + </p> + + <section id="section--_凡例"> + <h3><a href="#section--_凡例">凡例</a></h3> + <blockquote> + <p> + 発表・スライドのメモ (引用ではない) + </p> + </blockquote> - <li class="revision"> - <time datetime="2021-03-30">2021-03-30</time>: 公開 - </li> + <p> + 感想など + </p> + </section> + + <section id="section--_day_0_前夜祭_20210327"> + <h3><a href="#section--_day_0_前夜祭_20210327">Day 0 前夜祭 (2021/03/27)</a></h3> + <section id="section--_1730_a"> + <h4><a href="#section--_1730_a">17:30 [A]</a></h4> + <p> + PHP で AWS Lambda + </p> + + <blockquote> + <p> + Rails のプロジェクトを PHPer のメンバのみでメンテ →他のメンバもわかる PHP にリプレースを検討 + </p> + + <ul> + <li> + <p> + サーバレス + </p> + </li> + + <li> + <p> + サーバ・インフラの管理が不要 + </p> + </li> + + <li> + <p> + アプリケーションコードの知識だけで保守可能 + </p> + </li> + </ul> + + <p> + ゼロベースで作れる案件が (Railsの件とは別に) あるため、そちらで試験的に導入? + </p> + + <p> + AWSの学習 AWS のドキュメント DevelopersIO + </p> + + <p> + AWS Lambda のカスタムランタイムで PHP を動かす + </p> + + <p> + サーバのセットアップや維持管理を気にしなくて良い サーバーレスで PHP を動かすツールがすでにある サーバーレス構築はすんなり + </p> + + <p> + 今は Laravel がルーティングしている Laravel Livewire を Lambda に載せられないか? デプロイ方法は? バッチ処理は? (Lambda は 15分の制限) + </p> + + <p> + Lambda でコンテナイメージがサポートされるように + </p> + + <p> + 抽象化されたもの「だけ」しか知らないよりも具象の理解は助けになる + </p> + </blockquote> + + <p> + AWS Lambda のような Function as a Service はマイクロサービス化における一つの到達点に思えるのだが、これを使って実際に web サービスを作る具体的なイメージがまだ見えない (注: すべて for me として書いている)。 + </p> + + <p> + PHP on AWS Lambda があれだけ簡単に動かせるのには驚いた。 + </p> + + <p> + 勝手に AWS Lambda だとフットプリントの軽さが求められそう (= PHP + Laravel などでは動かなさそう) だという先入観を持っていたのだが、この発表のデモによればそうでもないらしい。 + </p> + </section> - </ol> + <section id="section--_1810_a"> + <h4><a href="#section--_1810_a">18:10 [A]</a></h4> + <p> + 大規模サイトの SEO + </p> + + <blockquote> + <p> + 大規模サイト (100万ページ以上) Google の基準 + </p> + + <p> + クロールバジェットを意識したSEO + </p> + + <p> + 大規模サイトでコンテンツが中頻度 (1回/週) で更新 OR 中規模サイト (10,000以上) でコンテンツが目まぐるしく変更される これを満たさないなら、クロールバジェットを考えなくてもいい + </p> + + <p> + サーチコンソール 「カバレッジ」の「除外」 多すぎるのは問題→クロールバジェットを浪費している + </p> + + <ul> + <li> + <p> + クエリの順番を決める + </p> + </li> + + <li> + <p> + 空の値のルールを決めておく + </p> + </li> + + <li> + <p> + リダイレクトすればインデックスはうまくいく + </p> + </li> + + <li> + <p> + リンクが存在する限りクロールはされる + </p> + </li> + </ul> + + <p> + リニューアル前のURL + </p> + + <p> + インデックスは移行される リンクのURLが存在する限り、別のURLとしてクロールされる リダイレクトされるとはいえ、リニューアル前のURLは移行した方が良い リニューアルで無視されるようになったパラメータも注意 + </p> + + <p> + robotes.txt で拒否しているのにクロールされる 一時的に拒否を外して 404 や 301 を読ませる 内部リンクを確認する JS でのリンクに書き換え + </p> + + <p> + クエリパラメータからURLのパスに<code>/tokyo?area=HOGE</code>→<code>/tokyo/HOGE</code> + </p> + + <p> + URL 設計だいじ + </p> + </blockquote> + + <p> + SEO (Search Engine Optimization) は大して知らないので新鮮な話が多かった。その分語れることも少ない……。 + </p> + </section> + + <section id="section--_1850_a"> + <h4><a href="#section--_1850_a">18:50 [A]</a></h4> + <blockquote> + <p> + 知覚可能 操作可能 理解可能 堅牢 ちゃんとしたHTMLを書く (閉じタグ・入れ子構造など) + </p> + + <ul> + <li> + <p> + 標準の HTML を適切に使う + </p> + </li> + + <li> + <p> + WAI-ARIA + </p> + </li> + + <li> + <p> + キーボードフレンドリー + </p> + </li> + + <li> + <p> + マシンフレンドリー + </p> + </li> + + <li> + <p> + SEOフレンドリー + </p> + </li> + </ul> + + <p> + button タグ →キーボード h1 タグ →スクリーンリーダー・クローラ a タグ + </p> + + <p> + WAI-ARIA HTML では表現できないセマンティクスを追加する + </p> + + <ul> + <li> + <p> + ロール + </p> + + <ul> + <li> + <p> + 何をするのか? + </p> + </li> + + <li> + <p> + ユーザーアクションによって変化しない + </p> + </li> + </ul> + </li> + + <li> + <p> + プロパティ + </p> + + <ul> + <li> + <p> + 関連づけられたデータ + </p> + </li> + </ul> + </li> + + <li> + <p> + ステート + </p> + + <ul> + <li> + <p> + 現在の状態 + </p> + </li> + </ul> + </li> + </ul> + + <p> + まずは標準の HTML 要素で解決する 何でもかんでも WAI-ARIA を使えばいいというものではない + </p> + + <p> + マウスホバーでツールチップが出てくるが、キーボード操作では出てこない + </p> + + <p> + VoiceOver + </p> + + <p> + 全ての属性を使う必要はない あくまでアクセシビリティを上げるための方法の一つにすぎない + </p> + </blockquote> + + <p> + つい最近 WAI-ARIA についての記事を読んだばかりだったので個人的にタイムリーな話題だった。(あまりこの言葉を使いたくないのだが) いわゆる「健常者」にとって、こうした問題を普段の生活の中で意識するのは難しい。だからこそ情報へのアンテナは張っておくようにしたい。 + </p> + </section> + + <section id="section--_1930_a"> + <h4><a href="#section--_1930_a">19:30 [A]</a></h4> + <p> + PHP で FUSE + </p> + + <p> + 個人的に楽しみだった発表。 + </p> + + <blockquote> + <p> + VFS (virtual filesystem) vs 具体的なファイルシステム + </p> + + <p> + 最適な実装方法は状況により異なる + </p> + + <p> + アプリケーションに見せるAPIは変えずに実装を隠蔽する→VFS + </p> + + <p> + カーネルのプログラムを作るのは難しい * 権限がデカすぎる * システム全体がクラッシュ * セキュリティリスク * 開発サイクルを回しづらい * ネイティブコードにコンパイルされる言語である必要がある + </p> + + <p> + Filesystem in USEr space (FUSE) + </p> + + <ul> + <li> + <p> + 特定の C の関数を呼ぶことで filesystem が作れる + </p> + </li> + + <li> + <p> + FFI を持つ言語なら FUSE が使える + </p> + </li> + </ul> + + <p> + SSHFS / s3fs / Docker Desktop + </p> + + <p> + Linux 以外でも使える + </p> + + <ul> + <li> + <p> + dokany (on Windows) + </p> + </li> + + <li> + <p> + osxfuse + </p> + </li> + </ul> + + <p> + VFS: システムコールが呼ばれると、ファイルシステムによってコール FUSE: カーネル空間からユーザ空間へ通信 + </p> + + <p> + 高レベルなラッパで型をつける + </p> + + <p> + PHP 以外では Wordpress を FUSE にマウントする実装がある (C, Python など) + </p> + + <ul> + <li> + <p> + grep できる + </p> + </li> + + <li> + <p> + sed できる + </p> + </li> + + <li> + <p> + 編集できる + </p> + </li> + </ul> + </blockquote> + + <p> + 期待通りの興味深い発表だった。FUSE 自体も今回の発表で知ったのだが、これ本体の実装を見るのも面白そうだ。 この発表を聞きながらファイルシステムにマウントできそうなものを考えていたのだが、およそ木構造をしているものすべてと言えそうだ (ハンマーしか持っていないと云々)。何かできそうだがなかなか思いつかない。 + </p> + </section> + </section> + + <section id="section--_day_1_20210327"> + <h3><a href="#section--_day_1_20210327">Day 1 (2021/03/27)</a></h3> + <section id="section--_1050_a"> + <h4><a href="#section--_1050_a">10:50 [A]</a></h4> + <p> + ATDD + </p> + + <blockquote> + <ul> + <li> + <p> + ユーザーストーリー + </p> + </li> + + <li> + <p> + ユニットテスト + </p> + </li> + + <li> + <p> + CI/CD + </p> + </li> + </ul> + + <p> + ユーザストーリーの受け入れ条件が曖昧になりがち デグレチェックがユニットレベルでは収まらない場合、手動で同じシナリオをテストしている + </p> + + <p> + Q2の強化 アジャイルテストの4象限 + </p> + + <p> + 技術面/ビジネス面 開発チーム支援(コーディング前・コーディング中)/製品批評(コーディング後) + </p> + + <ul> + <li> + <p> + Q1: 技術面 & チーム支援 + </p> + + <ul> + <li> + <p> + TDD + </p> + </li> + + <li> + <p> + ユニットテストなど + </p> + </li> + </ul> + </li> + + <li> + <p> + Q2: ビジネス面 & チーム支援 + </p> + + <ul> + <li> + <p> + ATDD + </p> + </li> + + <li> + <p> + ビジネス面の受け入れテストで駆動する + </p> + </li> + </ul> + </li> + </ul> + + <p> + Agile Alliance ユーザストーリーのスキルレベルを高める + </p> + + <p> + テストピラミッド + </p> + + <ul> + <li> + <p> + UI Tests + </p> + </li> + + <li> + <p> + Service Tests + </p> + </li> + + <li> + <p> + Unit Tests + </p> + </li> + + <li> + <p> + 異なる粒度のテストを書く + </p> + </li> + + <li> + <p> + 高レベルになるほど、持つべきテストは少なくなる + </p> + + <ul> + <li> + <p> + ピラミッド型になる + </p> + </li> + </ul> + </li> + </ul> + + <p> + 高レベルテストが多すぎる→アイスクリームコーン アンチパターン + </p> + + <p> + ATDD (Acceptance TDD) API経由・UI経由での高レベルテスト E2E test + </p> + + <p> + ストーリ受け入れテスト + </p> + + <p> + 入れ子のフィードバックループ ATDD(外側) と TDD(内側) + </p> + + <p> + 外部品質・内部品質 + </p> + + <p> + バーティカルスライスのデリバリー + </p> + + <ul> + <li> + <p> + cucumber + </p> + </li> + + <li> + <p> + gauge + </p> + </li> + + <li> + <p> + behat + </p> + </li> + </ul> + + <p> + ユビキタス言語 手動テストもspecに書く 自動化は可能だがコスパが悪い 失敗することがわかっているテスト(レッドテスト)はCIから外す 失敗時の原因究明が難しい 饒舌なエラーメッセージ 状況のスナップショット + </p> + + <p> + Continuous Testing + </p> + </blockquote> + + <p> + User Acceptance Test (UAT) くらいの規模になると個人開発・趣味開発では触れない領域なので、大いに勉強になった。スライドに添付されている資料が相当に充実していたので、これを読むのが本番といった様相すら感じる。 高レベルテストの自動化は現在のプロジェクトでも感じており、自動化のチャンスは伺っている。とはいえセッションでも指摘されているように自動化することにコストがかかりすぎる領域があるのも事実で、そのバランスが難しい。 + </p> + </section> + + <section id="section--_1150_a"> + <h4><a href="#section--_1150_a">11:50 [A]</a></h4> + <p> + 型解析を用いたリファクタリング + </p> + + <p> + 型のある世界で生きてきた身として大いに楽しみにしていた発表。 + </p> + + <blockquote> + <ul> + <li> + <p> + PHPStan + </p> + </li> + + <li> + <p> + Phan + </p> + </li> + + <li> + <p> + Psalm + </p> + </li> + </ul> + + <p> + autoload も認識できる bootstrapFiles + </p> + + <p> + 編集箇所と利用箇所を CI でチェック ルールレベルを徐々に引き上げていく 警告が多すぎると見落としてしまう・無視されやすくなる + </p> + + <p> + 型がついていないことによるエラーが多い + </p> + + <p> + 型よりも詳細な検査<code>Util_Assert::min</code> + </p> + + <p> + SQL を静的解析 placeholder の型付け + </p> + + <p> + 警告レベルを低いレベルから導入 タイプヒントを積極的に書いていく PHPStan の拡張を追加する + </p> + </blockquote> + + <p> + 昨今、動的型付き言語での型宣言・型アノテーション・型ヒントの導入が相次いでいる。長らく静的型付き言語を書いてきた私からすると、ようやく気づいたかといったところだが、ともかく型を導入する言語が増えてきた。 今のプロジェクトでも新しく追加するコードには型をつけるよう努めているが、どうしても古いコードには型がついていない。個人的には型のないコードに対してどう型を自動的に付けるかという点に興味があり、その点で Ruby の typeprof には注目している。 + </p> + </section> + + <section id="section--_1230_a"> + <h4><a href="#section--_1230_a">12:30 [A]</a></h4> + <p> + 昼食をとっていた。事前に何か食料を買っておくべきだった。 + </p> + </section> + + <section id="section--_1310_a"> + <h4><a href="#section--_1310_a">13:10 [A]</a></h4> + <p> + Documentation as Code + </p> + + <p> + この発表も以前から非常に楽しみにしていた。 + </p> + + <blockquote> + <p> + 開発開始までのオーバーヘッド 新規にチームにジョイン 担当範囲外の機能を理解 オンボーディングのコスト + </p> + + <p> + PHPerKaigi 2020 で発表あり + </p> + + <p> + 継続的にシステムの理解を助けるドキュメント + </p> + + <p> + 継続的ドキュメンテーション システムとドキュメントの乖離 + </p> + + <p> + 書いてあることが間違っている・足りない * 徐々にずれていく * システムの更新タイミングとドキュメントの更新タイミングに差がある + </p> + + <p> + システムとドキュメントは対応関係がある * 間違ったドキュメント * 存在しないドキュメント + </p> + + <p> + システムとドキュメントの乖離を定量化する 継続的に システムの更新に近いタイミングで ドキュメントを更新し続ける + </p> + + <p> + Documentation as Code + </p> + + <p> + コードと同じツールでドキュメントを書く * issue tracker * vcs * plain text markup * automation + </p> + + <p> + 開発者 システム ドキュメント 構造化データ ソフトウェア + </p> + + <p> + システムから構造化データを抽出する PHPDoc OpenAPI + </p> + + <p> + ビュー 関心ごとに合わせてアーキテクチャを一つ以上の側面(断面)で説明する + </p> + + <p> + ビューの単位でドキュメントに + </p> + + <p> + スタックトレースからのドキュメント生成 + </p> + </blockquote> + + <p> + ドキュメントの管理は現プロジェクトでも課題と感じている。作られた当初は正しくても、実態と乖離していくのを止めるのは困難を極める。全体的に興味深い発表だったが、特にスタックトレースからのドキュメント生成というアイデアに惹かれるものを感じた。スタックトレースという実態と不可分な (乖離しない) 情報を起点にするのは理にかなっている。問題はトレースをいつ、どう取るかだろうか。それを自動化しなければ、実態との乖離が避けられないだろう。 + </p> + </section> + + <section id="section--_1410_a"> + <h4><a href="#section--_1410_a">14:10 [A]</a></h4> + <p> + cookie による session 管理 + </p> + + <p> + 全体的に基本的な話だったので特に触れない。Cookie やセッションの話としては非常に分かりやすくまとめられていたので、知らない人が学ぶにはいい教材だろう。 + </p> + </section> + + <section id="section--_1450_a"> + <h4><a href="#section--_1450_a">14:50 [A]</a></h4> + <p> + PHP のエラーと例外 + </p> + + <blockquote> + <p> + エラー PHPエンジンがエラーを通知する 例外 プログラムが投げる + </p> + + <p> + PHP7-8とエラー + </p> + + <p> + PHPエンジンのエラーの一部が に変換されるようになった → try-catch で捕捉できる + </p> + + <p> + は例外とは異なる + </p> + + <p> + PHP8 でエラーレベルの引き上げ + </p> + + <ul> + <li> + <p> + 捕捉すべきもの + </p> + + <ul> + <li> + <p> + recoverable + </p> + </li> + </ul> + </li> + + <li> + <p> + 捕捉すべきでないもの + </p> + + <ul> + <li> + <p> + unrecoverable + </p> + </li> + + <li> + <p> + 開発時に対処できるもの + </p> + </li> + </ul> + </li> + </ul> + + <p> + 例外 * 捕捉して事後処理 * 捕捉せず(or 捕捉した上で)さらに上に是非を問う + </p> + + <p> + 開発段階で例外を把握し、ハンドリングを考えておく + </p> + + <p> + と + </p> + + <p> + はキャッチすべきでない + </p> + + <ul> + <li> + <p> + </p> + + <ul> + <li> + <p> + 本番で起きてはいけない + </p> + </li> + </ul> + </li> + + <li> + <p> + </p> + + <ul> + <li> + <p> + 本番で起きてはいけない →生じないのだから捕捉もしない + </p> + </li> + </ul> + </li> + + <li> + <p> + </p> + + <ul> + <li> + <p> + 起こるかもしれないので本番環境でも考慮する + </p> + </li> + </ul> + </li> + </ul> + + <p> + 捕捉して対応するのではなく、未然に防ぐ + </p> + + <p> + 独自例外を使う を投げてしまうと、 catch ()せざるを得ない →catch 範囲が広すぎる + </p> + + <p> + SPL の例外を使う + </p> + + <p> + 例外翻訳 上位のレイヤが下位のレイヤの例外を捕捉し、上位レイヤのAPIに「翻訳」する 下位レイヤの知識に依存させない + </p> + + <p> + @throws 捕捉してほしい例外を書き連ねておく + </p> + + <p> + 呼び出しもとに負わせたい責任 + </p> + </blockquote> + + <p> + PHP を学んでいる途中の私としては、今まさに聞きたい発表だった (現時点で PHP を書き始めてから 4ヶ月ほどになる)。 + </p> + + <p> + 個人的に例外やエラーを最もうまく扱っているのは Go、Swift、Rust、Haskell などのエラーを「値として」扱う言語だと思っている。try-catch は通常の処理フローを完全に壊してしまう上、構文としても重すぎる。値としてのエラー通知は C言語時代への回帰ともいえるが、その頃と異なるのはエラーを暗黙のうちに握り潰すことがないということだ。これらの言語は型を持っており、静的に検証ができる (C のそれはまともな型付けではない。念のため)。 + </p> + + <p> + PHP のように、すでに例外が言語システムに根ざしている言語ではどうすればよいか。この場合も同じく静的検証の力を借りることになるだろう。 + </p> + </section> + + <section id="section--_1530_a"> + <h4><a href="#section--_1530_a">15:30 [A]</a></h4> + <p> + Laravel のメール認証 + </p> + + <p> + Laravel の知識がない私にはまったくついていけなかった。また、個人的にタイトルがややミスリーディングに感じた。 + </p> + </section> + + <section id="section--_1610_a"> + <h4><a href="#section--_1610_a">16:10 [A]</a></h4> + <p> + gRPC + </p> + + <blockquote> + <p> + Unary RPCs Server streaming RPCs Client streaming RPCs Bidirectional streaming RPCs + </p> + + <p> + Protobuf + </p> + + <p> + 実装とAPIが乖離しにくい 自動生成 複数言語でも相互に使える + </p> + + <p> + マイクロサービスのサービス通信 スマホアプリ ゲームサーバ + </p> + + <p> + PHP では? + </p> + + <p> + PHP ではストリーミングが難しい リクエストごとにプロセスが使い捨て + </p> + + <p> + PHP ではgRPCのクライアントしか対応していない + </p> + + <p> + gRPC-Web ブラウザで扱うためのJSライブラリ+プロトコル + </p> + + <p> + HTTP/1.1 でも使える Unary RPC と Server streaming RPC のみ + </p> + + <p> + Envoy Nginx などで相互に gRPC と gRPC-Web で変換 + </p> + + <p> + Amp イベント駆動な並行処理のフレームワーク + </p> + + <p> + HTTP/2 対応 + </p> + + <p> + C#のgRPC-Webが楽 + </p> + </blockquote> + + <p> + (発表の中でもまさに同じことをおっしゃっていたが) PHP 以外の方が向いているだろう、というのが第一の感想である。gRPC はそれ自体というよりも Protobuf というエコシステムに乗れることのメリットが大きいと感じる。そのエコシステムにうまく乗れない時点で、うーんという感じ。 + </p> + </section> + + <section id="section--_1650_a"> + <h4><a href="#section--_1650_a">16:50 [A]</a></h4> + <p> + アーキテクチャテスト + </p> + + <blockquote> + <p> + Independent Core Layer Pattern + </p> + + <p> + 開発初期のアーキテクチャが崩れる アーキテクチャ観点のコードレビューができない + </p> + + <p> + どこにクラスを置けばよいか? ドキュメントがない + </p> + + <p> + アーキテクチャ設計に関する知識が属人化・暗黙知化 + </p> + + <p> + ガイドライン * 最初にルールを決めるのは簡単 * ルール通り作り始めるのも簡単 * →維持するのが難しい、人が決めたものゆえ壊れやすい + </p> + + <p> + PHP の特性 * クラスは public * 可視性の制御が public / protected / private のみ * 依存関係の制御が困難 + </p> + + <p> + アーキテクチャテスト クラスの依存関係や実装ルールをコードとして表現し、自動テスト化する + </p> + + <ul> + <li> + <p> + deptrac + </p> + </li> + + <li> + <p> + phpat + </p> + </li> + </ul> + + <p> + Independent Core Layer Pattern + </p> + + <p> + アーキテクチャテストの失敗 * 実装誤り * or アーキテクチャが適切でない * 開発の過程でフィードバックしていく + </p> + + <p> + モジュラーモノリス→マイクロサービスへ + </p> + </blockquote> + </section> + </section> + + <section id="section--_day_2_20210328"> + <h3><a href="#section--_day_2_20210328">Day 2 (2021/03/28)</a></h3> + <p> + 冒頭に書いた通り、2日目から体調が悪くまともに聴けていない。途中までは頭痛を我慢しつつ見ていたのだが、まともに入ってこなかった。 + </p> + + <p> + 残念ではあるが、いずれにせよ見られていない発表は他にもあるので、今週末にでもまとめて見ようと思う。 + </p> + </section> + + <section id="section--_全体の感想"> + <h3><a href="#section--_全体の感想">全体の感想</a></h3> + <p> + Day 2 にほとんど参加できなかったのは残念だが、イベント自体は大変楽しく、また興味深いものであった。自分がまったく知らない領域の話を聞けるのはこうしたイベントならではだと感じる。オンライン開催ゆえ現地に行く必要がなく、気軽に参加できたのも (特に初参加者として) 嬉しいポイントだった。 + </p> + + <p> + 今回、雑談/登壇者への質問等向けに Discord サーバもあったのだが、こちらは参加こそしたものの ROM のままになってしまった。発表に1ウィンドウ、メモを書くのに1ウィンドウ、Discord 表示に 1ウィンドウで私にはもう脳のリソースとディスプレイのスペースが追いつかなかった (さらにいうと Zoom でアンカンファレンスもやっていたようだ。こちらはまったく参加していない)。 + </p> + + <p> + 1つ個人的な反省点としては、一つ一つのセッションを真剣に聞き過ぎたというものがある。もっと適当に聞いておけばよかった。これだけだと大変語弊があるのだが、言い方を変えると、Discord しかりアンカンファレンスしかり「このイベントのこの瞬間にしかないコンテンツ」に触れずに、後から見返せる発表やスライドに注力してしまった、ということだ。発表の詳細な見直しはあとからできるのだから、今しかできないことを考えるべきだった。 まあ初カンファレンスだし、とお茶を濁しておこう。 + </p> + + <p> + さて、カンファレンスで一つ気になったことがある。それは、Discord という書き込み場所が増えたことでニコ生のコメントの流量が吸い取られてしまったのではないか、という点だ。ニコニコだけ見ていると過疎っているかのように見えた発表も、Discord の方では盛り上がっている、というのを何度か見かけた。ニコニコのコメント方式は盛り上がりを如実に反映するが、逆もまたしかり。Discord があったこと自体はプラスだったと思うが、この点はマイナスだったのではないかと感じる。 + </p> + + <p> + <hr> + </hr> + </p> + + <p> + 最後になりましたが、毎年の PHPerKaigi 開催にご尽力されている皆様、スピーカーの皆様、楽しい3日間でした。ありがとうございました! (ずっと常体で書いてしまったのでいきなり仏頂面から笑顔になったようで気持ち悪い) + </p> + + <p> + ではまた来年。 + </p> + </section> </section> - <section class="section-1"> - <h2 id="" class="section-header"> - - PHPerKaigi 2021 参加レポ - - </h2> - <div class="section-body"> - <div class="paragraph"> -<p>2021-03-26 から 2021-03-28 -にかけて開催された、 <a href="https://phperkaigi.jp/2021/">PHPerKaigi 2021</a> -に一般参加者として参加した。 -弊社 <a href="https://www.dgcircus.com/">デジタルサーカス株式会社</a> -(今年1月から勤務) -はダイヤモンドスポンサーとなっており、スポンサー枠のチケットを使わせていただいた。</p> -</div> -<div class="paragraph"> -<p>このようなカンファレンスには初めて参加するのでかねてより心待ちにしていたのだが、生憎2日目から体調を崩してしまい、この記事も途中までとなっている。まだ見ていないセッションも多いが、ひとまず現時点での参加レポを書いておく。</p> -</div> -<div class="paragraph"> -<p>発表はトラック A、B に分かれていたのだが、今回はすべて A -トラックを視聴している (切り替えるのが面倒だっただけ)。</p> -</div> -<section class="section-2"> - <h3 id="" class="section-header"> - - 凡例 - - </h3> - <div class="section-body"> - <div class="quoteblock"> -<blockquote> -<div class="paragraph"> -<p>発表・スライドのメモ (引用ではない)</p> -</div> -</blockquote> -</div> -<div class="paragraph"> -<p>感想など</p> -</div> - </div> -</section> -<section class="section-2"> - <h3 id="" class="section-header"> - - Day 0 前夜祭 (2021/03/27) - - </h3> - <div class="section-body"> - <section class="section-3"> - <h4 id="" class="section-header"> - - 17:30 [A] - - </h4> - <div class="section-body"> - <div class="paragraph"> -<p>PHP で AWS Lambda</p> -</div> -<div class="quoteblock"> -<blockquote> -<div class="paragraph"> -<p>Rails のプロジェクトを PHPer のメンバのみでメンテ →他のメンバもわかる -PHP にリプレースを検討</p> -</div> -<div class="ulist"> -<ul> -<li> -<p>サーバレス</p> -</li> -<li> -<p>サーバ・インフラの管理が不要</p> -</li> -<li> -<p>アプリケーションコードの知識だけで保守可能</p> -</li> -</ul> -</div> -<div class="paragraph"> -<p>ゼロベースで作れる案件が (Railsの件とは別に) -あるため、そちらで試験的に導入?</p> -</div> -<div class="paragraph"> -<p>AWSの学習 AWS のドキュメント DevelopersIO</p> -</div> -<div class="paragraph"> -<p>AWS Lambda のカスタムランタイムで PHP を動かす</p> -</div> -<div class="paragraph"> -<p>サーバのセットアップや維持管理を気にしなくて良い サーバーレスで PHP -を動かすツールがすでにある サーバーレス構築はすんなり</p> -</div> -<div class="paragraph"> -<p>今は Laravel がルーティングしている Laravel Livewire を Lambda -に載せられないか? デプロイ方法は? バッチ処理は? (Lambda は -15分の制限)</p> -</div> -<div class="paragraph"> -<p>Lambda でコンテナイメージがサポートされるように</p> -</div> -<div class="paragraph"> -<p>抽象化されたもの「だけ」しか知らないよりも具象の理解は助けになる</p> -</div> -</blockquote> -</div> -<div class="paragraph"> -<p>AWS Lambda のような Function as a Service -はマイクロサービス化における一つの到達点に思えるのだが、これを使って実際に -web サービスを作る具体的なイメージがまだ見えない (注: すべて for me -として書いている)。</p> -</div> -<div class="paragraph"> -<p>PHP on AWS Lambda があれだけ簡単に動かせるのには驚いた。</p> -</div> -<div class="paragraph"> -<p>勝手に AWS Lambda だとフットプリントの軽さが求められそう (= PHP<br> -Laravel などでは動かなさそう) -だという先入観を持っていたのだが、この発表のデモによればそうでもないらしい。</p> -</div> - </div> -</section> -<section class="section-3"> - <h4 id="" class="section-header"> - - 18:10 [A] - - </h4> - <div class="section-body"> - <div class="paragraph"> -<p>大規模サイトの SEO</p> -</div> -<div class="quoteblock"> -<blockquote> -<div class="paragraph"> -<p>大規模サイト (100万ページ以上) Google の基準</p> -</div> -<div class="paragraph"> -<p>クロールバジェットを意識したSEO</p> -</div> -<div class="paragraph"> -<p>大規模サイトでコンテンツが中頻度 (1回/週) で更新 OR 中規模サイト -(10,000以上) でコンテンツが目まぐるしく変更される -これを満たさないなら、クロールバジェットを考えなくてもいい</p> -</div> -<div class="paragraph"> -<p>サーチコンソール 「カバレッジ」の「除外」 -多すぎるのは問題→クロールバジェットを浪費している</p> -</div> -<div class="ulist"> -<ul> -<li> -<p>クエリの順番を決める</p> -</li> -<li> -<p>空の値のルールを決めておく</p> -</li> -<li> -<p>リダイレクトすればインデックスはうまくいく</p> -</li> -<li> -<p>リンクが存在する限りクロールはされる</p> -</li> -</ul> -</div> -<div class="paragraph"> -<p>リニューアル前のURL</p> -</div> -<div class="paragraph"> -<p>インデックスは移行される -リンクのURLが存在する限り、別のURLとしてクロールされる -リダイレクトされるとはいえ、リニューアル前のURLは移行した方が良い -リニューアルで無視されるようになったパラメータも注意</p> -</div> -<div class="paragraph"> -<p>robotes.txt で拒否しているのにクロールされる 一時的に拒否を外して 404 や -301 を読ませる 内部リンクを確認する JS でのリンクに書き換え</p> -</div> -<div class="paragraph"> -<p>クエリパラメータからURLのパスに <code>/tokyo?area=HOGE</code> → <code>/tokyo/HOGE</code></p> -</div> -<div class="paragraph"> -<p>URL 設計だいじ</p> -</div> -</blockquote> -</div> -<div class="paragraph"> -<p>SEO (Search Engine Optimization) -は大して知らないので新鮮な話が多かった。その分語れることも少ない……。</p> -</div> - </div> -</section> -<section class="section-3"> - <h4 id="" class="section-header"> - - 18:50 [A] - - </h4> - <div class="section-body"> - <div class="quoteblock"> -<blockquote> -<div class="paragraph"> -<p>知覚可能 操作可能 理解可能 堅牢 ちゃんとしたHTMLを書く -(閉じタグ・入れ子構造など)</p> -</div> -<div class="ulist"> -<ul> -<li> -<p>標準の HTML を適切に使う</p> -</li> -<li> -<p>WAI-ARIA</p> -</li> -<li> -<p>キーボードフレンドリー</p> -</li> -<li> -<p>マシンフレンドリー</p> -</li> -<li> -<p>SEOフレンドリー</p> -</li> -</ul> -</div> -<div class="paragraph"> -<p>button タグ →キーボード h1 タグ →スクリーンリーダー・クローラ a タグ</p> -</div> -<div class="paragraph"> -<p>WAI-ARIA HTML では表現できないセマンティクスを追加する</p> -</div> -<div class="ulist"> -<ul> -<li> -<p>ロール</p> -<div class="ulist"> -<ul> -<li> -<p>何をするのか?</p> -</li> -<li> -<p>ユーザーアクションによって変化しない</p> -</li> -</ul> -</div> -</li> -<li> -<p>プロパティ</p> -<div class="ulist"> -<ul> -<li> -<p>関連づけられたデータ</p> -</li> -</ul> -</div> -</li> -<li> -<p>ステート</p> -<div class="ulist"> -<ul> -<li> -<p>現在の状態</p> -</li> -</ul> -</div> -</li> -</ul> -</div> -<div class="paragraph"> -<p>まずは標準の HTML 要素で解決する 何でもかんでも WAI-ARIA -を使えばいいというものではない</p> -</div> -<div class="paragraph"> -<p>マウスホバーでツールチップが出てくるが、キーボード操作では出てこない</p> -</div> -<div class="paragraph"> -<p>VoiceOver</p> -</div> -<div class="paragraph"> -<p>全ての属性を使う必要はない -あくまでアクセシビリティを上げるための方法の一つにすぎない</p> -</div> -</blockquote> -</div> -<div class="paragraph"> -<p>つい最近 WAI-ARIA -についての記事を読んだばかりだったので個人的にタイムリーな話題だった。(あまりこの言葉を使いたくないのだが) -いわゆる「健常者」にとって、こうした問題を普段の生活の中で意識するのは難しい。だからこそ情報へのアンテナは張っておくようにしたい。</p> -</div> - </div> -</section> -<section class="section-3"> - <h4 id="" class="section-header"> - - 19:30 [A] - - </h4> - <div class="section-body"> - <div class="paragraph"> -<p>PHP で FUSE</p> -</div> -<div class="paragraph"> -<p>個人的に楽しみだった発表。</p> -</div> -<div class="quoteblock"> -<blockquote> -<div class="paragraph"> -<p>VFS (virtual filesystem) vs 具体的なファイルシステム</p> -</div> -<div class="paragraph"> -<p>最適な実装方法は状況により異なる</p> -</div> -<div class="paragraph"> -<p>アプリケーションに見せるAPIは変えずに実装を隠蔽する→VFS</p> -</div> -<div class="paragraph"> -<p>カーネルのプログラムを作るのは難しい -* 権限がデカすぎる -* システム全体がクラッシュ -* セキュリティリスク -* 開発サイクルを回しづらい -* ネイティブコードにコンパイルされる言語である必要がある</p> -</div> -<div class="paragraph"> -<p>Filesystem in USEr space (FUSE)</p> -</div> -<div class="ulist"> -<ul> -<li> -<p>特定の C の関数を呼ぶことで filesystem が作れる</p> -</li> -<li> -<p>FFI を持つ言語なら FUSE が使える</p> -</li> -</ul> -</div> -<div class="paragraph"> -<p>SSHFS / s3fs / Docker Desktop</p> -</div> -<div class="paragraph"> -<p>Linux 以外でも使える</p> -</div> -<div class="ulist"> -<ul> -<li> -<p>dokany (on Windows)</p> -</li> -<li> -<p>osxfuse</p> -</li> -</ul> -</div> -<div class="paragraph"> -<p>VFS: システムコールが呼ばれると、ファイルシステムによってコール FUSE: -カーネル空間からユーザ空間へ通信</p> -</div> -<div class="paragraph"> -<p>高レベルなラッパで型をつける</p> -</div> -<div class="paragraph"> -<p>PHP 以外では Wordpress を FUSE にマウントする実装がある (C, Python など)</p> -</div> -<div class="ulist"> -<ul> -<li> -<p>grep できる</p> -</li> -<li> -<p>sed できる</p> -</li> -<li> -<p>編集できる</p> -</li> -</ul> -</div> -</blockquote> -</div> -<div class="paragraph"> -<p>期待通りの興味深い発表だった。FUSE -自体も今回の発表で知ったのだが、これ本体の実装を見るのも面白そうだ。 -この発表を聞きながらファイルシステムにマウントできそうなものを考えていたのだが、およそ木構造をしているものすべてと言えそうだ -(ハンマーしか持っていないと云々)。何かできそうだがなかなか思いつかない。</p> -</div> - </div> -</section> - </div> -</section> -<section class="section-2"> - <h3 id="" class="section-header"> - - Day 1 (2021/03/27) - - </h3> - <div class="section-body"> - <section class="section-3"> - <h4 id="" class="section-header"> - - 10:50 [A] - - </h4> - <div class="section-body"> - <div class="paragraph"> -<p>ATDD</p> -</div> -<div class="quoteblock"> -<blockquote> -<div class="ulist"> -<ul> -<li> -<p>ユーザーストーリー</p> -</li> -<li> -<p>ユニットテスト</p> -</li> -<li> -<p>CI/CD</p> -</li> -</ul> -</div> -<div class="paragraph"> -<p>ユーザストーリーの受け入れ条件が曖昧になりがち -デグレチェックがユニットレベルでは収まらない場合、手動で同じシナリオをテストしている</p> -</div> -<div class="paragraph"> -<p>Q2の強化 アジャイルテストの4象限</p> -</div> -<div class="paragraph"> -<p>技術面/ビジネス面 -開発チーム支援(コーディング前・コーディング中)/製品批評(コーディング後)</p> -</div> -<div class="ulist"> -<ul> -<li> -<p>Q1: 技術面 & チーム支援</p> -<div class="ulist"> -<ul> -<li> -<p>TDD</p> -</li> -<li> -<p>ユニットテストなど</p> -</li> -</ul> -</div> -</li> -<li> -<p>Q2: ビジネス面 & チーム支援</p> -<div class="ulist"> -<ul> -<li> -<p>ATDD</p> -</li> -<li> -<p>ビジネス面の受け入れテストで駆動する</p> -</li> -</ul> -</div> -</li> -</ul> -</div> -<div class="paragraph"> -<p>Agile Alliance ユーザストーリーのスキルレベルを高める</p> -</div> -<div class="paragraph"> -<p>テストピラミッド</p> -</div> -<div class="ulist"> -<ul> -<li> -<p>UI Tests</p> -</li> -<li> -<p>Service Tests</p> -</li> -<li> -<p>Unit Tests</p> -</li> -<li> -<p>異なる粒度のテストを書く</p> -</li> -<li> -<p>高レベルになるほど、持つべきテストは少なくなる</p> -<div class="ulist"> -<ul> -<li> -<p>ピラミッド型になる</p> -</li> -</ul> -</div> -</li> -</ul> -</div> -<div class="paragraph"> -<p>高レベルテストが多すぎる→アイスクリームコーン アンチパターン</p> -</div> -<div class="paragraph"> -<p>ATDD (Acceptance TDD) API経由・UI経由での高レベルテスト E2E test</p> -</div> -<div class="paragraph"> -<p>ストーリ受け入れテスト</p> -</div> -<div class="paragraph"> -<p>入れ子のフィードバックループ ATDD(外側) と TDD(内側)</p> -</div> -<div class="paragraph"> -<p>外部品質・内部品質</p> -</div> -<div class="paragraph"> -<p>バーティカルスライスのデリバリー</p> -</div> -<div class="ulist"> -<ul> -<li> -<p>cucumber</p> -</li> -<li> -<p>gauge</p> -</li> -<li> -<p>behat</p> -</li> -</ul> -</div> -<div class="paragraph"> -<p>ユビキタス言語 手動テストもspecに書く 自動化は可能だがコスパが悪い -失敗することがわかっているテスト(レッドテスト)はCIから外す -失敗時の原因究明が難しい 饒舌なエラーメッセージ 状況のスナップショット</p> -</div> -<div class="paragraph"> -<p>Continuous Testing</p> -</div> -</blockquote> -</div> -<div class="paragraph"> -<p>User Acceptance Test (UAT) -くらいの規模になると個人開発・趣味開発では触れない領域なので、大いに勉強になった。スライドに添付されている資料が相当に充実していたので、これを読むのが本番といった様相すら感じる。 -高レベルテストの自動化は現在のプロジェクトでも感じており、自動化のチャンスは伺っている。とはいえセッションでも指摘されているように自動化することにコストがかかりすぎる領域があるのも事実で、そのバランスが難しい。</p> -</div> - </div> -</section> -<section class="section-3"> - <h4 id="" class="section-header"> - - 11:50 [A] - - </h4> - <div class="section-body"> - <div class="paragraph"> -<p>型解析を用いたリファクタリング</p> -</div> -<div class="paragraph"> -<p>型のある世界で生きてきた身として大いに楽しみにしていた発表。</p> -</div> -<div class="quoteblock"> -<blockquote> -<div class="ulist"> -<ul> -<li> -<p>PHPStan</p> -</li> -<li> -<p>Phan</p> -</li> -<li> -<p>Psalm</p> -</li> -</ul> -</div> -<div class="paragraph"> -<p>autoload も認識できる bootstrapFiles</p> -</div> -<div class="paragraph"> -<p>編集箇所と利用箇所を CI でチェック ルールレベルを徐々に引き上げていく -警告が多すぎると見落としてしまう・無視されやすくなる</p> -</div> -<div class="paragraph"> -<p>型がついていないことによるエラーが多い</p> -</div> -<div class="paragraph"> -<p>型よりも詳細な検査 <code>Util_Assert::min</code></p> -</div> -<div class="paragraph"> -<p>SQL を静的解析 placeholder の型付け</p> -</div> -<div class="paragraph"> -<p>警告レベルを低いレベルから導入 タイプヒントを積極的に書いていく PHPStan -の拡張を追加する</p> -</div> -</blockquote> -</div> -<div class="paragraph"> -<p>昨今、動的型付き言語での型宣言・型アノテーション・型ヒントの導入が相次いでいる。長らく静的型付き言語を書いてきた私からすると、ようやく気づいたかといったところだが、ともかく型を導入する言語が増えてきた。 -今のプロジェクトでも新しく追加するコードには型をつけるよう努めているが、どうしても古いコードには型がついていない。個人的には型のないコードに対してどう型を自動的に付けるかという点に興味があり、その点で -Ruby の typeprof には注目している。</p> -</div> - </div> -</section> -<section class="section-3"> - <h4 id="" class="section-header"> - - 12:30 [A] - - </h4> - <div class="section-body"> - <div class="paragraph"> -<p>昼食をとっていた。事前に何か食料を買っておくべきだった。</p> -</div> - </div> -</section> -<section class="section-3"> - <h4 id="" class="section-header"> - - 13:10 [A] - - </h4> - <div class="section-body"> - <div class="paragraph"> -<p>Documentation as Code</p> -</div> -<div class="paragraph"> -<p>この発表も以前から非常に楽しみにしていた。</p> -</div> -<div class="quoteblock"> -<blockquote> -<div class="paragraph"> -<p>開発開始までのオーバーヘッド 新規にチームにジョイン -担当範囲外の機能を理解 オンボーディングのコスト</p> -</div> -<div class="paragraph"> -<p>PHPerKaigi 2020 で発表あり</p> -</div> -<div class="paragraph"> -<p>継続的にシステムの理解を助けるドキュメント</p> -</div> -<div class="paragraph"> -<p>継続的ドキュメンテーション システムとドキュメントの乖離</p> -</div> -<div class="paragraph"> -<p>書いてあることが間違っている・足りない * 徐々にずれていく * -システムの更新タイミングとドキュメントの更新タイミングに差がある</p> -</div> -<div class="paragraph"> -<p>システムとドキュメントは対応関係がある * 間違ったドキュメント * -存在しないドキュメント</p> -</div> -<div class="paragraph"> -<p>システムとドキュメントの乖離を定量化する 継続的に -システムの更新に近いタイミングで ドキュメントを更新し続ける</p> -</div> -<div class="paragraph"> -<p>Documentation as Code</p> -</div> -<div class="paragraph"> -<p>コードと同じツールでドキュメントを書く * issue tracker * vcs * plain -text markup * automation</p> -</div> -<div class="paragraph"> -<p>開発者 システム ドキュメント 構造化データ ソフトウェア</p> -</div> -<div class="paragraph"> -<p>システムから構造化データを抽出する PHPDoc OpenAPI</p> -</div> -<div class="paragraph"> -<p>ビュー 関心ごとに合わせてアーキテクチャを一つ以上の側面(断面)で説明する</p> -</div> -<div class="paragraph"> -<p>ビューの単位でドキュメントに</p> -</div> -<div class="paragraph"> -<p>スタックトレースからのドキュメント生成</p> -</div> -</blockquote> -</div> -<div class="paragraph"> -<p>ドキュメントの管理は現プロジェクトでも課題と感じている。作られた当初は正しくても、実態と乖離していくのを止めるのは困難を極める。全体的に興味深い発表だったが、特にスタックトレースからのドキュメント生成というアイデアに惹かれるものを感じた。スタックトレースという実態と不可分な -(乖離しない) -情報を起点にするのは理にかなっている。問題はトレースをいつ、どう取るかだろうか。それを自動化しなければ、実態との乖離が避けられないだろう。</p> -</div> - </div> -</section> -<section class="section-3"> - <h4 id="" class="section-header"> - - 14:10 [A] - - </h4> - <div class="section-body"> - <div class="paragraph"> -<p>cookie による session 管理</p> -</div> -<div class="paragraph"> -<p>全体的に基本的な話だったので特に触れない。Cookie -やセッションの話としては非常に分かりやすくまとめられていたので、知らない人が学ぶにはいい教材だろう。</p> -</div> - </div> -</section> -<section class="section-3"> - <h4 id="" class="section-header"> - - 14:50 [A] - - </h4> - <div class="section-body"> - <div class="paragraph"> -<p>PHP のエラーと例外</p> -</div> -<div class="quoteblock"> -<blockquote> -<div class="paragraph"> -<p>エラー PHPエンジンがエラーを通知する 例外 プログラムが投げる</p> -</div> -<div class="paragraph"> -<p>PHP7-8とエラー</p> -</div> -<div class="paragraph"> -<p>PHPエンジンのエラーの一部が に変換されるようになった → try-catch -で捕捉できる</p> -</div> -<div class="paragraph"> -<p>は例外とは異なる</p> -</div> -<div class="paragraph"> -<p>PHP8 でエラーレベルの引き上げ</p> -</div> -<div class="ulist"> -<ul> -<li> -<p>捕捉すべきもの</p> -<div class="ulist"> -<ul> -<li> -<p>recoverable</p> -</li> -</ul> -</div> -</li> -<li> -<p>捕捉すべきでないもの</p> -<div class="ulist"> -<ul> -<li> -<p>unrecoverable</p> -</li> -<li> -<p>開発時に対処できるもの</p> -</li> -</ul> -</div> -</li> -</ul> -</div> -<div class="paragraph"> -<p>例外 * 捕捉して事後処理 * 捕捉せず(or 捕捉した上で)さらに上に是非を問う</p> -</div> -<div class="paragraph"> -<p>開発段階で例外を把握し、ハンドリングを考えておく</p> -</div> -<div class="paragraph"> -<p>と</p> -</div> -<div class="paragraph"> -<p>はキャッチすべきでない</p> -</div> -<div class="ulist"> -<ul> -<li> -<p></p> -<div class="ulist"> -<ul> -<li> -<p>本番で起きてはいけない</p> -</li> -</ul> -</div> -</li> -<li> -<p></p> -<div class="ulist"> -<ul> -<li> -<p>本番で起きてはいけない →生じないのだから捕捉もしない</p> -</li> -</ul> -</div> -</li> -<li> -<p></p> -<div class="ulist"> -<ul> -<li> -<p>起こるかもしれないので本番環境でも考慮する</p> -</li> -</ul> -</div> -</li> -</ul> -</div> -<div class="paragraph"> -<p>捕捉して対応するのではなく、未然に防ぐ</p> -</div> -<div class="paragraph"> -<p>独自例外を使う を投げてしまうと、 catch ()せざるを得ない →catch -範囲が広すぎる</p> -</div> -<div class="paragraph"> -<p>SPL の例外を使う</p> -</div> -<div class="paragraph"> -<p>例外翻訳 -上位のレイヤが下位のレイヤの例外を捕捉し、上位レイヤのAPIに「翻訳」する -下位レイヤの知識に依存させない</p> -</div> -<div class="paragraph"> -<p>@throws 捕捉してほしい例外を書き連ねておく</p> -</div> -<div class="paragraph"> -<p>呼び出しもとに負わせたい責任</p> -</div> -</blockquote> -</div> -<div class="paragraph"> -<p>PHP を学んでいる途中の私としては、今まさに聞きたい発表だった (現時点で -PHP を書き始めてから 4ヶ月ほどになる)。</p> -</div> -<div class="paragraph"> -<p>個人的に例外やエラーを最もうまく扱っているのは Go、Swift、Rust、Haskell -などのエラーを「値として」扱う言語だと思っている。try-catch -は通常の処理フローを完全に壊してしまう上、構文としても重すぎる。値としてのエラー通知は -C言語時代への回帰ともいえるが、その頃と異なるのはエラーを暗黙のうちに握り潰すことがないということだ。これらの言語は型を持っており、静的に検証ができる -(C のそれはまともな型付けではない。念のため)。</p> -</div> -<div class="paragraph"> -<p>PHP -のように、すでに例外が言語システムに根ざしている言語ではどうすればよいか。この場合も同じく静的検証の力を借りることになるだろう。</p> -</div> - </div> -</section> -<section class="section-3"> - <h4 id="" class="section-header"> - - 15:30 [A] - - </h4> - <div class="section-body"> - <div class="paragraph"> -<p>Laravel のメール認証</p> -</div> -<div class="paragraph"> -<p>Laravel -の知識がない私にはまったくついていけなかった。また、個人的にタイトルがややミスリーディングに感じた。</p> -</div> - </div> -</section> -<section class="section-3"> - <h4 id="" class="section-header"> - - 16:10 [A] - - </h4> - <div class="section-body"> - <div class="paragraph"> -<p>gRPC</p> -</div> -<div class="quoteblock"> -<blockquote> -<div class="paragraph"> -<p>Unary RPCs Server streaming RPCs Client streaming RPCs Bidirectional -streaming RPCs</p> -</div> -<div class="paragraph"> -<p>Protobuf</p> -</div> -<div class="paragraph"> -<p>実装とAPIが乖離しにくい 自動生成 複数言語でも相互に使える</p> -</div> -<div class="paragraph"> -<p>マイクロサービスのサービス通信 スマホアプリ ゲームサーバ</p> -</div> -<div class="paragraph"> -<p>PHP では?</p> -</div> -<div class="paragraph"> -<p>PHP ではストリーミングが難しい リクエストごとにプロセスが使い捨て</p> -</div> -<div class="paragraph"> -<p>PHP ではgRPCのクライアントしか対応していない</p> -</div> -<div class="paragraph"> -<p>gRPC-Web ブラウザで扱うためのJSライブラリ+プロトコル</p> -</div> -<div class="paragraph"> -<p>HTTP/1.1 でも使える Unary RPC と Server streaming RPC のみ</p> -</div> -<div class="paragraph"> -<p>Envoy Nginx などで相互に gRPC と gRPC-Web で変換</p> -</div> -<div class="paragraph"> -<p>Amp イベント駆動な並行処理のフレームワーク</p> -</div> -<div class="paragraph"> -<p>HTTP/2 対応</p> -</div> -<div class="paragraph"> -<p>C#のgRPC-Webが楽</p> -</div> -</blockquote> -</div> -<div class="paragraph"> -<p>(発表の中でもまさに同じことをおっしゃっていたが) PHP -以外の方が向いているだろう、というのが第一の感想である。gRPC -はそれ自体というよりも Protobuf -というエコシステムに乗れることのメリットが大きいと感じる。そのエコシステムにうまく乗れない時点で、うーんという感じ。</p> -</div> - </div> -</section> -<section class="section-3"> - <h4 id="" class="section-header"> - - 16:50 [A] - - </h4> - <div class="section-body"> - <div class="paragraph"> -<p>アーキテクチャテスト</p> -</div> -<div class="quoteblock"> -<blockquote> -<div class="paragraph"> -<p>Independent Core Layer Pattern</p> -</div> -<div class="paragraph"> -<p>開発初期のアーキテクチャが崩れる -アーキテクチャ観点のコードレビューができない</p> -</div> -<div class="paragraph"> -<p>どこにクラスを置けばよいか? ドキュメントがない</p> -</div> -<div class="paragraph"> -<p>アーキテクチャ設計に関する知識が属人化・暗黙知化</p> -</div> -<div class="paragraph"> -<p>ガイドライン * 最初にルールを決めるのは簡単 * -ルール通り作り始めるのも簡単 * -→維持するのが難しい、人が決めたものゆえ壊れやすい</p> -</div> -<div class="paragraph"> -<p>PHP の特性 * クラスは public * 可視性の制御が public / protected / -private のみ * 依存関係の制御が困難</p> -</div> -<div class="paragraph"> -<p>アーキテクチャテスト -クラスの依存関係や実装ルールをコードとして表現し、自動テスト化する</p> -</div> -<div class="ulist"> -<ul> -<li> -<p>deptrac</p> -</li> -<li> -<p>phpat</p> -</li> -</ul> -</div> -<div class="paragraph"> -<p>Independent Core Layer Pattern</p> -</div> -<div class="paragraph"> -<p>アーキテクチャテストの失敗 * 実装誤り * or アーキテクチャが適切でない * -開発の過程でフィードバックしていく</p> -</div> -<div class="paragraph"> -<p>モジュラーモノリス→マイクロサービスへ</p> -</div> -</blockquote> -</div> - </div> -</section> - </div> -</section> -<section class="section-2"> - <h3 id="" class="section-header"> - - Day 2 (2021/03/28) - - </h3> - <div class="section-body"> - <div class="paragraph"> -<p>冒頭に書いた通り、2日目から体調が悪くまともに聴けていない。途中までは頭痛を我慢しつつ見ていたのだが、まともに入ってこなかった。</p> -</div> -<div class="paragraph"> -<p>残念ではあるが、いずれにせよ見られていない発表は他にもあるので、今週末にでもまとめて見ようと思う。</p> -</div> - </div> -</section> -<section class="section-2"> - <h3 id="" class="section-header"> - - 全体の感想 - - </h3> - <div class="section-body"> - <div class="paragraph"> -<p>Day 2 -にほとんど参加できなかったのは残念だが、イベント自体は大変楽しく、また興味深いものであった。自分がまったく知らない領域の話を聞けるのはこうしたイベントならではだと感じる。オンライン開催ゆえ現地に行く必要がなく、気軽に参加できたのも -(特に初参加者として) 嬉しいポイントだった。</p> -</div> -<div class="paragraph"> -<p>今回、雑談/登壇者への質問等向けに Discord -サーバもあったのだが、こちらは参加こそしたものの ROM -のままになってしまった。発表に1ウィンドウ、メモを書くのに1ウィンドウ、Discord -表示に -1ウィンドウで私にはもう脳のリソースとディスプレイのスペースが追いつかなかった -(さらにいうと Zoom -でアンカンファレンスもやっていたようだ。こちらはまったく参加していない)。</p> -</div> -<div class="paragraph"> -<p>1つ個人的な反省点としては、一つ一つのセッションを真剣に聞き過ぎたというものがある。もっと適当に聞いておけばよかった。これだけだと大変語弊があるのだが、言い方を変えると、Discord -しかりアンカンファレンスしかり「このイベントのこの瞬間にしかないコンテンツ」に触れずに、後から見返せる発表やスライドに注力してしまった、ということだ。発表の詳細な見直しはあとからできるのだから、今しかできないことを考えるべきだった。 -まあ初カンファレンスだし、とお茶を濁しておこう。</p> -</div> -<div class="paragraph"> -<p>さて、カンファレンスで一つ気になったことがある。それは、Discord -という書き込み場所が増えたことでニコ生のコメントの流量が吸い取られてしまったのではないか、という点だ。ニコニコだけ見ていると過疎っているかのように見えた発表も、Discord -の方では盛り上がっている、というのを何度か見かけた。ニコニコのコメント方式は盛り上がりを如実に反映するが、逆もまたしかり。Discord -があったこと自体はプラスだったと思うが、この点はマイナスだったのではないかと感じる。</p> -</div> -<hr> -<div class="paragraph"> -<p>最後になりましたが、毎年の PHPerKaigi -開催にご尽力されている皆様、スピーカーの皆様、楽しい3日間でした。ありがとうございました! -(ずっと常体で書いてしまったのでいきなり仏頂面から笑顔になったようで気持ち悪い)</p> -</div> -<div class="paragraph"> -<p>ではまた来年。</p> -</div> - </div> -</section> - </div> -</section> </div> - </article> </main> <footer class="footer"> diff --git a/public/posts/2021-10-02/cpp-you-can-use-keywords-in-attributes/index.html b/public/posts/2021-10-02/cpp-you-can-use-keywords-in-attributes/index.html index d844baa..233908b 100644 --- a/public/posts/2021-10-02/cpp-you-can-use-keywords-in-attributes/index.html +++ b/public/posts/2021-10-02/cpp-you-can-use-keywords-in-attributes/index.html @@ -4,18 +4,14 @@ <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="author" content="nsfisis"> - <meta name="copyright" content="© nsfisis"> + <meta name="copyright" content="© 2021 nsfisis"> <meta name="description" content="C++ の属性構文の属性名には、キーワードが使える。ネタ記事。"> <meta name="keywords" content="C++,C++ 17"> <link rel="icon" type="image/svg+xml" href="/favicon.svg"> - <title>【C++】属性構文の属性名にはキーワードが使える | REPL: Rest-Eat-Program Loop</title> - - <link rel="stylesheet" href="/hl.css?208c52e3b7c9db1cad782c5d30b4698f"> - - <link rel="stylesheet" href="/style.css?779b1a3debcaeba619f6fe500e93d525"> - - <link rel="stylesheet" href="/custom.css?a649ea3528d4b626fb636505d94c1144"> - + <title>【C++】 属性構文の属性名にはキーワードが使える | REPL: Rest-Eat-Program Loop</title> + <link rel="stylesheet" href="/hl.css?h=208c52e3b7c9db1cad782c5d30b4698f"> + <link rel="stylesheet" href="/style.css?h=779b1a3debcaeba619f6fe500e93d525"> + <link rel="stylesheet" href="/custom.css?h=a649ea3528d4b626fb636505d94c1144"> </head> <body class="single"> <header class="header"> @@ -28,183 +24,181 @@ <main class="main"> <article class="post-single"> <header class="post-header"> - <h1 class="post-title">【C++】属性構文の属性名にはキーワードが使える</h1> - - <ul class="post-tags"> - - <li class="tag"> - <a href="/tags/cpp/">C++</a> - </li> - - <li class="tag"> - <a href="/tags/cpp17/">C++ 17</a> - </li> - - </ul> - + <h1 class="post-title">【C++】 属性構文の属性名にはキーワードが使える</h1> + <ul class="post-tags"> + <li class="tag"> + <a href="/tags/cpp">C++</a> + </li> + <li class="tag"> + <a href="/tags/cpp17">C++ 17</a> + </li> + </ul> </header> <div class="post-content"> <section> <h2 id="changelog">更新履歴</h2> <ol> - - <li class="revision"> - <time datetime="2021-10-02">2021-10-02</time>: Qiita から移植 - </li> - + <li class="revision"> + <time datetime="2021-10-02">2021-10-02</time>: Qiita から移植 + </li> </ol> </section> - <div class="paragraph"> -<p>この記事は Qiita から移植してきたものです。 元 URL: -<a href="https://qiita.com/nsfisis/items/94090937bcf860cfa93b" class="bare">https://qiita.com/nsfisis/items/94090937bcf860cfa93b</a></p> -</div> -<hr> -<div class="paragraph"> -<p>タイトル落ち。まずはこのコードを見て欲しい。</p> -</div> -<div id="source." class="listingblock"> -<div class="content"> -<pre class="rouge highlight"><code data-lang="cpp"><span class="cp">#include</span> <span class="cpf"><iostream></span><span class="cp"> -</span> -<span class="p">[[</span><span class="k">alignas</span><span class="p">]]</span> <span class="p">[[</span><span class="k">alignof</span><span class="p">]]</span> <span class="p">[[</span><span class="n">and</span><span class="p">]]</span> <span class="p">[[</span><span class="n">and_eq</span><span class="p">]]</span> <span class="p">[[</span><span class="k">asm</span><span class="p">]]</span> <span class="p">[[</span><span class="k">auto</span><span class="p">]]</span> <span class="p">[[</span><span class="n">bitand</span><span class="p">]]</span> -<span class="p">[[</span><span class="n">bitor</span><span class="p">]]</span> <span class="p">[[</span><span class="kt">bool</span><span class="p">]]</span> <span class="p">[[</span><span class="k">break</span><span class="p">]]</span> <span class="p">[[</span><span class="k">case</span><span class="p">]]</span> <span class="p">[[</span><span class="k">catch</span><span class="p">]]</span> <span class="p">[[</span><span class="kt">char</span><span class="p">]]</span> <span class="p">[[</span><span class="kt">char16_t</span><span class="p">]]</span> -<span class="p">[[</span><span class="kt">char32_t</span><span class="p">]]</span> <span class="p">[[</span><span class="k">class</span><span class="p">]]</span> <span class="p">[[</span><span class="n">compl</span><span class="p">]]</span> <span class="p">[[</span><span class="k">const</span><span class="p">]]</span> <span class="p">[[</span><span class="k">const_cast</span><span class="p">]]</span> <span class="p">[[</span><span class="k">constexpr</span><span class="p">]]</span> -<span class="p">[[</span><span class="k">continue</span><span class="p">]]</span> <span class="p">[[</span><span class="k">decltype</span><span class="p">]]</span> <span class="p">[[</span><span class="k">default</span><span class="p">]]</span> <span class="p">[[</span><span class="k">delete</span><span class="p">]]</span> <span class="p">[[</span><span class="k">do</span><span class="p">]]</span> <span class="p">[[</span><span class="kt">double</span><span class="p">]]</span> -<span class="p">[[</span><span class="k">dynamic_cast</span><span class="p">]]</span> <span class="p">[[</span><span class="k">else</span><span class="p">]]</span> <span class="p">[[</span><span class="k">enum</span><span class="p">]]</span> <span class="p">[[</span><span class="k">explicit</span><span class="p">]]</span> <span class="p">[[</span><span class="k">export</span><span class="p">]]</span> <span class="p">[[</span><span class="k">extern</span><span class="p">]]</span> <span class="p">[[</span><span class="nb">false</span><span class="p">]]</span> -<span class="p">[[</span><span class="k">final</span><span class="p">]]</span> <span class="p">[[</span><span class="kt">float</span><span class="p">]]</span> <span class="p">[[</span><span class="k">for</span><span class="p">]]</span> <span class="p">[[</span><span class="k">friend</span><span class="p">]]</span> <span class="p">[[</span><span class="k">goto</span><span class="p">]]</span> <span class="p">[[</span><span class="k">if</span><span class="p">]]</span> <span class="p">[[</span><span class="kr">inline</span><span class="p">]]</span> <span class="p">[[</span><span class="kt">int</span><span class="p">]]</span> -<span class="p">[[</span><span class="kt">long</span><span class="p">]]</span> <span class="p">[[</span><span class="k">mutable</span><span class="p">]]</span> <span class="p">[[</span><span class="k">namespace</span><span class="p">]]</span> <span class="p">[[</span><span class="k">new</span><span class="p">]]</span> <span class="p">[[</span><span class="k">noexcept</span><span class="p">]]</span> <span class="p">[[</span><span class="n">not</span><span class="p">]]</span> <span class="p">[[</span><span class="n">not_eq</span><span class="p">]]</span> -<span class="p">[[</span><span class="nb">nullptr</span><span class="p">]]</span> <span class="p">[[</span><span class="k">operator</span><span class="p">]]</span> <span class="p">[[</span><span class="n">or</span><span class="p">]]</span> <span class="p">[[</span><span class="n">or_eq</span><span class="p">]]</span> <span class="p">[[</span><span class="k">override</span><span class="p">]]</span> <span class="p">[[</span><span class="k">private</span><span class="p">]]</span> -<span class="p">[[</span><span class="k">protected</span><span class="p">]]</span> <span class="p">[[</span><span class="k">public</span><span class="p">]]</span> <span class="p">[[</span><span class="k">register</span><span class="p">]]</span> <span class="p">[[</span><span class="k">reinterpret_cast</span><span class="p">]]</span> <span class="p">[[</span><span class="k">return</span><span class="p">]]</span> <span class="p">[[</span><span class="kt">short</span><span class="p">]]</span> -<span class="p">[[</span><span class="kt">signed</span><span class="p">]]</span> <span class="p">[[</span><span class="k">sizeof</span><span class="p">]]</span> <span class="p">[[</span><span class="k">static</span><span class="p">]]</span> <span class="p">[[</span><span class="k">static_assert</span><span class="p">]]</span> <span class="p">[[</span><span class="k">static_cast</span><span class="p">]]</span> <span class="p">[[</span><span class="k">struct</span><span class="p">]]</span> -<span class="p">[[</span><span class="k">switch</span><span class="p">]]</span> <span class="p">[[</span><span class="k">template</span><span class="p">]]</span> <span class="p">[[</span><span class="k">this</span><span class="p">]]</span> <span class="p">[[</span><span class="k">thread_local</span><span class="p">]]</span> <span class="p">[[</span><span class="k">throw</span><span class="p">]]</span> <span class="p">[[</span><span class="nb">true</span><span class="p">]]</span> <span class="p">[[</span><span class="k">try</span><span class="p">]]</span> -<span class="p">[[</span><span class="k">typedef</span><span class="p">]]</span> <span class="p">[[</span><span class="k">typeid</span><span class="p">]]</span> <span class="p">[[</span><span class="k">typename</span><span class="p">]]</span> <span class="p">[[</span><span class="k">union</span><span class="p">]]</span> <span class="p">[[</span><span class="kt">unsigned</span><span class="p">]]</span> -<span class="p">[[</span><span class="k">virtual</span><span class="p">]]</span> <span class="p">[[</span><span class="kt">void</span><span class="p">]]</span> <span class="p">[[</span><span class="k">volatile</span><span class="p">]]</span> <span class="p">[[</span><span class="kt">wchar_t</span><span class="p">]]</span> <span class="p">[[</span><span class="k">while</span><span class="p">]]</span> <span class="p">[[</span><span class="n">xor</span><span class="p">]]</span> <span class="p">[[</span><span class="n">xor_eq</span><span class="p">]]</span> -<span class="c1">// [[using]]</span> -<span class="kt">int</span> <span class="n">main</span><span class="p">()</span> <span class="p">{</span> - <span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o"><<</span> <span class="s">"Hello, World!"</span> <span class="o"><<</span> <span class="n">std</span><span class="o">::</span><span class="n">endl</span><span class="p">;</span> -<span class="p">}</span></code></pre> -</div> -</div> -<div class="quoteblock"> -<blockquote> -<div class="paragraph"> -<p>コンパイラのバージョン $ clang++ –version Apple clang version 11.0.0 -(clang-1100.0.33.8) Target: x86_64-apple-darwin19.6.0 Thread model: -posix InstalledDir: -/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin</p> -</div> -<div class="paragraph"> -<p>コンパイルコマンド (C17指定) $ clang –std=c++17 hoge.cpp</p> -</div> -</blockquote> -</div> -<div class="paragraph"> -<p>この記事から得られるものはこれ以上ないので以下は蛇足になる。</p> -</div> -<div class="paragraph"> -<p>別件で cppreference.com の -<a href="https://en.cppreference.com/w/cpp/language/identifiers">identifier -のページ</a> を読んでいた時、次の文が目に止まった。</p> -</div> -<div class="quoteblock"> -<blockquote> -<div class="ulist"> -<ul> -<li> -<p>the identifiers that are keywords cannot be used for other purposes;</p> -<div class="ulist"> -<ul> -<li> -<p>The only place they can be used as non-keywords is in an -attribute-token. (e.g. <a id="private"></a> is a valid attribute) (since C++11)</p> -</li> -</ul> -</div> -</li> -</ul> -</div> -</blockquote> -</div> -<div class="paragraph"> -<p>キーワードでも属性として指定する場合は非キーワードとして使えるらしい。 -実際にやってみる。</p> -</div> -<div class="paragraph"> -<p>同サイトの <a href="https://en.cppreference.com/w/cpp/keyword">keywords のページ</a> -から一覧を拝借し、上のコードが出来上がった (C++17 -においてキーワードでないものなど、一部省いている)。 大量の警告 (unknown -attribute `〇〇' ignored) -がコンパイラから出力されるが、コンパイルできる。</p> -</div> -<div class="paragraph"> -<p>上のコードでは <code><a id="using"></a></code> をコメントアウトしているが、これは <code>using</code> -キーワードのみ属性構文の中で意味を持つからであり、このコメントアウトを外すとコンパイルに失敗する。</p> -</div> -<div id="source." class="listingblock"> -<div class="content"> -<pre class="rouge highlight"><code data-lang="cpp"><span class="c1">// using の例</span> -<span class="p">[[</span><span class="k">using</span> <span class="n">foo</span><span class="o">:</span> <span class="n">attr1</span><span class="p">,</span> <span class="n">attr2</span><span class="p">]]</span> <span class="kt">int</span> <span class="n">x</span><span class="p">;</span> <span class="c1">// [[foo::attr1, foo::attr2]] の糖衣構文</span></code></pre> -</div> -</div> -<div class="paragraph"> -<p>C++17 の仕様も見てみる (正確には標準化前のドラフト)。</p> -</div> -<div class="paragraph"> -<p>引用元: <a href="https://timsong-cpp.github.io/cppwp/n4659/dcl.attr#grammar-4" class="bare">https://timsong-cpp.github.io/cppwp/n4659/dcl.attr#grammar-4</a></p> -</div> -<div class="quoteblock"> -<blockquote> -<div class="paragraph"> -<p>If a keyword or an alternative token that satisfies the syntactic -requirements of an identifier is contained in an attribute-token, it is -considered an identifier.</p> -</div> -</blockquote> -</div> -<div class="paragraph"> -<p>「<code>identifier</code> の構文上の要件を満たすキーワードまたは代替トークンが -<code>attribute-token</code> に含まれている場合、<code>identifier</code> -とみなされる」とある。どうやら間違いないようだ。</p> -</div> -<div class="paragraph"> -<p>ところで、代替トークン (alternative token) とは <code>and</code> (<code>&</code>) や <code>bitor</code> -(<code>|</code>) などのことだが、<code>identifier</code> -の構文上の要件を満たさないような代替トークンなどあるのか? -疑問に思って調べたところ、代替トークンという語にはダイグラフも含まれるらしい -(参考: -<a href="https://timsong-cpp.github.io/cppwp/n4659/lex.digraph">同ドラフト</a>)</p> -</div> -<div class="ulist"> -<ul> -<li> -<p><code><%</code> → <code>{</code></p> -</li> -<li> -<p><code>%></code> → <code>}</code></p> -</li> -<li> -<p><code><:</code> → <code>[</code></p> -</li> -<li> -<p><code>:></code> → <code>]</code></p> -</li> -<li> -<p><code>%:</code> → <code>#</code></p> -</li> -<li> -<p><code>%:%:</code> → <code>##</code></p> -</li> -</ul> -</div> -<div class="paragraph"> -<p>「<code>identifier</code> -の構文上の要件を満たさないような代替トークン」はこれらが当てはまると思われる。</p> -</div> -<div class="paragraph"> -<p>調べた感想: 字句解析器か構文解析器が辛そう</p> -</div> + <p> + この記事は Qiita から移植してきたものです。 元 URL:<a xl:href="https://qiita.com/nsfisis/items/94090937bcf860cfa93b">https://qiita.com/nsfisis/items/94090937bcf860cfa93b</a> + </p> + + <p> + <hr> + </hr> + </p> + + <p> + タイトル落ち。まずはこのコードを見て欲しい。 + </p> + + <pre language="cpp" linenumbering="unnumbered"> + <code>#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; +}</code> + </pre> + + <blockquote> + <p> + コンパイラのバージョン $ clang++ –version Apple clang version 11.0.0 (clang-1100.0.33.8) Target: x86_64-apple-darwin19.6.0 Thread model: posix InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin + </p> + + <p> + コンパイルコマンド (C17指定) $ clang –std=c++17 hoge.cpp + </p> + </blockquote> + + <p> + この記事から得られるものはこれ以上ないので以下は蛇足になる。 + </p> + + <p> + 別件で cppreference.com の<a xl:href="https://en.cppreference.com/w/cpp/language/identifiers">identifier のページ</a>を読んでいた時、次の文が目に止まった。 + </p> + + <blockquote> + <ul> + <li> + <p> + the identifiers that are keywords cannot be used for other purposes; + </p> + + <ul> + <li> + <p> + The only place they can be used as non-keywords is in an attribute-token. (e.g. [[private]] is a valid attribute) (since C++11) + </p> + </li> + </ul> + </li> + </ul> + </blockquote> + + <p> + キーワードでも属性として指定する場合は非キーワードとして使えるらしい。 実際にやってみる。 + </p> + + <p> + 同サイトの<a xl:href="https://en.cppreference.com/w/cpp/keyword">keywords のページ</a>から一覧を拝借し、上のコードが出来上がった (C++17 においてキーワードでないものなど、一部省いている)。 大量の警告 (unknown attribute `〇〇' ignored) がコンパイラから出力されるが、コンパイルできる。 + </p> + + <p> + 上のコードでは<code>[[using]]</code>をコメントアウトしているが、これは<code>using</code>キーワードのみ属性構文の中で意味を持つからであり、このコメントアウトを外すとコンパイルに失敗する。 + </p> + + <pre language="cpp" linenumbering="unnumbered"> + <code>// using の例 +[[using foo: attr1, attr2]] int x; // [[foo::attr1, foo::attr2]] の糖衣構文</code> + </pre> + + <p> + C++17 の仕様も見てみる (正確には標準化前のドラフト)。 + </p> + + <p> + 引用元:<a xl:href="https://timsong-cpp.github.io/cppwp/n4659/dcl.attr#grammar-4">https://timsong-cpp.github.io/cppwp/n4659/dcl.attr#grammar-4</a> + </p> + + <blockquote> + <p> + If a keyword or an alternative token that satisfies the syntactic requirements of an identifier is contained in an attribute-token, it is considered an identifier. + </p> + </blockquote> + + <p> + 「<code>identifier</code>の構文上の要件を満たすキーワードまたは代替トークンが<code>attribute-token</code>に含まれている場合、<code>identifier</code>とみなされる」とある。どうやら間違いないようだ。 + </p> + + <p> + ところで、代替トークン (alternative token) とは<code>and</code>(<code>&</code>) や<code>bitor</code>(<code>|</code>) などのことだが、<code>identifier</code>の構文上の要件を満たさないような代替トークンなどあるのか? 疑問に思って調べたところ、代替トークンという語にはダイグラフも含まれるらしい (参考:<a xl:href="https://timsong-cpp.github.io/cppwp/n4659/lex.digraph">同ドラフト</a>) + </p> + + <ul> + <li> + <p> + <code><%</code>→<code>{</code> + </p> + </li> + + <li> + <p> + <code>%></code>→<code>}</code> + </p> + </li> + + <li> + <p> + <code><:</code>→<code>[</code> + </p> + </li> + + <li> + <p> + <code>:></code>→<code>]</code> + </p> + </li> + + <li> + <p> + <code>%:</code>→<code>#</code> + </p> + </li> + + <li> + <p> + <code>%:%:</code>→<code>##</code> + </p> + </li> + </ul> + + <p> + 「<code>identifier</code>の構文上の要件を満たさないような代替トークン」はこれらが当てはまると思われる。 + </p> + + <p> + 調べた感想: 字句解析器か構文解析器が辛そう + </p> </div> - </article> </main> <footer class="footer"> diff --git a/public/posts/2021-10-02/python-unbound-local-error/index.html b/public/posts/2021-10-02/python-unbound-local-error/index.html index bc93332..ee8d6f2 100644 --- a/public/posts/2021-10-02/python-unbound-local-error/index.html +++ b/public/posts/2021-10-02/python-unbound-local-error/index.html @@ -4,18 +4,14 @@ <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="author" content="nsfisis"> - <meta name="copyright" content="© nsfisis"> + <meta name="copyright" content="© 2021 nsfisis"> <meta name="description" content="Python における UnboundLocalError の理由と対処法。"> <meta name="keywords" content="Python,Python 3"> <link rel="icon" type="image/svg+xml" href="/favicon.svg"> - <title>【Python】クロージャとUnboundLocalError: local variable 'x' referenced before assignment | REPL: Rest-Eat-Program Loop</title> - - <link rel="stylesheet" href="/hl.css?208c52e3b7c9db1cad782c5d30b4698f"> - - <link rel="stylesheet" href="/style.css?779b1a3debcaeba619f6fe500e93d525"> - - <link rel="stylesheet" href="/custom.css?a649ea3528d4b626fb636505d94c1144"> - + <title>【Python】 クロージャとUnboundLocalError: local variable 'x' referenced before assignment | REPL: Rest-Eat-Program Loop</title> + <link rel="stylesheet" href="/hl.css?h=208c52e3b7c9db1cad782c5d30b4698f"> + <link rel="stylesheet" href="/style.css?h=779b1a3debcaeba619f6fe500e93d525"> + <link rel="stylesheet" href="/custom.css?h=a649ea3528d4b626fb636505d94c1144"> </head> <body class="single"> <header class="header"> @@ -28,105 +24,96 @@ <main class="main"> <article class="post-single"> <header class="post-header"> - <h1 class="post-title">【Python】クロージャとUnboundLocalError: local variable 'x' referenced before assignment</h1> - - <ul class="post-tags"> - - <li class="tag"> - <a href="/tags/python/">Python</a> - </li> - - <li class="tag"> - <a href="/tags/python3/">Python 3</a> - </li> - - </ul> - + <h1 class="post-title">【Python】 クロージャとUnboundLocalError: local variable 'x' referenced before assignment</h1> + <ul class="post-tags"> + <li class="tag"> + <a href="/tags/python">Python</a> + </li> + <li class="tag"> + <a href="/tags/python3">Python 3</a> + </li> + </ul> </header> <div class="post-content"> <section> <h2 id="changelog">更新履歴</h2> <ol> - - <li class="revision"> - <time datetime="2021-10-02">2021-10-02</time>: Qiita から移植 - </li> - + <li class="revision"> + <time datetime="2021-10-02">2021-10-02</time>: Qiita から移植 + </li> </ol> </section> - <div class="paragraph"> -<p>この記事は Qiita から移植してきたものです。 元 URL: -<a href="https://qiita.com/nsfisis/items/5d733703afcb35bbf399" class="bare">https://qiita.com/nsfisis/items/5d733703afcb35bbf399</a></p> -</div> -<hr> -<div class="paragraph"> -<p>本記事は Python 3.7.6 の動作結果を元にして書かれている。</p> -</div> -<div class="paragraph"> -<p>Python でクロージャを作ろうと、次のようなコードを書いた。</p> -</div> -<div id="source." class="listingblock"> -<div class="content"> -<pre class="rouge highlight"><code data-lang="python"><span class="k">def</span> <span class="nf">f</span><span class="p">():</span> - <span class="n">x</span> <span class="o">=</span> <span class="mi">0</span> - <span class="k">def</span> <span class="nf">g</span><span class="p">():</span> - <span class="n">x</span> <span class="o">+=</span> <span class="mi">1</span> - <span class="n">g</span><span class="p">()</span> + <p> + この記事は Qiita から移植してきたものです。 元 URL:<a xl:href="https://qiita.com/nsfisis/items/5d733703afcb35bbf399">https://qiita.com/nsfisis/items/5d733703afcb35bbf399</a> + </p> + + <p> + <hr> + </hr> + </p> + + <p> + 本記事は Python 3.7.6 の動作結果を元にして書かれている。 + </p> + + <p> + Python でクロージャを作ろうと、次のようなコードを書いた。 + </p> + + <pre language="python" linenumbering="unnumbered"> + <code>def f(): +x = 0 +def g(): +x += 1 +g() -<span class="n">f</span><span class="p">()</span></code></pre> -</div> -</div> -<div class="paragraph"> -<p>関数 <code>g</code> から 関数 <code>f</code> のスコープ内で定義された変数 <code>x</code> を参照し、それに -1 を足そうとしている。 これを実行すると <code>x += 1</code> -の箇所でエラーが発生する。</p> -</div> -<div class="quoteblock"> -<blockquote> -<div class="paragraph"> -<p>UnboundLocalError: local variable `x' referenced before assignment</p> -</div> -</blockquote> -</div> -<div class="paragraph"> -<p>local変数 <code>x</code> が代入前に参照された、とある。これは、<code>f</code> の <code>x</code> -を参照するのではなく、新しく別の変数を <code>g</code> 内に作ってしまっているため。 -前述のコードを宣言と代入を便宜上分けて書き直すと次のようになる。<code>var</code> -を変数宣言のための構文として擬似的に利用している。</p> -</div> -<div id="source." class="listingblock"> -<div class="content"> -<pre class="rouge highlight"><code data-lang="python"><span class="c1"># 注: var は正しい Python の文法ではない。上記参照のこと -</span><span class="k">def</span> <span class="nf">f</span><span class="p">():</span> - <span class="n">var</span> <span class="n">x</span> <span class="c1"># f の local変数 'x' を宣言 -</span> <span class="n">x</span> <span class="o">=</span> <span class="mi">0</span> <span class="c1"># x に 0 を代入 -</span> <span class="k">def</span> <span class="nf">g</span><span class="p">():</span> <span class="c1"># f の内部関数 g を定義 -</span> <span class="n">var</span> <span class="n">x</span> <span class="c1"># g の local変数 'x' を宣言 -</span> <span class="c1"># たまたま f にも同じ名前の変数があるが、それとは別の変数 -</span> <span class="n">x</span> <span class="o">+=</span> <span class="mi">1</span> <span class="c1"># x に 1 を加算 (x = x + 1 の糖衣構文) -</span> <span class="c1"># 加算する前の値を参照しようとするが、まだ代入されていないためエラー -</span> <span class="n">g</span><span class="p">()</span></code></pre> -</div> -</div> -<div class="paragraph"> -<p>当初の意図を表現するには、次のように書けばよい。</p> -</div> -<div id="source." class="listingblock"> -<div class="content"> -<pre class="rouge highlight"><code data-lang="python"><span class="k">def</span> <span class="nf">f</span><span class="p">():</span> - <span class="n">x</span> <span class="o">=</span> <span class="mi">0</span> - <span class="k">def</span> <span class="nf">g</span><span class="p">():</span> - <span class="k">nonlocal</span> <span class="n">x</span> <span class="c1">## (*) -</span> <span class="n">x</span> <span class="o">+=</span> <span class="mi">1</span> - <span class="n">g</span><span class="p">()</span></code></pre> -</div> -</div> -<div class="paragraph"> -<p><code>(*)</code> のように、<code>nonlocal</code> を追加する。これにより一つ外側のスコープ (<code>g</code> -の一つ外側 = <code>f</code>) で定義されている <code>x</code> を探しに行くようになる。</p> -</div> +f()</code> + </pre> + + <p> + 関数<code>g</code>から 関数<code>f</code>のスコープ内で定義された変数<code>x</code>を参照し、それに 1 を足そうとしている。 これを実行すると<code>x += 1</code>の箇所でエラーが発生する。 + </p> + + <blockquote> + <p> + UnboundLocalError: local variable `x' referenced before assignment + </p> + </blockquote> + + <p> + local変数<code>x</code>が代入前に参照された、とある。これは、<code>f</code>の<code>x</code>を参照するのではなく、新しく別の変数を<code>g</code>内に作ってしまっているため。 前述のコードを宣言と代入を便宜上分けて書き直すと次のようになる。<code>var</code>を変数宣言のための構文として擬似的に利用している。 + </p> + + <pre language="python" linenumbering="unnumbered"> + <code># 注: 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()</code> + </pre> + + <p> + 当初の意図を表現するには、次のように書けばよい。 + </p> + + <pre language="python" linenumbering="unnumbered"> + <code>def f(): +x = 0 +def g(): +nonlocal x ## (*) +x += 1 +g()</code> + </pre> + + <p> + <code>(*)</code>のように、<code>nonlocal</code>を追加する。これにより一つ外側のスコープ (<code>g</code>の一つ外側 =<code>f</code>) で定義されている<code>x</code>を探しに行くようになる。 + </p> </div> - </article> </main> <footer class="footer"> diff --git a/public/posts/2021-10-02/ruby-detect-running-implementation/index.html b/public/posts/2021-10-02/ruby-detect-running-implementation/index.html index dac7277..cf04687 100644 --- a/public/posts/2021-10-02/ruby-detect-running-implementation/index.html +++ b/public/posts/2021-10-02/ruby-detect-running-implementation/index.html @@ -4,18 +4,14 @@ <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="author" content="nsfisis"> - <meta name="copyright" content="© nsfisis"> - <meta name="description" content="Ruby には複数の実装があるが、自身を実行している処理系の種類を スクリプト上からどのように判定すればよいだろうか。"> + <meta name="copyright" content="© 2021 nsfisis"> + <meta name="description" content="Ruby には複数の実装があるが、自身を実行している処理系の種類をスクリプト上からどのように判定すればよいだろうか。"> <meta name="keywords" content="Ruby"> <link rel="icon" type="image/svg+xml" href="/favicon.svg"> - <title>【Ruby】自身を実行している処理系の種類を判定する | REPL: Rest-Eat-Program Loop</title> - - <link rel="stylesheet" href="/hl.css?208c52e3b7c9db1cad782c5d30b4698f"> - - <link rel="stylesheet" href="/style.css?779b1a3debcaeba619f6fe500e93d525"> - - <link rel="stylesheet" href="/custom.css?a649ea3528d4b626fb636505d94c1144"> - + <title>【Ruby】 自身を実行している処理系の種類を判定する | REPL: Rest-Eat-Program Loop</title> + <link rel="stylesheet" href="/hl.css?h=208c52e3b7c9db1cad782c5d30b4698f"> + <link rel="stylesheet" href="/style.css?h=779b1a3debcaeba619f6fe500e93d525"> + <link rel="stylesheet" href="/custom.css?h=a649ea3528d4b626fb636505d94c1144"> </head> <body class="single"> <header class="header"> @@ -28,141 +24,181 @@ <main class="main"> <article class="post-single"> <header class="post-header"> - <h1 class="post-title">【Ruby】自身を実行している処理系の種類を判定する</h1> - - <ul class="post-tags"> - - <li class="tag"> - <a href="/tags/ruby/">Ruby</a> - </li> - - </ul> - + <h1 class="post-title">【Ruby】 自身を実行している処理系の種類を判定する</h1> + <ul class="post-tags"> + <li class="tag"> + <a href="/tags/ruby">Ruby</a> + </li> + </ul> </header> <div class="post-content"> <section> <h2 id="changelog">更新履歴</h2> <ol> - - <li class="revision"> - <time datetime="2021-10-02">2021-10-02</time>: Qiita から移植 - </li> - + <li class="revision"> + <time datetime="2021-10-02">2021-10-02</time>: Qiita から移植 + </li> </ol> </section> - <div class="paragraph"> -<p>この記事は Qiita から移植してきたものです。 元 URL: -<a href="https://qiita.com/nsfisis/items/74d7ffeeebc51b20d791" class="bare">https://qiita.com/nsfisis/items/74d7ffeeebc51b20d791</a></p> -</div> -<hr> -<div class="paragraph"> -<p>Ruby -という言語には複数の実装があるが、それらをスクリプト上からどのようにして -programmatically に見分ければよいだろうか。</p> -</div> -<div class="paragraph"> -<p><code>Object</code> クラスに定義されている <code>RUBY_ENGINE</code> -という定数がこの用途に使える。</p> -</div> -<div class="paragraph"> -<p>参考: -<a href="https://docs.ruby-lang.org/ja/latest/method/Object/c/RUBY_ENGINE.html">Object::RUBY_ENGINE</a></p> -</div> -<div class="paragraph"> -<p>上記ページの例から引用する:</p> -</div> -<div id="source." class="listingblock"> -<div class="content"> -<pre class="rouge highlight"><code data-lang="shell-session"><span class="gp">$</span><span class="w"> </span>ruby-1.9.1 <span class="nt">-ve</span> <span class="s1">'p RUBY_ENGINE'</span> -<span class="go">ruby 1.9.1p0 (2009-03-04 revision 22762) [x86_64-linux] -"ruby" -</span><span class="gp">$</span><span class="w"> </span>jruby <span class="nt">-ve</span> <span class="s1">'p RUBY_ENGINE'</span> -<span class="go">jruby 1.2.0 (ruby 1.8.6 patchlevel 287) (2009-03-16 rev 9419) [i386-java] -"jruby"</span></code></pre> -</div> -</div> -<div class="paragraph"> -<p>それぞれの処理系がどのような値を返すかだが、stack overflow -に良い質問と回答があった。</p> -</div> -<div class="paragraph"> -<p><a href="https://stackoverflow.com/a/9894232">What values for RUBY_ENGINE -correspond to which Ruby implementations?</a> より引用:</p> -</div> -<div class="quoteblock"> -<blockquote> -<table class="tableblock frame-all grid-all stretch"> -<colgroup> -<col style="width: 50%;"> -<col style="width: 50%;"> -</colgroup> -<thead> -<tr> -<th class="tableblock halign-center valign-top">RUBY_ENGINE</th> -<th class="tableblock halign-left valign-top">Implementation</th> -</tr> -</thead> -<tbody> -<tr> -<td class="tableblock halign-center valign-top"><p class="tableblock"><undefined></p></td> -<td class="tableblock halign-left valign-top"><p class="tableblock">MRI < 1.9</p></td> -</tr> -<tr> -<td class="tableblock halign-center valign-top"><p class="tableblock">`ruby'</p></td> -<td class="tableblock halign-left valign-top"><p class="tableblock">MRI >= 1.9 or REE</p></td> -</tr> -<tr> -<td class="tableblock halign-center valign-top"><p class="tableblock">`jruby'</p></td> -<td class="tableblock halign-left valign-top"><p class="tableblock">JRuby</p></td> -</tr> -<tr> -<td class="tableblock halign-center valign-top"><p class="tableblock">`macruby'</p></td> -<td class="tableblock halign-left valign-top"><p class="tableblock">MacRuby</p></td> -</tr> -<tr> -<td class="tableblock halign-center valign-top"><p class="tableblock">`rbx'</p></td> -<td class="tableblock halign-left valign-top"><p class="tableblock">Rubinius</p></td> -</tr> -<tr> -<td class="tableblock halign-center valign-top"><p class="tableblock">`maglev'</p></td> -<td class="tableblock halign-left valign-top"><p class="tableblock">MagLev</p></td> -</tr> -<tr> -<td class="tableblock halign-center valign-top"><p class="tableblock">`ironruby'</p></td> -<td class="tableblock halign-left valign-top"><p class="tableblock">IronRuby</p></td> -</tr> -<tr> -<td class="tableblock halign-center valign-top"><p class="tableblock">`cardinal'</p></td> -<td class="tableblock halign-left valign-top"><p class="tableblock">Cardinal</p></td> -</tr> -</tbody> -</table> -</blockquote> -</div> -<div class="paragraph"> -<p>なお、この質問・回答は -2014年になされたものであり、値は変わっている可能性がある。MRI (aka -CRuby) については執筆時現在 (2020/12/8) も <code>'ruby'</code> -が返ってくることを確認済み。</p> -</div> -<div class="paragraph"> -<p>この表にない主要な処理系として、https://mruby.org[mruby] は <code>'mruby'</code> -を返す。</p> -</div> -<div class="paragraph"> -<p><a href="https://github.com/mruby/mruby/blob/ed29d74bfd95362eaeb946fcf7e865d80346b62b/include/mruby/version.h#L32-L35">mruby -該当部分のソース</a> より引用:</p> -</div> -<div id="source." class="listingblock"> -<div class="content"> -<pre class="rouge highlight"><code data-lang="c"><span class="cm">/* - * Ruby engine. - */</span> -<span class="cp">#define MRUBY_RUBY_ENGINE "mruby"</span></code></pre> -</div> -</div> + <p> + この記事は Qiita から移植してきたものです。 元 URL:<a xl:href="https://qiita.com/nsfisis/items/74d7ffeeebc51b20d791">https://qiita.com/nsfisis/items/74d7ffeeebc51b20d791</a> + </p> + + <p> + <hr> + </hr> + </p> + + <p> + Ruby という言語には複数の実装があるが、それらをスクリプト上からどのようにして programmatically に見分ければよいだろうか。 + </p> + + <p> + <code>Object</code>クラスに定義されている<code>RUBY_ENGINE</code>という定数がこの用途に使える。 + </p> + + <p> + 参考:<a xl:href="https://docs.ruby-lang.org/ja/latest/method/Object/c/RUBY_ENGINE.html">Object::RUBY_ENGINE</a> + </p> + + <p> + 上記ページの例から引用する: + </p> + + <pre language="shell-session" linenumbering="unnumbered"> + <code>$ 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"</code> + </pre> + + <p> + それぞれの処理系がどのような値を返すかだが、stack overflow に良い質問と回答があった。 + </p> + + <p> + <a xl:href="https://stackoverflow.com/a/9894232">What values for RUBY_ENGINE correspond to which Ruby implementations?</a>より引用: + </p> + + <blockquote> + <table> + <thead> + <tr> + <td> + RUBY_ENGINE + </td> + + <td> + Implementation + </td> + </tr> + </thead> + + <tbody> + <tr> + <td> + <undefined> + </td> + + <td> + MRI < 1.9 + </td> + </tr> + + <tr> + <td> + `ruby' + </td> + + <td> + MRI >= 1.9 or REE + </td> + </tr> + + <tr> + <td> + `jruby' + </td> + + <td> + JRuby + </td> + </tr> + + <tr> + <td> + `macruby' + </td> + + <td> + MacRuby + </td> + </tr> + + <tr> + <td> + `rbx' + </td> + + <td> + Rubinius + </td> + </tr> + + <tr> + <td> + `maglev' + </td> + + <td> + MagLev + </td> + </tr> + + <tr> + <td> + `ironruby' + </td> + + <td> + IronRuby + </td> + </tr> + + <tr> + <td> + `cardinal' + </td> + + <td> + Cardinal + </td> + </tr> + </tbody> + </table> + </blockquote> + + <p> + なお、この質問・回答は 2014年になされたものであり、値は変わっている可能性がある。MRI (aka CRuby) については執筆時現在 (2020/12/8) も<code>'ruby'</code>が返ってくることを確認済み。 + </p> + + <p> + この表にない主要な処理系として、https://mruby.org[mruby] は<code>'mruby'</code>を返す。 + </p> + + <p> + <a xl:href="https://github.com/mruby/mruby/blob/ed29d74bfd95362eaeb946fcf7e865d80346b62b/include/mruby/version.h#L32-L35">mruby 該当部分のソース</a>より引用: + </p> + + <pre language="c" linenumbering="unnumbered"> + <code>/* +* Ruby engine. +*/ +#define MRUBY_RUBY_ENGINE "mruby"</code> + </pre> </div> - </article> </main> <footer class="footer"> diff --git a/public/posts/2021-10-02/ruby-then-keyword-and-case-in/index.html b/public/posts/2021-10-02/ruby-then-keyword-and-case-in/index.html index 62f6c43..2c6e31c 100644 --- a/public/posts/2021-10-02/ruby-then-keyword-and-case-in/index.html +++ b/public/posts/2021-10-02/ruby-then-keyword-and-case-in/index.html @@ -4,18 +4,14 @@ <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="author" content="nsfisis"> - <meta name="copyright" content="© nsfisis"> + <meta name="copyright" content="© 2021 nsfisis"> <meta name="description" content="Ruby 3.0 で追加される case in 構文と、then キーワードについて。"> <meta name="keywords" content="Ruby,Ruby 3"> <link rel="icon" type="image/svg+xml" href="/favicon.svg"> - <title>【Ruby】then キーワードと case in | REPL: Rest-Eat-Program Loop</title> - - <link rel="stylesheet" href="/hl.css?208c52e3b7c9db1cad782c5d30b4698f"> - - <link rel="stylesheet" href="/style.css?779b1a3debcaeba619f6fe500e93d525"> - - <link rel="stylesheet" href="/custom.css?a649ea3528d4b626fb636505d94c1144"> - + <title>【Ruby】 then キーワードと case in | REPL: Rest-Eat-Program Loop</title> + <link rel="stylesheet" href="/hl.css?h=208c52e3b7c9db1cad782c5d30b4698f"> + <link rel="stylesheet" href="/style.css?h=779b1a3debcaeba619f6fe500e93d525"> + <link rel="stylesheet" href="/custom.css?h=a649ea3528d4b626fb636505d94c1144"> </head> <body class="single"> <header class="header"> @@ -28,336 +24,283 @@ <main class="main"> <article class="post-single"> <header class="post-header"> - <h1 class="post-title">【Ruby】then キーワードと case in</h1> - - <ul class="post-tags"> - - <li class="tag"> - <a href="/tags/ruby/">Ruby</a> - </li> - - <li class="tag"> - <a href="/tags/ruby3/">Ruby 3</a> - </li> - - </ul> - + <h1 class="post-title">【Ruby】 then キーワードと case in</h1> + <ul class="post-tags"> + <li class="tag"> + <a href="/tags/ruby">Ruby</a> + </li> + <li class="tag"> + <a href="/tags/ruby3">Ruby 3</a> + </li> + </ul> </header> <div class="post-content"> <section> <h2 id="changelog">更新履歴</h2> <ol> - - <li class="revision"> - <time datetime="2021-10-02">2021-10-02</time>: Qiita から移植 - </li> - + <li class="revision"> + <time datetime="2021-10-02">2021-10-02</time>: Qiita から移植 + </li> </ol> </section> - <div id="preamble"> -<div class="sectionbody"> -<div class="paragraph"> -<p>この記事は Qiita から移植してきたものです。 元 URL: -<a href="https://qiita.com/nsfisis/items/787a8cf888a304497223" class="bare">https://qiita.com/nsfisis/items/787a8cf888a304497223</a></p> -</div> -<hr> -</div> -</div> -<section class="section-1"> - <h2 id="" class="section-header"> - - TL; DR - - </h2> - <div class="section-body"> - <div class="paragraph"> -<p><code>case</code> - <code>in</code> によるパターンマッチング構文でも、<code>case</code> - <code>when</code> -と同じように <code>then</code> が使える (場合によっては使う必要がある)。</p> -</div> - </div> -</section> -<section class="section-1"> - <h2 id="" class="section-header"> - - <code>then</code> とは - - </h2> - <div class="section-body"> - <div class="paragraph"> -<p>使われることは稀だが、Ruby では <code>then</code> -がキーワードになっている。次のように使う:</p> -</div> -<div id="source." class="listingblock"> -<div class="content"> -<pre class="rouge highlight"><code data-lang="ruby"><span class="k">if</span> <span class="n">cond</span> <span class="k">then</span> - <span class="nb">puts</span> <span class="s2">"Y"</span> -<span class="k">else</span> - <span class="nb">puts</span> <span class="s2">"N"</span> -<span class="k">end</span></code></pre> -</div> -</div> -<div class="paragraph"> -<p>このキーワードが現れうる場所はいくつかあり、<code>if</code>、<code>unless</code>、<code>rescue</code>、<code>case</code> -構文がそれに当たる。 上記のように、何か条件を書いた後 <code>then</code> -を置き、式がそこで終了していることを示すマーカーとして機能する。</p> -</div> -<div id="source." class="listingblock"> -<div class="content"> -<pre class="rouge highlight"><code data-lang="ruby"><span class="c1"># Example:</span> + <p> + この記事は Qiita から移植してきたものです。 元 URL:<a xl:href="https://qiita.com/nsfisis/items/787a8cf888a304497223">https://qiita.com/nsfisis/items/787a8cf888a304497223</a> + </p> + + <p> + <hr> + </hr> + </p> + + <section id="section--_tl_dr"> + <h2><a href="#section--_tl_dr">TL; DR</a></h2> + <p> + <code>case</code>-<code>in</code>によるパターンマッチング構文でも、<code>case</code>-<code>when</code>と同じように<code>then</code>が使える (場合によっては使う必要がある)。 + </p> + </section> + + <section id="section--_then_とは"> + <h2><a href="#section--_then_とは"><code>then</code>とは</a></h2> + <p> + 使われることは稀だが、Ruby では<code>then</code>がキーワードになっている。次のように使う: + </p> + + <pre language="ruby" linenumbering="unnumbered"> + <code>if cond then + puts "Y" + else + puts "N" + end</code> + </pre> + + <p> + このキーワードが現れうる場所はいくつかあり、<code>if</code>、<code>unless</code>、<code>rescue</code>、<code>case</code>構文がそれに当たる。 上記のように、何か条件を書いた後<code>then</code>を置き、式がそこで終了していることを示すマーカーとして機能する。 + </p> + + <pre language="ruby" linenumbering="unnumbered"> + <code># Example: -<span class="k">if</span> <span class="n">x</span> <span class="k">then</span> - <span class="n">a</span> -<span class="k">end</span> +if x then +a +end -<span class="k">unless</span> <span class="n">x</span> <span class="k">then</span> - <span class="n">a</span> -<span class="k">end</span> +unless x then +a +end -<span class="k">begin</span> - <span class="n">a</span> -<span class="k">rescue</span> <span class="k">then</span> - <span class="n">b</span> -<span class="k">end</span> +begin +a +rescue then +b +end -<span class="k">case</span> <span class="n">x</span> -<span class="k">when</span> <span class="nb">p</span> <span class="k">then</span> - <span class="n">a</span> -<span class="k">end</span></code></pre> -</div> -</div> - </div> -</section> -<section class="section-1"> - <h2 id="" class="section-header"> - - なぜ普段は書かなくてもよいのか - - </h2> - <div class="section-body"> - <div class="paragraph"> -<p>普通 Ruby のコードで <code>then</code> -を書くことはない。なぜか。次のコードを実行してみるとわかる。</p> -</div> -<div id="source." class="listingblock"> -<div class="content"> -<pre class="rouge highlight"><code data-lang="ruby"><span class="k">if</span> <span class="kp">true</span> <span class="nb">puts</span> <span class="s1">'Hello, World!'</span> <span class="k">end</span></code></pre> -</div> -</div> -<div class="paragraph"> -<p>次のような構文エラーが出力される。</p> -</div> -<div class="literalblock"> -<div class="content"> -<pre>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</pre> -</div> -</div> -<div class="paragraph"> -<p>二つ目のメッセージは無視して一つ目を読むと、<code>then</code> か <code>;</code> -か改行が来るはずのところ変数だかメソッドだかが現れたことによりエラーとなっているようだ。</p> -</div> -<div class="paragraph"> -<p>ポイントは改行が <code>then</code> (や <code>;</code>) の代わりとなることである。<code>true</code> -の後に改行を入れてみる。</p> -</div> -<div id="source." class="listingblock"> -<div class="content"> -<pre class="rouge highlight"><code data-lang="ruby"><span class="k">if</span> <span class="kp">true</span> -<span class="nb">puts</span> <span class="s1">'Hello, World!'</span> <span class="k">end</span></code></pre> -</div> -</div> -<div class="paragraph"> -<p>無事 Hello, World! と出力されるようになった。</p> -</div> - </div> -</section> -<section class="section-1"> - <h2 id="" class="section-header"> - - なぜ <code>then</code> や <code>;</code> や改行が必要か - - </h2> - <div class="section-body"> - <div class="paragraph"> -<p>なぜ <code>then</code> や <code>;</code> や改行 (以下 「<code>then</code> 等」) -が必要なのだろうか。次の例を見てほしい:</p> -</div> -<div id="source." class="listingblock"> -<div class="content"> -<pre class="rouge highlight"><code data-lang="ruby"><span class="k">if</span> <span class="n">a</span> <span class="n">b</span> <span class="k">end</span></code></pre> -</div> -</div> -<div class="paragraph"> -<p><code>then</code> も <code>;</code> -も改行もないのでエラーになるが、これは条件式がどこまで続いているのかわからないためだ。 -この例は二通りに解釈できる。</p> -</div> -<div id="source." class="listingblock"> -<div class="content"> -<pre class="rouge highlight"><code data-lang="ruby"><span class="c1"># a という変数かメソッドの評価結果が truthy なら b という変数かメソッドを評価</span> -<span class="k">if</span> <span class="n">a</span> <span class="k">then</span> - <span class="n">b</span> -<span class="k">end</span></code></pre> -</div> -</div> -<div id="source." class="listingblock"> -<div class="content"> -<pre class="rouge highlight"><code data-lang="ruby"><span class="c1"># a というメソッドに b という変数かメソッドの評価結果を渡して呼び出し、</span> -<span class="c1"># その結果が truthy なら何もしない</span> -<span class="k">if</span> <span class="n">a</span><span class="p">(</span><span class="n">b</span><span class="p">)</span> <span class="k">then</span> -<span class="k">end</span></code></pre> -</div> -</div> -<div class="paragraph"> -<p><code>then</code> 等はこの曖昧性を排除するためにあり、条件式は <code>if</code> から <code>then</code> -等までの間にある、ということを明確にする。 C系の <code>if</code> 後に来る <code>(</code>/<code>)</code> -や、Python の <code>:</code>、Rust/Go/Swift などの <code>{</code> も同じ役割を持つ。</p> -</div> -<div class="paragraph"> -<p>Ruby の場合、プログラマーが書きやすいよう改行でもって <code>then</code> -が代用できるので、ほとんどの場合 <code>then</code> は必要ない。</p> -</div> - </div> -</section> -<section class="section-1"> - <h2 id="" class="section-header"> - - <code>case</code> - <code>in</code> における <code>then</code> - - </h2> - <div class="section-body"> - <div class="paragraph"> -<p>ようやく本題にたどり着いた。来る Ruby 3.0 では <code>case</code> と <code>in</code> -キーワードを使ったパターンマッチングの構文が入る予定である。この構文でもパターン部との区切りとして -<code>then</code> 等が必要になる。 (現在の) Ruby には formal -な形式での文法仕様は存在しないので、yacc の定義ファイルを参照した (yacc -の説明は省略)。</p> -</div> -<div class="paragraph"> -<p><a href="https://github.com/ruby/ruby/blob/221ca0f8281d39f0dfdfe13b2448875384bbf735/parse.y#L3961-L3986" class="bare">https://github.com/ruby/ruby/blob/221ca0f8281d39f0dfdfe13b2448875384bbf735/parse.y#L3961-L3986</a></p> -</div> -<div id="source." class="listingblock"> -<div class="content"> -<pre class="rouge highlight"><code data-lang="yacc">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)) %*/ - } - ;</code></pre> -</div> -</div> -<div class="paragraph"> -<p>簡略版:</p> -</div> -<div id="source." class="listingblock"> -<div class="content"> -<pre class="rouge highlight"><code data-lang="yacc">p_case_body : keyword_in p_top_expr then compstmt p_cases - ;</code></pre> -</div> -</div> -<div class="paragraph"> -<p>ここで、<code>keyword_in</code> は文字通り <code>in</code>、<code>p_top_expr</code> -はいわゆるパターン、<code>then</code> は <code>then</code> -キーワードのことではなく、この記事で <code>then</code> 等と呼んでいるもの、つまり -<code>then</code> キーワード、<code>;</code>、改行のいずれかである。</p> -</div> -<div class="paragraph"> -<p>これにより、<code>case</code> - <code>when</code> による従来の構文と同じように、<code>then</code> -等をパターンの後ろに挿入すればよいことがわかった。つまり次の3通りのいずれかになる:</p> -</div> -<div id="source." class="listingblock"> -<div class="content"> -<pre class="rouge highlight"><code data-lang="ruby"><span class="k">case</span> <span class="n">x</span> -<span class="k">in</span> <span class="mi">1</span> <span class="k">then</span> <span class="n">a</span> -<span class="k">in</span> <span class="mi">2</span> <span class="k">then</span> <span class="n">b</span> -<span class="k">in</span> <span class="mi">3</span> <span class="k">then</span> <span class="n">c</span> -<span class="k">end</span> +case x +when p then +a +end</code> + </pre> + </section> + + <section id="section--_なぜ普段は書かなくてもよいのか"> + <h2><a href="#section--_なぜ普段は書かなくてもよいのか">なぜ普段は書かなくてもよいのか</a></h2> + <p> + 普通 Ruby のコードで<code>then</code>を書くことはない。なぜか。次のコードを実行してみるとわかる。 + </p> + + <pre language="ruby" linenumbering="unnumbered"> + <code>if true puts 'Hello, World!' end</code> + </pre> + + <p> + 次のような構文エラーが出力される。 + </p> + + <pre class="monospaced"> + <code>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</code> + </pre> + + <p> + 二つ目のメッセージは無視して一つ目を読むと、<code>then</code>か<code>;</code>か改行が来るはずのところ変数だかメソッドだかが現れたことによりエラーとなっているようだ。 + </p> + + <p> + ポイントは改行が<code>then</code>(や<code>;</code>) の代わりとなることである。<code>true</code>の後に改行を入れてみる。 + </p> + + <pre language="ruby" linenumbering="unnumbered"> + <code>if true +puts 'Hello, World!' end</code> + </pre> + + <p> + 無事 Hello, World! と出力されるようになった。 + </p> + </section> + + <section id="section--_なぜ_then_や_や改行が必要か"> + <h2><a href="#section--_なぜ_then_や_や改行が必要か">なぜ<code>then</code>や<code>;</code>や改行が必要か</a></h2> + <p> + なぜ<code>then</code>や<code>;</code>や改行 (以下 「<code>then</code>等」) が必要なのだろうか。次の例を見てほしい: + </p> + + <pre language="ruby" linenumbering="unnumbered"> + <code>if a b end</code> + </pre> + + <p> + <code>then</code>も<code>;</code>も改行もないのでエラーになるが、これは条件式がどこまで続いているのかわからないためだ。 この例は二通りに解釈できる。 + </p> + + <pre language="ruby" linenumbering="unnumbered"> + <code># a という変数かメソッドの評価結果が truthy なら b という変数かメソッドを評価 +if a then +b +end</code> + </pre> + + <pre language="ruby" linenumbering="unnumbered"> + <code># a というメソッドに b という変数かメソッドの評価結果を渡して呼び出し、 +# その結果が truthy なら何もしない +if a(b) then +end</code> + </pre> + + <p> + <code>then</code>等はこの曖昧性を排除するためにあり、条件式は<code>if</code>から<code>then</code>等までの間にある、ということを明確にする。 C系の<code>if</code>後に来る<code>(</code>/<code>)</code>や、Python の<code>:</code>、Rust/Go/Swift などの<code>{</code>も同じ役割を持つ。 + </p> + + <p> + Ruby の場合、プログラマーが書きやすいよう改行でもって<code>then</code>が代用できるので、ほとんどの場合<code>then</code>は必要ない。 + </p> + </section> + + <section id="section--_case_in_における_then"> + <h2><a href="#section--_case_in_における_then"><code>case</code>-<code>in</code>における<code>then</code></a></h2> + <p> + ようやく本題にたどり着いた。来る Ruby 3.0 では<code>case</code>と<code>in</code>キーワードを使ったパターンマッチングの構文が入る予定である。この構文でもパターン部との区切りとして<code>then</code>等が必要になる。 (現在の) Ruby には formal な形式での文法仕様は存在しないので、yacc の定義ファイルを参照した (yacc の説明は省略)。 + </p> + + <p> + <a xl:href="https://github.com/ruby/ruby/blob/221ca0f8281d39f0dfdfe13b2448875384bbf735/parse.y#L3961-L3986">https://github.com/ruby/ruby/blob/221ca0f8281d39f0dfdfe13b2448875384bbf735/parse.y#L3961-L3986</a> + </p> + + <pre language="yacc" linenumbering="unnumbered"> + <code>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)) %*/ + } + ;</code> + </pre> + + <p> + 簡略版: + </p> + + <pre language="yacc" linenumbering="unnumbered"> + <code>p_case_body : keyword_in p_top_expr then compstmt p_cases +;</code> + </pre> + + <p> + ここで、<code>keyword_in</code>は文字通り<code>in</code>、<code>p_top_expr</code>はいわゆるパターン、<code>then</code>は<code>then</code>キーワードのことではなく、この記事で<code>then</code>等と呼んでいるもの、つまり<code>then</code>キーワード、<code>;</code>、改行のいずれかである。 + </p> + + <p> + これにより、<code>case</code>-<code>when</code>による従来の構文と同じように、<code>then</code>等をパターンの後ろに挿入すればよいことがわかった。つまり次の3通りのいずれかになる: + </p> + + <pre language="ruby" linenumbering="unnumbered"> + <code>case x +in 1 then a +in 2 then b +in 3 then c +end -<span class="k">case</span> <span class="n">x</span> -<span class="k">in</span> <span class="mi">1</span> - <span class="n">a</span> -<span class="k">in</span> <span class="mi">2</span> - <span class="n">b</span> -<span class="k">in</span> <span class="mi">3</span> - <span class="n">c</span> -<span class="k">end</span> +case x +in 1 +a +in 2 +b +in 3 +c +end -<span class="k">case</span> <span class="n">x</span> -<span class="k">in</span> <span class="mi">1</span><span class="p">;</span> <span class="n">a</span> -<span class="k">in</span> <span class="mi">2</span><span class="p">;</span> <span class="n">b</span> -<span class="k">in</span> <span class="mi">3</span><span class="p">;</span> <span class="n">c</span> -<span class="k">end</span></code></pre> -</div> -</div> -<div class="paragraph"> -<p>ところで、<code>p_top_expr</code> には <code>if</code> による guard clause -が書けるので、その場合は <code>if</code> - <code>then</code> と似たような見た目になる。</p> -</div> -<div id="source." class="listingblock"> -<div class="content"> -<pre class="rouge highlight"><code data-lang="ruby"><span class="k">case</span> <span class="n">x</span> -<span class="k">in</span> <span class="mi">0</span> <span class="k">then</span> <span class="n">a</span> -<span class="k">in</span> <span class="n">n</span> <span class="k">if</span> <span class="n">n</span> <span class="o"><</span> <span class="mi">0</span> <span class="k">then</span> <span class="n">b</span> -<span class="k">in</span> <span class="n">n</span> <span class="k">then</span> <span class="n">c</span> -<span class="k">end</span></code></pre> -</div> -</div> - </div> -</section> -<section class="section-1"> - <h2 id="" class="section-header"> - - まとめ - - </h2> - <div class="section-body"> - <div class="ulist"> -<ul> -<li> -<p><code>if</code> や <code>case</code> の条件の後ろには <code>then</code>、<code>;</code>、改行のいずれかが必要</p> -<div class="ulist"> -<ul> -<li> -<p>通常は改行しておけばよい</p> -</li> -</ul> -</div> -</li> -<li> -<p>3.0 で入る予定の <code>case</code> - <code>in</code> でも <code>then</code> 等が必要になる</p> -</li> -<li> -<p>Ruby の構文を正確に知るには (現状) <code>parse.y</code> を直接読めばよい</p> -</li> -</ul> -</div> - </div> -</section> +case x +in 1; a +in 2; b +in 3; c +end</code> + </pre> + + <p> + ところで、<code>p_top_expr</code>には<code>if</code>による guard clause が書けるので、その場合は<code>if</code>-<code>then</code>と似たような見た目になる。 + </p> + + <pre language="ruby" linenumbering="unnumbered"> + <code>case x +in 0 then a +in n if n < 0 then b +in n then c +end</code> + </pre> + </section> + + <section id="section--_まとめ"> + <h2><a href="#section--_まとめ">まとめ</a></h2> + <ul> + <li> + <p> + <code>if</code>や<code>case</code>の条件の後ろには<code>then</code>、<code>;</code>、改行のいずれかが必要 + </p> + + <ul> + <li> + <p> + 通常は改行しておけばよい + </p> + </li> + </ul> + </li> + + <li> + <p> + 3.0 で入る予定の<code>case</code>-<code>in</code>でも<code>then</code>等が必要になる + </p> + </li> + + <li> + <p> + Ruby の構文を正確に知るには (現状)<code>parse.y</code>を直接読めばよい + </p> + </li> + </ul> + </section> </div> - </article> </main> <footer class="footer"> diff --git a/public/posts/2021-10-02/rust-where-are-primitive-types-from/index.html b/public/posts/2021-10-02/rust-where-are-primitive-types-from/index.html index bbcda80..76443a8 100644 --- a/public/posts/2021-10-02/rust-where-are-primitive-types-from/index.html +++ b/public/posts/2021-10-02/rust-where-are-primitive-types-from/index.html @@ -4,18 +4,14 @@ <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="author" content="nsfisis"> - <meta name="copyright" content="© nsfisis"> - <meta name="description" content="Rust のプリミティブ型は予約語ではなく普通の識別子である。 どのようにこれが名前解決されるのかを調べた。"> + <meta name="copyright" content="© 2021 nsfisis"> + <meta name="description" content="Rust のプリミティブ型は予約語ではなく普通の識別子である。どのようにこれが名前解決されるのかを調べた。"> <meta name="keywords" content="Rust"> <link rel="icon" type="image/svg+xml" href="/favicon.svg"> <title>Rust のプリミティブ型はどこからやって来るか | REPL: Rest-Eat-Program Loop</title> - - <link rel="stylesheet" href="/hl.css?208c52e3b7c9db1cad782c5d30b4698f"> - - <link rel="stylesheet" href="/style.css?779b1a3debcaeba619f6fe500e93d525"> - - <link rel="stylesheet" href="/custom.css?a649ea3528d4b626fb636505d94c1144"> - + <link rel="stylesheet" href="/hl.css?h=208c52e3b7c9db1cad782c5d30b4698f"> + <link rel="stylesheet" href="/style.css?h=779b1a3debcaeba619f6fe500e93d525"> + <link rel="stylesheet" href="/custom.css?h=a649ea3528d4b626fb636505d94c1144"> </head> <body class="single"> <header class="header"> @@ -29,275 +25,229 @@ <article class="post-single"> <header class="post-header"> <h1 class="post-title">Rust のプリミティブ型はどこからやって来るか</h1> - - <ul class="post-tags"> - - <li class="tag"> - <a href="/tags/rust/">Rust</a> - </li> - - </ul> - + <ul class="post-tags"> + <li class="tag"> + <a href="/tags/rust">Rust</a> + </li> + </ul> </header> <div class="post-content"> <section> <h2 id="changelog">更新履歴</h2> <ol> - - <li class="revision"> - <time datetime="2021-10-02">2021-10-02</time>: Qiita から移植 - </li> - + <li class="revision"> + <time datetime="2021-10-02">2021-10-02</time>: Qiita から移植 + </li> </ol> </section> - <div id="preamble"> -<div class="sectionbody"> -<div class="paragraph"> -<p>この記事は Qiita から移植してきたものです。 元 URL: -<a href="https://qiita.com/nsfisis/items/9a429432258bbcd6c565" class="bare">https://qiita.com/nsfisis/items/9a429432258bbcd6c565</a></p> -</div> -<hr> -</div> -</div> -<section class="section-1"> - <h2 id="" class="section-header"> - - 前置き - - </h2> - <div class="section-body"> - <div class="paragraph"> -<p>Rust -において、プリミティブ型の名前は予約語でない。したがって、次のコードは合法である。</p> -</div> -<div id="source." class="listingblock"> -<div class="content"> -<pre class="rouge highlight"><code data-lang="rust"><span class="nd">#![allow(non_camel_case_types)]</span> -<span class="nd">#![allow(dead_code)]</span> + <p> + この記事は Qiita から移植してきたものです。 元 URL:<a xl:href="https://qiita.com/nsfisis/items/9a429432258bbcd6c565">https://qiita.com/nsfisis/items/9a429432258bbcd6c565</a> + </p> + + <p> + <hr> + </hr> + </p> + + <section id="section--_前置き"> + <h2><a href="#section--_前置き">前置き</a></h2> + <p> + Rust において、プリミティブ型の名前は予約語でない。したがって、次のコードは合法である。 + </p> + + <pre language="rust" linenumbering="unnumbered"> + <code>#![allow(non_camel_case_types)] +#![allow(dead_code)] -<span class="k">struct</span> <span class="nb">bool</span><span class="p">;</span> -<span class="k">struct</span> <span class="nb">char</span><span class="p">;</span> -<span class="k">struct</span> <span class="nb">i8</span><span class="p">;</span> -<span class="k">struct</span> <span class="nb">i16</span><span class="p">;</span> -<span class="k">struct</span> <span class="nb">i32</span><span class="p">;</span> -<span class="k">struct</span> <span class="nb">i64</span><span class="p">;</span> -<span class="k">struct</span> <span class="nb">i128</span><span class="p">;</span> -<span class="k">struct</span> <span class="nb">isize</span><span class="p">;</span> -<span class="k">struct</span> <span class="nb">u8</span><span class="p">;</span> -<span class="k">struct</span> <span class="nb">u16</span><span class="p">;</span> -<span class="k">struct</span> <span class="nb">u32</span><span class="p">;</span> -<span class="k">struct</span> <span class="nb">u64</span><span class="p">;</span> -<span class="k">struct</span> <span class="nb">u128</span><span class="p">;</span> -<span class="k">struct</span> <span class="nb">usize</span><span class="p">;</span> -<span class="k">struct</span> <span class="nb">f32</span><span class="p">;</span> -<span class="k">struct</span> <span class="nb">f64</span><span class="p">;</span> -<span class="k">struct</span> <span class="nb">str</span><span class="p">;</span></code></pre> -</div> -</div> -<div class="paragraph"> -<p>では、普段単に <code>bool</code> と書いたとき、この <code>bool</code> -は一体どこから来ているのか。rustc のソースを追ってみた。</p> -</div> -<div class="quoteblock"> -<blockquote> -<div class="paragraph"> -<p>前提知識: 一般的なコンパイラの構造、用語。<code>rustc</code> そのものの知識は不要 -(というよりも筆者自身がよく知らない)</p> -</div> -</blockquote> -</div> - </div> -</section> -<section class="section-1"> - <h2 id="" class="section-header"> - - 調査 - - </h2> - <div class="section-body"> - <div class="paragraph"> -<p>調査に使用したソース (調査時点での最新 master)</p> -</div> -<div class="paragraph"> -<p><a href="https://github.com/rust-lang/rust/tree/511ed9f2356af365ad8affe046b3dd33f7ac3c98" class="bare">https://github.com/rust-lang/rust/tree/511ed9f2356af365ad8affe046b3dd33f7ac3c98</a></p> -</div> -<div class="paragraph"> -<p>どのようにして調べるか。rustc -の構造には詳しくないため、すぐに当たりをつけるのは難しい。</p> -</div> -<div class="paragraph"> -<p>大雑把な構造としては、<code>compiler</code> フォルダ以下に <code>rustc_*</code> -という名前のクレートが数十個入っている。これがどうやら <code>rustc</code> -コマンドの実装部のようだ。</p> -</div> -<div class="paragraph"> -<p><code>rustc</code> はセルフホストされている (= <code>rustc</code> 自身が Rust で書かれている) -ので、<code>bool</code> や <code>char</code> -などで適当に検索をかけてもノイズが多すぎて話にならない。 -しかし、お誂え向きなことに <code>i128</code>/<code>u128</code> -というコンパイラ自身が使うことがなさそうな型が存在するのでこれを使って -<code>git grep</code> してみる。</p> -</div> -<div class="literalblock"> -<div class="content"> -<pre>$ git grep "\bi128\b" | wc # i128 - 165 1069 15790 +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;</code> + </pre> + + <p> + では、普段単に<code>bool</code>と書いたとき、この<code>bool</code>は一体どこから来ているのか。rustc のソースを追ってみた。 + </p> + + <blockquote> + <p> + 前提知識: 一般的なコンパイラの構造、用語。<code>rustc</code>そのものの知識は不要 (というよりも筆者自身がよく知らない) + </p> + </blockquote> + </section> + + <section id="section--_調査"> + <h2><a href="#section--_調査">調査</a></h2> + <p> + 調査に使用したソース (調査時点での最新 master) + </p> + + <p> + <a xl:href="https://github.com/rust-lang/rust/tree/511ed9f2356af365ad8affe046b3dd33f7ac3c98">https://github.com/rust-lang/rust/tree/511ed9f2356af365ad8affe046b3dd33f7ac3c98</a> + </p> + + <p> + どのようにして調べるか。rustc の構造には詳しくないため、すぐに当たりをつけるのは難しい。 + </p> + + <p> + 大雑把な構造としては、<code>compiler</code>フォルダ以下に<code>rustc_*</code>という名前のクレートが数十個入っている。これがどうやら<code>rustc</code>コマンドの実装部のようだ。 + </p> + + <p> + <code>rustc</code>はセルフホストされている (=<code>rustc</code>自身が Rust で書かれている) ので、<code>bool</code>や<code>char</code>などで適当に検索をかけてもノイズが多すぎて話にならない。 しかし、お誂え向きなことに<code>i128</code>/<code>u128</code>というコンパイラ自身が使うことがなさそうな型が存在するのでこれを使って<code>git grep</code>してみる。 + </p> + + <pre class="monospaced"> + <code>$ git grep "\bi128\b" | wc # i128 +165 1069 15790 -$ git grep "\bu128\b" | wc # u128 - 293 2127 26667 +$ git grep "\bu128\b" | wc # u128 +293 2127 26667 -$ git grep "\bbool\b" | wc # cf. bool の結果 - 3563 23577 294659</pre> -</div> -</div> -<div class="paragraph"> -<p>165 -程度であれば探すことができそうだ。今回は、クレート名を見ておおよその当たりをつけた。</p> -</div> -<div class="literalblock"> -<div class="content"> -<pre>$ git grep "\bi128\b" +$ git grep "\bbool\b" | wc # cf. bool の結果 +3563 23577 294659</code> + </pre> + + <p> + 165 程度であれば探すことができそうだ。今回は、クレート名を見ておおよその当たりをつけた。 + </p> + + <pre class="monospaced"> + <code>$ git grep "\bi128\b" ... rustc_resolve/src/lib.rs: table.insert(sym::i128, Int(IntTy::I128)); -...</pre> -</div> -</div> -<div class="paragraph"> -<p><code>rustc_resolve</code> -というのはいかにも名前解決を担いそうなクレート名である。該当箇所を見てみる。</p> -</div> -<div id="source." class="listingblock"> -<div class="content"> -<pre class="rouge highlight"><code data-lang="rust"><span class="cd">/// Interns the names of the primitive types.</span> -<span class="cd">///</span> -<span class="cd">/// All other types are defined somewhere and possibly imported, but the primitive ones need</span> -<span class="cd">/// special handling, since they have no place of origin.</span> -<span class="k">struct</span> <span class="n">PrimitiveTypeTable</span> <span class="p">{</span> - <span class="n">primitive_types</span><span class="p">:</span> <span class="n">FxHashMap</span><span class="o"><</span><span class="n">Symbol</span><span class="p">,</span> <span class="n">PrimTy</span><span class="o">></span><span class="p">,</span> -<span class="p">}</span> +...</code> + </pre> + + <p> + <code>rustc_resolve</code>というのはいかにも名前解決を担いそうなクレート名である。該当箇所を見てみる。 + </p> + + <pre language="rust" linenumbering="unnumbered"> + <code>/// 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>, +} -<span class="k">impl</span> <span class="n">PrimitiveTypeTable</span> <span class="p">{</span> - <span class="k">fn</span> <span class="nf">new</span><span class="p">()</span> <span class="k">-></span> <span class="n">PrimitiveTypeTable</span> <span class="p">{</span> - <span class="k">let</span> <span class="k">mut</span> <span class="n">table</span> <span class="o">=</span> <span class="nn">FxHashMap</span><span class="p">::</span><span class="nf">default</span><span class="p">();</span> +impl PrimitiveTypeTable { +fn new() -> PrimitiveTypeTable { +let mut table = FxHashMap::default(); - <span class="n">table</span><span class="nf">.insert</span><span class="p">(</span><span class="nn">sym</span><span class="p">::</span><span class="nb">bool</span><span class="p">,</span> <span class="n">Bool</span><span class="p">);</span> - <span class="n">table</span><span class="nf">.insert</span><span class="p">(</span><span class="nn">sym</span><span class="p">::</span><span class="nb">char</span><span class="p">,</span> <span class="n">Char</span><span class="p">);</span> - <span class="n">table</span><span class="nf">.insert</span><span class="p">(</span><span class="nn">sym</span><span class="p">::</span><span class="nb">f32</span><span class="p">,</span> <span class="nf">Float</span><span class="p">(</span><span class="nn">FloatTy</span><span class="p">::</span><span class="n">F32</span><span class="p">));</span> - <span class="n">table</span><span class="nf">.insert</span><span class="p">(</span><span class="nn">sym</span><span class="p">::</span><span class="nb">f64</span><span class="p">,</span> <span class="nf">Float</span><span class="p">(</span><span class="nn">FloatTy</span><span class="p">::</span><span class="n">F64</span><span class="p">));</span> - <span class="n">table</span><span class="nf">.insert</span><span class="p">(</span><span class="nn">sym</span><span class="p">::</span><span class="nb">isize</span><span class="p">,</span> <span class="nf">Int</span><span class="p">(</span><span class="nn">IntTy</span><span class="p">::</span><span class="n">Isize</span><span class="p">));</span> - <span class="n">table</span><span class="nf">.insert</span><span class="p">(</span><span class="nn">sym</span><span class="p">::</span><span class="nb">i8</span><span class="p">,</span> <span class="nf">Int</span><span class="p">(</span><span class="nn">IntTy</span><span class="p">::</span><span class="n">I8</span><span class="p">));</span> - <span class="n">table</span><span class="nf">.insert</span><span class="p">(</span><span class="nn">sym</span><span class="p">::</span><span class="nb">i16</span><span class="p">,</span> <span class="nf">Int</span><span class="p">(</span><span class="nn">IntTy</span><span class="p">::</span><span class="n">I16</span><span class="p">));</span> - <span class="n">table</span><span class="nf">.insert</span><span class="p">(</span><span class="nn">sym</span><span class="p">::</span><span class="nb">i32</span><span class="p">,</span> <span class="nf">Int</span><span class="p">(</span><span class="nn">IntTy</span><span class="p">::</span><span class="n">I32</span><span class="p">));</span> - <span class="n">table</span><span class="nf">.insert</span><span class="p">(</span><span class="nn">sym</span><span class="p">::</span><span class="nb">i64</span><span class="p">,</span> <span class="nf">Int</span><span class="p">(</span><span class="nn">IntTy</span><span class="p">::</span><span class="n">I64</span><span class="p">));</span> - <span class="n">table</span><span class="nf">.insert</span><span class="p">(</span><span class="nn">sym</span><span class="p">::</span><span class="nb">i128</span><span class="p">,</span> <span class="nf">Int</span><span class="p">(</span><span class="nn">IntTy</span><span class="p">::</span><span class="n">I128</span><span class="p">));</span> - <span class="n">table</span><span class="nf">.insert</span><span class="p">(</span><span class="nn">sym</span><span class="p">::</span><span class="nb">str</span><span class="p">,</span> <span class="n">Str</span><span class="p">);</span> - <span class="n">table</span><span class="nf">.insert</span><span class="p">(</span><span class="nn">sym</span><span class="p">::</span><span class="nb">usize</span><span class="p">,</span> <span class="nf">Uint</span><span class="p">(</span><span class="nn">UintTy</span><span class="p">::</span><span class="n">Usize</span><span class="p">));</span> - <span class="n">table</span><span class="nf">.insert</span><span class="p">(</span><span class="nn">sym</span><span class="p">::</span><span class="nb">u8</span><span class="p">,</span> <span class="nf">Uint</span><span class="p">(</span><span class="nn">UintTy</span><span class="p">::</span><span class="n">U8</span><span class="p">));</span> - <span class="n">table</span><span class="nf">.insert</span><span class="p">(</span><span class="nn">sym</span><span class="p">::</span><span class="nb">u16</span><span class="p">,</span> <span class="nf">Uint</span><span class="p">(</span><span class="nn">UintTy</span><span class="p">::</span><span class="n">U16</span><span class="p">));</span> - <span class="n">table</span><span class="nf">.insert</span><span class="p">(</span><span class="nn">sym</span><span class="p">::</span><span class="nb">u32</span><span class="p">,</span> <span class="nf">Uint</span><span class="p">(</span><span class="nn">UintTy</span><span class="p">::</span><span class="n">U32</span><span class="p">));</span> - <span class="n">table</span><span class="nf">.insert</span><span class="p">(</span><span class="nn">sym</span><span class="p">::</span><span class="nb">u64</span><span class="p">,</span> <span class="nf">Uint</span><span class="p">(</span><span class="nn">UintTy</span><span class="p">::</span><span class="n">U64</span><span class="p">));</span> - <span class="n">table</span><span class="nf">.insert</span><span class="p">(</span><span class="nn">sym</span><span class="p">::</span><span class="nb">u128</span><span class="p">,</span> <span class="nf">Uint</span><span class="p">(</span><span class="nn">UintTy</span><span class="p">::</span><span class="n">U128</span><span class="p">));</span> - <span class="k">Self</span> <span class="p">{</span> <span class="n">primitive_types</span><span class="p">:</span> <span class="n">table</span> <span class="p">}</span> - <span class="p">}</span> -<span class="p">}</span></code></pre> -</div> -</div> -<div class="paragraph"> -<p>これは初めに列挙したプリミティブ型の一覧と一致している。doc comment -にも、</p> -</div> -<div class="quoteblock"> -<blockquote> -<div class="paragraph"> -<p>All other types are defined somewhere and possibly imported, but the -primitive ones need special handling, since they have no place of -origin.</p> -</div> -</blockquote> -</div> -<div class="paragraph"> -<p>とある。次はこの struct -の使用箇所を追う。追うと言っても使われている箇所は次の一箇所しかない。なお説明に不要な箇所は大きく削っている。</p> -</div> -<div id="source." class="listingblock"> -<div class="content"> -<pre class="rouge highlight"><code data-lang="rust"> <span class="cd">/// This resolves the identifier `ident` in the namespace `ns` in the current lexical scope.</span> - <span class="cd">/// (略)</span> - <span class="k">fn</span> <span class="nf">resolve_ident_in_lexical_scope</span><span class="p">(</span> - <span class="o">&</span><span class="k">mut</span> <span class="k">self</span><span class="p">,</span> - <span class="k">mut</span> <span class="n">ident</span><span class="p">:</span> <span class="n">Ident</span><span class="p">,</span> - <span class="n">ns</span><span class="p">:</span> <span class="n">Namespace</span><span class="p">,</span> - <span class="c1">// (略)</span> - <span class="p">)</span> <span class="k">-></span> <span class="nb">Option</span><span class="o"><</span><span class="n">LexicalScopeBinding</span><span class="o"><</span><span class="nv">'a</span><span class="o">>></span> <span class="p">{</span> - <span class="c1">// (略)</span> +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 } +} +}</code> + </pre> + + <p> + これは初めに列挙したプリミティブ型の一覧と一致している。doc comment にも、 + </p> + + <blockquote> + <p> + All other types are defined somewhere and possibly imported, but the primitive ones need special handling, since they have no place of origin. + </p> + </blockquote> + + <p> + とある。次はこの struct の使用箇所を追う。追うと言っても使われている箇所は次の一箇所しかない。なお説明に不要な箇所は大きく削っている。 + </p> + + <pre language="rust" linenumbering="unnumbered"> + <code> /// 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>> { +// (略) - <span class="k">if</span> <span class="n">ns</span> <span class="o">==</span> <span class="n">TypeNS</span> <span class="p">{</span> - <span class="k">if</span> <span class="k">let</span> <span class="nf">Some</span><span class="p">(</span><span class="n">prim_ty</span><span class="p">)</span> <span class="o">=</span> <span class="k">self</span><span class="py">.primitive_type_table.primitive_types</span><span class="nf">.get</span><span class="p">(</span><span class="o">&</span><span class="n">ident</span><span class="py">.name</span><span class="p">)</span> <span class="p">{</span> - <span class="k">let</span> <span class="n">binding</span> <span class="o">=</span> - <span class="p">(</span><span class="nn">Res</span><span class="p">::</span><span class="nf">PrimTy</span><span class="p">(</span><span class="o">*</span><span class="n">prim_ty</span><span class="p">),</span> <span class="nn">ty</span><span class="p">::</span><span class="nn">Visibility</span><span class="p">::</span><span class="n">Public</span><span class="p">,</span> <span class="n">DUMMY_SP</span><span class="p">,</span> <span class="nn">ExpnId</span><span class="p">::</span><span class="nf">root</span><span class="p">())</span> - <span class="nf">.to_name_binding</span><span class="p">(</span><span class="k">self</span><span class="py">.arenas</span><span class="p">);</span> - <span class="k">return</span> <span class="nf">Some</span><span class="p">(</span><span class="nn">LexicalScopeBinding</span><span class="p">::</span><span class="nf">Item</span><span class="p">(</span><span class="n">binding</span><span class="p">));</span> - <span class="p">}</span> - <span class="p">}</span> +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)); +} +} - <span class="nb">None</span> - <span class="p">}</span></code></pre> -</div> -</div> -<div class="paragraph"> -<p>関数名や doc comment が示している通り、この関数は識別子 (identifier, -ident) を現在のレキシカルスコープ内で解決 (resolve) する。 -<code>if ns == TypeNS</code> のブロック内では、<code>primitive_type_table</code> (上記の -<code>PrimitiveTypeTable::new()</code> で作られた変数) に含まれている識別子 -(<code>bool</code>、<code>i32</code> など) -かどうか判定し、そうであればそれに紐づけられたプリミティブ型を返している。</p> -</div> -<div class="paragraph"> -<p>なお、<code>ns</code> は「名前空間」を示す変数である。Rust -における名前空間はC言語におけるそれとほとんど同じで、今探している名前が関数名/変数名なのか型なのかマクロなのかを区別している。この -<code>if</code> -は、プリミティブ型に解決されるのは型を探しているときだけだ、と言っている。</p> -</div> -<div class="paragraph"> -<p>重要なのは、これが <code>resolve_ident_in_lexical_scope()</code> -の最後に書かれている点である。つまり、最初に挙げたプリミティブ型の識別子は、「名前解決の最終段階で」、「他に同名の型が見つかっていなければ」プリミティブ型として解決される。</p> -</div> -<div class="paragraph"> -<p>動作がわかったところで、例として次のコードを考える。</p> -</div> -<div id="source." class="listingblock"> -<div class="content"> -<pre class="rouge highlight"><code data-lang="rust"><span class="nd">#![allow(non_camel_case_types)]</span> +None +}</code> + </pre> + + <p> + 関数名や doc comment が示している通り、この関数は識別子 (identifier, ident) を現在のレキシカルスコープ内で解決 (resolve) する。<code>if ns == TypeNS</code>のブロック内では、<code>primitive_type_table</code>(上記の<code>PrimitiveTypeTable::new()</code>で作られた変数) に含まれている識別子 (<code>bool</code>、<code>i32</code>など) かどうか判定し、そうであればそれに紐づけられたプリミティブ型を返している。 + </p> + + <p> + なお、<code>ns</code>は「名前空間」を示す変数である。Rust における名前空間はC言語におけるそれとほとんど同じで、今探している名前が関数名/変数名なのか型なのかマクロなのかを区別している。この<code>if</code>は、プリミティブ型に解決されるのは型を探しているときだけだ、と言っている。 + </p> + + <p> + 重要なのは、これが<code>resolve_ident_in_lexical_scope()</code>の最後に書かれている点である。つまり、最初に挙げたプリミティブ型の識別子は、「名前解決の最終段階で」、「他に同名の型が見つかっていなければ」プリミティブ型として解決される。 + </p> + + <p> + 動作がわかったところで、例として次のコードを考える。 + </p> + + <pre language="rust" linenumbering="unnumbered"> + <code>#![allow(non_camel_case_types)] -<span class="k">struct</span> <span class="nb">bool</span><span class="p">;</span> +struct bool; -<span class="k">fn</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span> - <span class="k">let</span> <span class="n">_</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="nb">bool</span><span class="p">;</span> -<span class="p">}</span></code></pre> -</div> -</div> -<div class="paragraph"> -<p>ここで <code>main()</code> の <code>bool</code> は <code>struct bool</code> -として解決される。なぜなら、プリミティブ型の判定をする前に <code>bool</code> -という名前の別の型が見つかるからだ。</p> -</div> - </div> -</section> -<section class="section-1"> - <h2 id="" class="section-header"> - - まとめ - - </h2> - <div class="section-body"> - <div class="paragraph"> -<p>Rust -のプリミティブ型は予約語ではない。名前解決の最終段階で特別扱いされ、他に同名の型が見つかっていなければ対応するプリミティブ型に解決される。</p> -</div> - </div> -</section> +fn main() { +let _: bool = bool; +}</code> + </pre> + + <p> + ここで<code>main()</code>の<code>bool</code>は<code>struct bool</code>として解決される。なぜなら、プリミティブ型の判定をする前に<code>bool</code>という名前の別の型が見つかるからだ。 + </p> + </section> + + <section id="section--_まとめ"> + <h2><a href="#section--_まとめ">まとめ</a></h2> + <p> + Rust のプリミティブ型は予約語ではない。名前解決の最終段階で特別扱いされ、他に同名の型が見つかっていなければ対応するプリミティブ型に解決される。 + </p> + </section> </div> - </article> </main> <footer class="footer"> diff --git a/public/posts/2021-10-02/vim-difference-between-autocmd-bufwrite-and-bufwritepre/index.html b/public/posts/2021-10-02/vim-difference-between-autocmd-bufwrite-and-bufwritepre/index.html index 9b040ee..8144f03 100644 --- a/public/posts/2021-10-02/vim-difference-between-autocmd-bufwrite-and-bufwritepre/index.html +++ b/public/posts/2021-10-02/vim-difference-between-autocmd-bufwrite-and-bufwritepre/index.html @@ -4,18 +4,14 @@ <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="author" content="nsfisis"> - <meta name="copyright" content="© nsfisis"> - <meta name="description" content="Vim の autocmd events における BufWrite/BufWritePre がどう違うのかを調べた結果、 違いはないことがわかった。"> + <meta name="copyright" content="© 2021 nsfisis"> + <meta name="description" content="Vim の autocmd events における BufWrite/BufWritePre がどう違うのかを調べた結果、違いはないことがわかった。"> <meta name="keywords" content="Vim"> <link rel="icon" type="image/svg+xml" href="/favicon.svg"> - <title>【Vim】autocmd events の BufWrite/BufWritePre の違い | REPL: Rest-Eat-Program Loop</title> - - <link rel="stylesheet" href="/hl.css?208c52e3b7c9db1cad782c5d30b4698f"> - - <link rel="stylesheet" href="/style.css?779b1a3debcaeba619f6fe500e93d525"> - - <link rel="stylesheet" href="/custom.css?a649ea3528d4b626fb636505d94c1144"> - + <title>【Vim】 autocmd events の BufWrite/BufWritePre の違い | REPL: Rest-Eat-Program Loop</title> + <link rel="stylesheet" href="/hl.css?h=208c52e3b7c9db1cad782c5d30b4698f"> + <link rel="stylesheet" href="/style.css?h=779b1a3debcaeba619f6fe500e93d525"> + <link rel="stylesheet" href="/custom.css?h=a649ea3528d4b626fb636505d94c1144"> </head> <body class="single"> <header class="header"> @@ -28,248 +24,220 @@ <main class="main"> <article class="post-single"> <header class="post-header"> - <h1 class="post-title">【Vim】autocmd events の BufWrite/BufWritePre の違い</h1> - - <ul class="post-tags"> - - <li class="tag"> - <a href="/tags/vim/">Vim</a> - </li> - - </ul> - + <h1 class="post-title">【Vim】 autocmd events の BufWrite/BufWritePre の違い</h1> + <ul class="post-tags"> + <li class="tag"> + <a href="/tags/vim">Vim</a> + </li> + </ul> </header> <div class="post-content"> <section> <h2 id="changelog">更新履歴</h2> <ol> + <li class="revision"> + <time datetime="2021-10-02">2021-10-02</time>: Qiita から移植 + </li> + </ol> + </section> + <p> + この記事は Qiita から移植してきたものです。 元 URL:<a xl:href="https://qiita.com/nsfisis/items/79ab4db8564032de0b25">https://qiita.com/nsfisis/items/79ab4db8564032de0b25</a> + </p> + + <p> + <hr> + </hr> + </p> + + <section id="section--_tl_dr"> + <h2><a href="#section--_tl_dr">TL; DR</a></h2> + <p> + 違いはない。ただのエイリアス。 + </p> + </section> + + <section id="section--_調査記録"> + <h2><a href="#section--_調査記録">調査記録</a></h2> + <p> + Vim の autocmd events には似通った名前のものがいくつかある。大抵は<code>:help</code>に説明があるが、この記事のタイトルにある2つを含めた以下のイベントには、その違いについて説明がない。 + </p> + + <ul> + <li> + <p> + <code>BufRead</code>/<code>BufReadPost</code> + </p> + </li> + + <li> + <p> + <code>BufWrite</code>/<code>BufWritePre</code> + </p> + </li> + + <li> + <p> + <code>BufAdd</code>/<code>BufCreate</code> + </p> + </li> + </ul> + + <p> + このうち、<code>BufAdd</code>/<code>BufCreate</code>に関しては、<code>:help BufCreate</code>に + </p> + + <blockquote> + <p> + The BufCreate event is for historic reasons. + </p> + </blockquote> + + <p> + とあり、おそらくは<code>BufAdd</code>のエイリアスであろうということがわかる。他の2組も同様ではないかと予想されるが、確認のため vim と neovim のソースコードを調査した。 + </p> + + <blockquote> + <p> + ソースコードへのリンク<a xl:href="https://github.com/vim/vim/tree/8e6be34338f13a6a625f19bcef82019c9adc65f2">vim (調査時点での master branch)</a><a xl:href="https://github.com/neovim/neovim/tree/71d4f5851f068eeb432af34850dddda8cc1c71e3">neovim (上に同じ)</a> + </p> + </blockquote> + + <section id="section--_vim_のソースコード"> + <h3><a href="#section--_vim_のソースコード">vim のソースコード</a></h3> + <p> + 以下は、autocmd events の名前と内部で使われている整数値とのマッピングを定義している箇所である。見ての通り、上でエイリアスではないかと述べた3組には、それぞれ同じ内部値が使われている。 + </p> + + <p> + <a xl:href="https://github.com/vim/vim/blob/8e6be34338f13a6a625f19bcef82019c9adc65f2/src/autocmd.c#L85-L86">https://github.com/vim/vim/blob/8e6be34338f13a6a625f19bcef82019c9adc65f2/src/autocmd.c#L85-L86</a> + </p> - <li class="revision"> - <time datetime="2021-10-02">2021-10-02</time>: Qiita から移植 + <pre language="c" linenumbering="unnumbered"> + <code> {"BufAdd", EVENT_BUFADD}, +{"BufCreate", EVENT_BUFADD},</code> + </pre> + + <p> + <a xl:href="https://github.com/vim/vim/blob/8e6be34338f13a6a625f19bcef82019c9adc65f2/src/autocmd.c#L95-L97">https://github.com/vim/vim/blob/8e6be34338f13a6a625f19bcef82019c9adc65f2/src/autocmd.c#L95-L97</a> + </p> + + <pre language="c" linenumbering="unnumbered"> + <code> {"BufRead", EVENT_BUFREADPOST}, +{"BufReadCmd", EVENT_BUFREADCMD}, +{"BufReadPost", EVENT_BUFREADPOST},</code> + </pre> + + <p> + <a xl:href="https://github.com/vim/vim/blob/8e6be34338f13a6a625f19bcef82019c9adc65f2/src/autocmd.c#L103-L105">https://github.com/vim/vim/blob/8e6be34338f13a6a625f19bcef82019c9adc65f2/src/autocmd.c#L103-L105</a> + </p> + + <pre language="c" linenumbering="unnumbered"> + <code> {"BufWrite", EVENT_BUFWRITEPRE}, +{"BufWritePost", EVENT_BUFWRITEPOST}, +{"BufWritePre", EVENT_BUFWRITEPRE},</code> + </pre> + </section> + + <section id="section--_neovim_のソースコード"> + <h3><a href="#section--_neovim_のソースコード">neovim のソースコード</a></h3> + <p> + neovim の場合でも同様のマッピングが定義されているが、こちらの場合は Lua で書かれている。以下にある通り、はっきり<code>aliases</code>と書かれている。 + </p> + + <p> + <a xl:href="https://github.com/neovim/neovim/blob/71d4f5851f068eeb432af34850dddda8cc1c71e3/src/nvim/auevents.lua#L119-L124">https://github.com/neovim/neovim/blob/71d4f5851f068eeb432af34850dddda8cc1c71e3/src/nvim/auevents.lua#L119-L124</a> + </p> + + <pre language="lua" linenumbering="unnumbered"> + <code> aliases = { +BufCreate = 'BufAdd', +BufRead = 'BufReadPost', +BufWrite = 'BufWritePre', +FileEncoding = 'EncodingChanged', +},</code> + </pre> + + <p> + ところで、上では取り上げなかった<code>FileEncoding</code>だが、これは<code>:help FileEncoding</code>にしっかりと書いてある。 + </p> + + <pre class="monospaced"> + <code> *FileEncoding* +FileEncoding Obsolete. It still works and is equivalent +to |EncodingChanged|.</code> + </pre> + </section> + + <section id="section--_まとめ"> + <h3><a href="#section--_まとめ">まとめ</a></h3> + <p> + 記事タイトルについて言えば、どちらも変わらないので好きな方を使えばよい。あえて言えば、次のようになるだろう。 + </p> + + <ul> + <li> + <p> + <code>BufAdd</code>/<code>BufCreate</code> + </p> + + <ul> + <li> + <p> + →<code>BufCreate</code>は歴史的な理由により (<code>`for historic reasons'') 存在しているため、新しい方 (`BufAdd</code>) を使う + </p> + </li> + </ul> + </li> + + <li> + <p> + <code>BufRead</code>/<code>BufReadPost</code> + </p> + + <ul> + <li> + <p> + →<code>BufReadPre</code>との対称性のため、あるいは<code>BufWritePost</code>との対称性のため<code>BufReadPost</code>を使う + </p> + </li> + </ul> + </li> + + <li> + <p> + <code>BufWrite</code>/<code>BufWritePre</code> + </p> + + <ul> + <li> + <p> + →<code>BufWritePost</code>との対称性のため、あるいは<code>BufReadPre</code>との対称性のため<code>BufWritePre</code>を使う + </p> + </li> + </ul> </li> + + <li> + <p> + <code>FileEncoding</code>/<code>EncodingChanged</code> + </p> + + <ul> + <li> + <p> + →<code>FileEncoding</code>は<code>`Obsolete'' と明言されているので、`EncodingChanged</code>を使う + </p> + </li> + </ul> + </li> + </ul> - </ol> + <p> + ところでこの調査で知ったのだが、<code>BufRead</code>と<code>BufWrite</code>は上にある通り発火するタイミングが「後」と「前」で対称性がない。可能なら<code>Pre</code>/<code>Post</code>付きのものを使った方が分かりやすいだろう。 + </p> + </section> </section> - <div id="preamble"> -<div class="sectionbody"> -<div class="paragraph"> -<p>この記事は Qiita から移植してきたものです。 元 URL: -<a href="https://qiita.com/nsfisis/items/79ab4db8564032de0b25" class="bare">https://qiita.com/nsfisis/items/79ab4db8564032de0b25</a></p> -</div> -<hr> -</div> -</div> -<section class="section-1"> - <h2 id="" class="section-header"> - - TL; DR - - </h2> - <div class="section-body"> - <div class="paragraph"> -<p>違いはない。ただのエイリアス。</p> -</div> - </div> -</section> -<section class="section-1"> - <h2 id="" class="section-header"> - - 調査記録 - - </h2> - <div class="section-body"> - <div class="paragraph"> -<p>Vim の autocmd events には似通った名前のものがいくつかある。大抵は -<code>:help</code> -に説明があるが、この記事のタイトルにある2つを含めた以下のイベントには、その違いについて説明がない。</p> -</div> -<div class="ulist"> -<ul> -<li> -<p><code>BufRead</code>/<code>BufReadPost</code></p> -</li> -<li> -<p><code>BufWrite</code>/<code>BufWritePre</code></p> -</li> -<li> -<p><code>BufAdd</code>/<code>BufCreate</code></p> -</li> -</ul> -</div> -<div class="paragraph"> -<p>このうち、<code>BufAdd</code>/<code>BufCreate</code> に関しては、<code>:help BufCreate</code> に</p> -</div> -<div class="quoteblock"> -<blockquote> -<div class="paragraph"> -<p>The BufCreate event is for historic reasons.</p> -</div> -</blockquote> -</div> -<div class="paragraph"> -<p>とあり、おそらくは <code>BufAdd</code> -のエイリアスであろうということがわかる。他の2組も同様ではないかと予想されるが、確認のため -vim と neovim のソースコードを調査した。</p> -</div> -<div class="quoteblock"> -<blockquote> -<div class="paragraph"> -<p>ソースコードへのリンク -<a href="https://github.com/vim/vim/tree/8e6be34338f13a6a625f19bcef82019c9adc65f2">vim -(調査時点での master branch)</a> -<a href="https://github.com/neovim/neovim/tree/71d4f5851f068eeb432af34850dddda8cc1c71e3">neovim -(上に同じ)</a></p> -</div> -</blockquote> -</div> -<section class="section-2"> - <h3 id="" class="section-header"> - - vim のソースコード - - </h3> - <div class="section-body"> - <div class="paragraph"> -<p>以下は、autocmd events -の名前と内部で使われている整数値とのマッピングを定義している箇所である。見ての通り、上でエイリアスではないかと述べた3組には、それぞれ同じ内部値が使われている。</p> -</div> -<div class="paragraph"> -<p><a href="https://github.com/vim/vim/blob/8e6be34338f13a6a625f19bcef82019c9adc65f2/src/autocmd.c#L85-L86" class="bare">https://github.com/vim/vim/blob/8e6be34338f13a6a625f19bcef82019c9adc65f2/src/autocmd.c#L85-L86</a></p> -</div> -<div id="source." class="listingblock"> -<div class="content"> -<pre class="rouge highlight"><code data-lang="c"> <span class="p">{</span><span class="s">"BufAdd"</span><span class="p">,</span> <span class="n">EVENT_BUFADD</span><span class="p">},</span> - <span class="p">{</span><span class="s">"BufCreate"</span><span class="p">,</span> <span class="n">EVENT_BUFADD</span><span class="p">},</span></code></pre> -</div> -</div> -<div class="paragraph"> -<p><a href="https://github.com/vim/vim/blob/8e6be34338f13a6a625f19bcef82019c9adc65f2/src/autocmd.c#L95-L97" class="bare">https://github.com/vim/vim/blob/8e6be34338f13a6a625f19bcef82019c9adc65f2/src/autocmd.c#L95-L97</a></p> -</div> -<div id="source." class="listingblock"> -<div class="content"> -<pre class="rouge highlight"><code data-lang="c"> <span class="p">{</span><span class="s">"BufRead"</span><span class="p">,</span> <span class="n">EVENT_BUFREADPOST</span><span class="p">},</span> - <span class="p">{</span><span class="s">"BufReadCmd"</span><span class="p">,</span> <span class="n">EVENT_BUFREADCMD</span><span class="p">},</span> - <span class="p">{</span><span class="s">"BufReadPost"</span><span class="p">,</span> <span class="n">EVENT_BUFREADPOST</span><span class="p">},</span></code></pre> -</div> -</div> -<div class="paragraph"> -<p><a href="https://github.com/vim/vim/blob/8e6be34338f13a6a625f19bcef82019c9adc65f2/src/autocmd.c#L103-L105" class="bare">https://github.com/vim/vim/blob/8e6be34338f13a6a625f19bcef82019c9adc65f2/src/autocmd.c#L103-L105</a></p> -</div> -<div id="source." class="listingblock"> -<div class="content"> -<pre class="rouge highlight"><code data-lang="c"> <span class="p">{</span><span class="s">"BufWrite"</span><span class="p">,</span> <span class="n">EVENT_BUFWRITEPRE</span><span class="p">},</span> - <span class="p">{</span><span class="s">"BufWritePost"</span><span class="p">,</span> <span class="n">EVENT_BUFWRITEPOST</span><span class="p">},</span> - <span class="p">{</span><span class="s">"BufWritePre"</span><span class="p">,</span> <span class="n">EVENT_BUFWRITEPRE</span><span class="p">},</span></code></pre> -</div> -</div> - </div> -</section> -<section class="section-2"> - <h3 id="" class="section-header"> - - neovim のソースコード - - </h3> - <div class="section-body"> - <div class="paragraph"> -<p>neovim の場合でも同様のマッピングが定義されているが、こちらの場合は Lua -で書かれている。以下にある通り、はっきり <code>aliases</code> と書かれている。</p> -</div> -<div class="paragraph"> -<p><a href="https://github.com/neovim/neovim/blob/71d4f5851f068eeb432af34850dddda8cc1c71e3/src/nvim/auevents.lua#L119-L124" class="bare">https://github.com/neovim/neovim/blob/71d4f5851f068eeb432af34850dddda8cc1c71e3/src/nvim/auevents.lua#L119-L124</a></p> -</div> -<div id="source." class="listingblock"> -<div class="content"> -<pre class="rouge highlight"><code data-lang="lua"> <span class="n">aliases</span> <span class="o">=</span> <span class="p">{</span> - <span class="n">BufCreate</span> <span class="o">=</span> <span class="s1">'BufAdd'</span><span class="p">,</span> - <span class="n">BufRead</span> <span class="o">=</span> <span class="s1">'BufReadPost'</span><span class="p">,</span> - <span class="n">BufWrite</span> <span class="o">=</span> <span class="s1">'BufWritePre'</span><span class="p">,</span> - <span class="n">FileEncoding</span> <span class="o">=</span> <span class="s1">'EncodingChanged'</span><span class="p">,</span> - <span class="p">},</span></code></pre> -</div> -</div> -<div class="paragraph"> -<p>ところで、上では取り上げなかった <code>FileEncoding</code> だが、これは -<code>:help FileEncoding</code> にしっかりと書いてある。</p> -</div> -<div class="literalblock"> -<div class="content"> -<pre> *FileEncoding* -FileEncoding Obsolete. It still works and is equivalent - to |EncodingChanged|.</pre> -</div> -</div> - </div> -</section> -<section class="section-2"> - <h3 id="" class="section-header"> - - まとめ - - </h3> - <div class="section-body"> - <div class="paragraph"> -<p>記事タイトルについて言えば、どちらも変わらないので好きな方を使えばよい。あえて言えば、次のようになるだろう。</p> -</div> -<div class="ulist"> -<ul> -<li> -<p><code>BufAdd</code>/<code>BufCreate</code></p> -<div class="ulist"> -<ul> -<li> -<p>→ <code>BufCreate</code> は歴史的な理由により (<code>`for historic reasons'') -存在しているため、新しい方 (`BufAdd</code>) を使う</p> -</li> -</ul> -</div> -</li> -<li> -<p><code>BufRead</code>/<code>BufReadPost</code></p> -<div class="ulist"> -<ul> -<li> -<p>→ <code>BufReadPre</code> との対称性のため、あるいは <code>BufWritePost</code> -との対称性のため <code>BufReadPost</code> を使う</p> -</li> -</ul> -</div> -</li> -<li> -<p><code>BufWrite</code>/<code>BufWritePre</code></p> -<div class="ulist"> -<ul> -<li> -<p>→ <code>BufWritePost</code> との対称性のため、あるいは <code>BufReadPre</code> -との対称性のため <code>BufWritePre</code> を使う</p> -</li> -</ul> -</div> -</li> -<li> -<p><code>FileEncoding</code>/<code>EncodingChanged</code></p> -<div class="ulist"> -<ul> -<li> -<p>→ <code>FileEncoding</code> は <code>`Obsolete'' -と明言されているので、`EncodingChanged</code> を使う</p> -</li> -</ul> -</div> -</li> -</ul> -</div> -<div class="paragraph"> -<p>ところでこの調査で知ったのだが、<code>BufRead</code> と <code>BufWrite</code> -は上にある通り発火するタイミングが「後」と「前」で対称性がない。可能なら -<code>Pre</code>/<code>Post</code> 付きのものを使った方が分かりやすいだろう。</p> -</div> - </div> -</section> - </div> -</section> </div> - </article> </main> <footer class="footer"> diff --git a/public/posts/2021-10-02/vim-swap-order-of-selected-lines/index.html b/public/posts/2021-10-02/vim-swap-order-of-selected-lines/index.html index 8b94185..14beb7f 100644 --- a/public/posts/2021-10-02/vim-swap-order-of-selected-lines/index.html +++ b/public/posts/2021-10-02/vim-swap-order-of-selected-lines/index.html @@ -4,18 +4,14 @@ <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="author" content="nsfisis"> - <meta name="copyright" content="© nsfisis"> + <meta name="copyright" content="© 2021 nsfisis"> <meta name="description" content="Vim で選択した行の順番を入れ替える方法。"> <meta name="keywords" content="Vim"> <link rel="icon" type="image/svg+xml" href="/favicon.svg"> <title>Vimで選択した行の順番を入れ替える | REPL: Rest-Eat-Program Loop</title> - - <link rel="stylesheet" href="/hl.css?208c52e3b7c9db1cad782c5d30b4698f"> - - <link rel="stylesheet" href="/style.css?779b1a3debcaeba619f6fe500e93d525"> - - <link rel="stylesheet" href="/custom.css?a649ea3528d4b626fb636505d94c1144"> - + <link rel="stylesheet" href="/hl.css?h=208c52e3b7c9db1cad782c5d30b4698f"> + <link rel="stylesheet" href="/style.css?h=779b1a3debcaeba619f6fe500e93d525"> + <link rel="stylesheet" href="/custom.css?h=a649ea3528d4b626fb636505d94c1144"> </head> <body class="single"> <header class="header"> @@ -29,299 +25,220 @@ <article class="post-single"> <header class="post-header"> <h1 class="post-title">Vimで選択した行の順番を入れ替える</h1> - - <ul class="post-tags"> - - <li class="tag"> - <a href="/tags/vim/">Vim</a> - </li> - - </ul> - + <ul class="post-tags"> + <li class="tag"> + <a href="/tags/vim">Vim</a> + </li> + </ul> </header> <div class="post-content"> <section> <h2 id="changelog">更新履歴</h2> <ol> + <li class="revision"> + <time datetime="2021-10-02">2021-10-02</time>: Qiita から移植 + </li> + </ol> + </section> + <p> + この記事は Qiita から移植してきたものです。 元 URL:<a xl:href="https://qiita.com/nsfisis/items/4fefb361d9a693803520">https://qiita.com/nsfisis/items/4fefb361d9a693803520</a> + </p> + + <p> + <hr> + </hr> + </p> + + <section id="section--_バージョン情報"> + <h2><a href="#section--_バージョン情報">バージョン情報</a></h2> + <p> + <code>:version</code>の一部 + </p> + + <blockquote> + <p> + VIM - Vi IMproved 8.2 (2019 Dec 12, compiled Jan 26 2020 11:30:30) macOS version Included patches: 1-148 Huge version without GUI. + </p> + </blockquote> + </section> + + <section id="section--_よく紹介されている手法"> + <h2><a href="#section--_よく紹介されている手法">よく紹介されている手法</a></h2> + <section id="section--_tac_tail"> + <h3><a href="#section--_tac_tail"><code>tac</code>/<code>tail</code></a></h3> + <p> + <code>tac</code>や<code>tail -r</code>などの外部コマンドを<code>!</code>を使って呼び出し、置き換える。 + </p> - <li class="revision"> - <time datetime="2021-10-02">2021-10-02</time>: Qiita から移植 - </li> + <blockquote> + <p> + :h v_! + </p> + </blockquote> - </ol> + <p> + <code>tac</code>コマンドや<code>tail</code>の<code>-r</code>オプションは環境によって利用できないことがあり、複数の環境を行き来する場合に採用しづらい + </p> + </section> + + <section id="section--_gm0"> + <h3><a href="#section--_gm0"><code>:g/^/m0</code></a></h3> + <p> + こちらは外部コマンドに頼らず、Vim の機能のみを使う。<code>g</code>は<code>:global</code>コマンドの、<code>m</code>は<code>:move</code>コマンドの略 + </p> + + <p> + <code>:global</code>コマンドは<code>:[range]global/{pattern}/[command]</code>のように使い、<code>[range]</code>で指定された範囲の行のうち、<code>{pattern}</code>で指定された検索パターンにマッチする行に対して、順番に<code>[command]</code>で指定された Ex コマンドを呼び出す。 + </p> + + <blockquote> + <p> + :h :global + </p> + </blockquote> + + <p> + <code>:move</code>コマンドは<code>[range]:move {address}</code>のように使い、<code>[range]</code>で指定された範囲の行を<code>{address}</code>で指定された位置に移動させる。 + </p> + + <blockquote> + <p> + :h :move + </p> + </blockquote> + + <p> + <code>:g/^/m0</code>のように組み合わせると、「すべての行を1行ずつ 0行目(1行目の上)に動かす」という動きをする。これは確かに行の入れ替えになっている。 + </p> + + <p> + なお、<code>:g/^/m0</code>は全ての行を入れ替えるが、<code>:N,Mg/^/mN-1</code>とすることで N行目から M行目を処理範囲とするよう拡張できる。手でこれを入力するわけにはいかないので、次のようなコマンドを用意する。 + </p> + + <pre language="vim" linenumbering="unnumbered"> + <code>command! -bar -range=% +\ Reverse +\ <line1>,<line2>g/^/m<line1>-1</code> + </pre> + + <p> + これは望みの動作をするが、実際に実行してみると全行がハイライトされてしまう。次節で詳細を述べる。 + </p> + </section> </section> - <div id="preamble"> -<div class="sectionbody"> -<div class="paragraph"> -<p>この記事は Qiita から移植してきたものです。 元 URL: -<a href="https://qiita.com/nsfisis/items/4fefb361d9a693803520" class="bare">https://qiita.com/nsfisis/items/4fefb361d9a693803520</a></p> -</div> -<hr> -</div> -</div> -<section class="section-1"> - <h2 id="" class="section-header"> - - バージョン情報 - - </h2> - <div class="section-body"> - <div class="paragraph"> -<p><code>:version</code> の一部</p> -</div> -<div class="quoteblock"> -<blockquote> -<div class="paragraph"> -<p>VIM - Vi IMproved 8.2 (2019 Dec 12, compiled Jan 26 2020 11:30:30) macOS -version Included patches: 1-148 Huge version without GUI.</p> -</div> -</blockquote> -</div> - </div> -</section> -<section class="section-1"> - <h2 id="" class="section-header"> - - よく紹介されている手法 - - </h2> - <div class="section-body"> - <section class="section-2"> - <h3 id="" class="section-header"> - - <code>tac</code> / <code>tail</code> - - </h3> - <div class="section-body"> - <div class="paragraph"> -<p><code>tac</code> や <code>tail -r</code> などの外部コマンドを <code>!</code> -を使って呼び出し、置き換える。</p> -</div> -<div class="quoteblock"> -<blockquote> -<div class="paragraph"> -<p>:h v_!</p> -</div> -</blockquote> -</div> -<div class="paragraph"> -<p><code>tac</code> コマンドや <code>tail</code> の <code>-r</code> -オプションは環境によって利用できないことがあり、複数の環境を行き来する場合に採用しづらい</p> -</div> - </div> -</section> -<section class="section-2"> - <h3 id="" class="section-header"> - - <code>:g/^/m0</code> - - </h3> - <div class="section-body"> - <div class="paragraph"> -<p>こちらは外部コマンドに頼らず、Vim の機能のみを使う。<code>g</code> は <code>:global</code> -コマンドの、<code>m</code> は <code>:move</code> コマンドの略</p> -</div> -<div class="paragraph"> -<p><code>:global</code> コマンドは <code>:[range]global/{pattern}/[command]</code> -のように使い、<code>[range]</code> で指定された範囲の行のうち、<code>{pattern}</code> -で指定された検索パターンにマッチする行に対して、順番に <code>[command]</code> -で指定された Ex コマンドを呼び出す。</p> -</div> -<div class="quoteblock"> -<blockquote> -<div class="paragraph"> -<p>:h :global</p> -</div> -</blockquote> -</div> -<div class="paragraph"> -<p><code>:move</code> コマンドは <code>[range]:move {address}</code> のように使い、<code>[range]</code> -で指定された範囲の行を <code>{address}</code> で指定された位置に移動させる。</p> -</div> -<div class="quoteblock"> -<blockquote> -<div class="paragraph"> -<p>:h :move</p> -</div> -</blockquote> -</div> -<div class="paragraph"> -<p><code>:g/^/m0</code> のように組み合わせると、「すべての行を1行ずつ -0行目(1行目の上)に動かす」という動きをする。これは確かに行の入れ替えになっている。</p> -</div> -<div class="paragraph"> -<p>なお、<code>:g/^/m0</code> は全ての行を入れ替えるが、<code>:N,Mg/^/mN-1</code> とすることで -N行目から -M行目を処理範囲とするよう拡張できる。手でこれを入力するわけにはいかないので、次のようなコマンドを用意する。</p> -</div> -<div id="source." class="listingblock"> -<div class="content"> -<pre class="rouge highlight"><code data-lang="vim">command<span class="p">!</span> <span class="p">-</span>bar <span class="p">-</span><span class="nb">range</span><span class="p">=</span>% -<span class="se"> \</span> Reverse -<span class="se"> \</span> <span class="p"><</span>line1<span class="p">>,<</span>line2<span class="p">></span><span class="k">g</span><span class="sr">/^/</span><span class="k">m</span><span class="p"><</span>line1<span class="p">></span><span class="m">-1</span></code></pre> -</div> -</div> -<div class="paragraph"> -<p>これは望みの動作をするが、実際に実行してみると全行がハイライトされてしまう。次節で詳細を述べる。</p> -</div> - </div> -</section> - </div> -</section> -<section class="section-1"> - <h2 id="" class="section-header"> - - <code>:g/^/m0</code> の問題点 - - </h2> - <div class="section-body"> - <div class="paragraph"> -<p><code>:global</code> -コマンドは各行に対してマッチングを行う際、現在の検索パターンを上書きしてしまう。<code>^</code> -は行の先頭にマッチするため、結果として全ての行がハイライトされてしまう。<code>'hlsearch'</code> -オプションを無効にしている場合その限りではないが、その場合でも直前の検索パターンが失われてしまうと -<code>n</code> コマンドなどの際に不便である。</p> -</div> -<div class="quoteblock"> -<blockquote> -<div class="paragraph"> -<p>:h @/</p> -</div> -</blockquote> -</div> - </div> -</section> -<section class="section-1"> - <h2 id="" class="section-header"> - - 解決策 - - </h2> - <div class="section-body"> - <div class="quoteblock"> -<blockquote> -<div class="paragraph"> -<p>[2020/9/28追記] より簡潔な方法を見つけたので次節に追記した</p> -</div> -</blockquote> -</div> -<div class="paragraph"> -<p>前述した <code>:Reverse</code> コマンドの定義を少し変えて、次のようにする:</p> -</div> -<div id="source." class="listingblock"> -<div class="content"> -<pre class="rouge highlight"><code data-lang="vim"><span class="k">function</span><span class="p">!</span> <span class="nv">s:reverse_lines</span><span class="p">(</span>from<span class="p">,</span> <span class="k">to</span><span class="p">)</span> abort - <span class="nb">execute</span> <span class="nb">printf</span><span class="p">(</span><span class="s2">"%d,%dg/^/m%d"</span><span class="p">,</span> <span class="nv">a:from</span><span class="p">,</span> <span class="nv">a:to</span><span class="p">,</span> <span class="nv">a:from</span> <span class="p">-</span> <span class="m">1</span><span class="p">)</span> -<span class="k">endfunction</span> + + <section id="section--_gm0_の問題点"> + <h2><a href="#section--_gm0_の問題点"><code>:g/^/m0</code>の問題点</a></h2> + <p> + <code>:global</code>コマンドは各行に対してマッチングを行う際、現在の検索パターンを上書きしてしまう。<code>^</code>は行の先頭にマッチするため、結果として全ての行がハイライトされてしまう。<code>'hlsearch'</code>オプションを無効にしている場合その限りではないが、その場合でも直前の検索パターンが失われてしまうと<code>n</code>コマンドなどの際に不便である。 + </p> + + <blockquote> + <p> + :h @/ + </p> + </blockquote> + </section> + + <section id="section--_解決策"> + <h2><a href="#section--_解決策">解決策</a></h2> + <blockquote> + <p> + [2020/9/28追記] より簡潔な方法を見つけたので次節に追記した + </p> + </blockquote> + + <p> + 前述した<code>:Reverse</code>コマンドの定義を少し変えて、次のようにする: + </p> + + <pre language="vim" linenumbering="unnumbered"> + <code>function! s:reverse_lines(from, to) abort + execute printf("%d,%dg/^/m%d", a:from, a:to, a:from - 1) + endfunction -command<span class="p">!</span> <span class="p">-</span>bar <span class="p">-</span><span class="nb">range</span><span class="p">=</span>% -<span class="se"> \</span> Reverse -<span class="se"> \</span> <span class="k">call</span> <span class="p"><</span>SID<span class="p">></span>reverse_lines<span class="p">(<</span>line1<span class="p">>,</span> <span class="p"><</span>line2<span class="p">>)</span></code></pre> -</div> -</div> -<div class="paragraph"> -<p>実行しているコマンドが変わったわけではないが、関数呼び出しを経由するようにした。これだけで前述の問題が解決する。</p> -</div> -<div class="paragraph"> -<p>この理由は、ユーザー定義関数を実行する際は検索パターンが一度保存され、実行が終了したあと復元されるため。結果として検索パターンが -<code>^</code> で上書きされることがなくなる。</p> -</div> -<div class="paragraph"> -<p>Vim のヘルプから該当箇所を引用する (強調は筆者による)。</p> -</div> -<div class="quoteblock"> -<blockquote> -<div class="paragraph"> -<p>:h autocmd-searchpat</p> -</div> -<div class="paragraph"> -<p><strong>Autocommands do not change the current search patterns.</strong> Vim saves the -current search patterns before executing autocommands then restores them -after the autocommands finish. This means that autocommands do not -affect the strings highlighted with the `hlsearch' option.</p> -</div> -</blockquote> -</div> -<div class="paragraph"> -<p>これは autocommand -の実行に関しての記述だが、これと同じことがユーザー定義関数の実行時にも適用される。このことは -<code>:nohlsearch</code> のヘルプにある。同じく該当箇所を引用する -(強調は筆者による)。</p> -</div> -<div class="quoteblock"> -<blockquote> -<div class="paragraph"> -<p>:h :nohlsearch</p> -</div> -<div class="paragraph"> -<p>(略) This command doesn’t work in an autocommand, because the -highlighting state is saved and restored when executing autocommands -|autocmd-searchpat|. <strong>Same thing for when invoking a user function.</strong></p> -</div> -</blockquote> -</div> -<div class="paragraph"> -<p>この仕様により、<code>:g/^/m0</code> -の呼び出しをユーザー定義関数に切り出すことで上述の問題を解決できる。</p> -</div> - </div> -</section> -<section class="section-1"> - <h2 id="" class="section-header"> - - 解決策 (改訂版) - - </h2> - <div class="section-body"> - <div class="quoteblock"> -<blockquote> -<div class="paragraph"> -<p>[2020/9/28追記] より簡潔な方法を見つけたため追記する</p> -</div> -</blockquote> -</div> -<div id="source." class="listingblock"> -<div class="content"> -<pre class="rouge highlight"><code data-lang="vim">command<span class="p">!</span> <span class="p">-</span>bar <span class="p">-</span><span class="nb">range</span><span class="p">=</span>% -<span class="se"> \</span> Reverse -<span class="se"> \</span> <span class="k">keeppatterns</span> <span class="p"><</span>line1<span class="p">>,<</span>line2<span class="p">></span><span class="k">g</span><span class="sr">/^/</span><span class="k">m</span><span class="p"><</span>line1<span class="p">></span><span class="m">-1</span></code></pre> -</div> -</div> -<div class="paragraph"> -<p>まさにこのための Exコマンド、<code>:keeppatterns</code> -が存在する。<code>:keeppatterns {command}</code> -のように使い、読んで字の如く、後ろに続く -Exコマンドを「現在の検索パターンを保ったまま」実行する。はるかに分かりやすく意図を表現できる。</p> -</div> -<div class="quoteblock"> -<blockquote> -<div class="paragraph"> -<p>:h :keeppatterns</p> -</div> -</blockquote> -</div> - </div> -</section> -<section class="section-1"> - <h2 id="" class="section-header"> - - コピペ用再掲 - - </h2> - <div class="section-body"> - <div id="source." class="listingblock"> -<div class="content"> -<pre class="rouge highlight"><code data-lang="vim"><span class="c">" License: Public Domain</span> + command! -bar -range=% + \ Reverse + \ call <SID>reverse_lines(<line1>, <line2>)</code> + </pre> + + <p> + 実行しているコマンドが変わったわけではないが、関数呼び出しを経由するようにした。これだけで前述の問題が解決する。 + </p> + + <p> + この理由は、ユーザー定義関数を実行する際は検索パターンが一度保存され、実行が終了したあと復元されるため。結果として検索パターンが<code>^</code>で上書きされることがなくなる。 + </p> + + <p> + Vim のヘルプから該当箇所を引用する (強調は筆者による)。 + </p> + + <blockquote> + <p> + :h autocmd-searchpat + </p> + + <p> + <em role="strong">Autocommands do not change the current search patterns.</em>Vim saves the current search patterns before executing autocommands then restores them after the autocommands finish. This means that autocommands do not affect the strings highlighted with the `hlsearch' option. + </p> + </blockquote> + + <p> + これは autocommand の実行に関しての記述だが、これと同じことがユーザー定義関数の実行時にも適用される。このことは<code>:nohlsearch</code>のヘルプにある。同じく該当箇所を引用する (強調は筆者による)。 + </p> + + <blockquote> + <p> + :h :nohlsearch + </p> + + <p> + (略) This command doesn’t work in an autocommand, because the highlighting state is saved and restored when executing autocommands |autocmd-searchpat|.<em role="strong">Same thing for when invoking a user function.</em> + </p> + </blockquote> + + <p> + この仕様により、<code>:g/^/m0</code>の呼び出しをユーザー定義関数に切り出すことで上述の問題を解決できる。 + </p> + </section> + + <section id="section--_解決策_改訂版"> + <h2><a href="#section--_解決策_改訂版">解決策 (改訂版)</a></h2> + <blockquote> + <p> + [2020/9/28追記] より簡潔な方法を見つけたため追記する + </p> + </blockquote> + + <pre language="vim" linenumbering="unnumbered"> + <code>command! -bar -range=% + \ Reverse + \ keeppatterns <line1>,<line2>g/^/m<line1>-1</code> + </pre> + + <p> + まさにこのための Exコマンド、<code>:keeppatterns</code>が存在する。<code>:keeppatterns {command}</code>のように使い、読んで字の如く、後ろに続く Exコマンドを「現在の検索パターンを保ったまま」実行する。はるかに分かりやすく意図を表現できる。 + </p> + + <blockquote> + <p> + :h :keeppatterns + </p> + </blockquote> + </section> + + <section id="section--_コピペ用再掲"> + <h2><a href="#section--_コピペ用再掲">コピペ用再掲</a></h2> + <pre language="vim" linenumbering="unnumbered"> + <code>" License: Public Domain -command<span class="p">!</span> <span class="p">-</span>bar <span class="p">-</span><span class="nb">range</span><span class="p">=</span>% -<span class="se"> \</span> Reverse -<span class="se"> \</span> <span class="k">keeppatterns</span> <span class="p"><</span>line1<span class="p">>,<</span>line2<span class="p">></span><span class="k">g</span><span class="sr">/^/</span><span class="k">m</span><span class="p"><</span>line1<span class="p">></span><span class="m">-1</span></code></pre> -</div> -</div> - </div> -</section> + command! -bar -range=% + \ Reverse + \ keeppatterns <line1>,<line2>g/^/m<line1>-1</code> + </pre> + </section> </div> - </article> </main> <footer class="footer"> diff --git a/public/posts/2022-04-09/phperkaigi-2022-tokens/index.html b/public/posts/2022-04-09/phperkaigi-2022-tokens/index.html index 4f8d30d..855836e 100644 --- a/public/posts/2022-04-09/phperkaigi-2022-tokens/index.html +++ b/public/posts/2022-04-09/phperkaigi-2022-tokens/index.html @@ -4,18 +4,14 @@ <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="author" content="nsfisis"> - <meta name="copyright" content="© nsfisis"> + <meta name="copyright" content="© 2022 nsfisis"> <meta name="description" content="PHPerKaigi 2022 で私が作成した PHPer チャレンジ問題を解説する。"> <meta name="keywords" content="カンファレンス,PHP,PHPerKaigi"> <link rel="icon" type="image/svg+xml" href="/favicon.svg"> <title>PHPerKaigi 2022 トークン問題の解説 | REPL: Rest-Eat-Program Loop</title> - - <link rel="stylesheet" href="/hl.css?208c52e3b7c9db1cad782c5d30b4698f"> - - <link rel="stylesheet" href="/style.css?779b1a3debcaeba619f6fe500e93d525"> - - <link rel="stylesheet" href="/custom.css?a649ea3528d4b626fb636505d94c1144"> - + <link rel="stylesheet" href="/hl.css?h=208c52e3b7c9db1cad782c5d30b4698f"> + <link rel="stylesheet" href="/style.css?h=779b1a3debcaeba619f6fe500e93d525"> + <link rel="stylesheet" href="/custom.css?h=a649ea3528d4b626fb636505d94c1144"> </head> <body class="single"> <header class="header"> @@ -29,190 +25,154 @@ <article class="post-single"> <header class="post-header"> <h1 class="post-title">PHPerKaigi 2022 トークン問題の解説</h1> - - <ul class="post-tags"> - - <li class="tag"> - <a href="/tags/conference/">カンファレンス</a> - </li> - - <li class="tag"> - <a href="/tags/php/">PHP</a> - </li> - - <li class="tag"> - <a href="/tags/phperkaigi/">PHPerKaigi</a> - </li> - - </ul> - + <ul class="post-tags"> + <li class="tag"> + <a href="/tags/conference">カンファレンス</a> + </li> + <li class="tag"> + <a href="/tags/php">PHP</a> + </li> + <li class="tag"> + <a href="/tags/phperkaigi">PHPerKaigi</a> + </li> + </ul> </header> <div class="post-content"> <section> <h2 id="changelog">更新履歴</h2> <ol> - - <li class="revision"> - <time datetime="2022-04-09">2022-04-09</time>: 公開 - </li> - - <li class="revision"> - <time datetime="2022-04-16">2022-04-16</time>: 2問目、3問目の解説を追加、1問目に加筆 - </li> - + <li class="revision"> + <time datetime="2022-04-09">2022-04-09</time>: 公開 + </li> + <li class="revision"> + <time datetime="2022-04-16">2022-04-16</time>: 2問目、3問目の解説を追加、1問目に加筆 + </li> </ol> </section> - <section class="section-1"> - <h2 id="" class="section-header"> - - はじめに - - </h2> - <div class="section-body"> - <div class="paragraph"> -<p>本日開始された <a href="https://phperkaigi.jp/2022/">PHPerKaigi 2022</a> の PHPer -チャレンジにおいて、弊社 -<a href="https://www.dgcircus.com/">デジタルサーカス株式会社</a> の問題を -3問作成した。この記事では、これらの問題の解説をおこなう。</p> -</div> -<div class="paragraph"> -<p>リポジトリはこちら: <a href="https://github.com/nsfisis/PHPerKaigi2022-tokens" class="bare">https://github.com/nsfisis/PHPerKaigi2022-tokens</a></p> -</div> - </div> -</section> -<section class="section-1"> - <h2 id="" class="section-header"> - - 第1問 brainf_ck.php - - </h2> - <div class="section-body"> - <div class="paragraph"> -<p>ソースコードはこちら。実行には PHP 8.1 以上が必要なので注意。</p> -</div> -<div id="source." class="listingblock"> -<div class="content"> -<pre class="rouge highlight"><code data-lang="php"><span class="o"><?</span><span class="n">php</span> + <section id="section--_はじめに"> + <h2><a href="#section--_はじめに">はじめに</a></h2> + <p> + 本日開始された<a xl:href="https://phperkaigi.jp/2022/">PHPerKaigi 2022</a>の PHPer チャレンジにおいて、弊社<a xl:href="https://www.dgcircus.com/">デジタルサーカス株式会社</a>の問題を 3問作成した。この記事では、これらの問題の解説をおこなう。 + </p> + + <p> + リポジトリはこちら:<a xl:href="https://github.com/nsfisis/PHPerKaigi2022-tokens">https://github.com/nsfisis/PHPerKaigi2022-tokens</a> + </p> + </section> + + <section id="section--_第1問_brainf_ck_php"> + <h2><a href="#section--_第1問_brainf_ck_php">第1問 brainf_ck.php</a></h2> + <p> + ソースコードはこちら。実行には PHP 8.1 以上が必要なので注意。 + </p> + + <pre language="php" linenumbering="unnumbered"> + <code><?php -<span class="k">declare</span><span class="p">(</span><span class="n">strict_types</span><span class="o">=</span><span class="mi">0</span><span class="no">O1</span><span class="p">);</span> + declare(strict_types=0O1); -<span class="kn">namespace</span> <span class="nn">Dgcircus\PHPerKaigi\Y2022</span><span class="p">;</span> + namespace Dgcircus\PHPerKaigi\Y2022; -<span class="cd">/** - * @todo - * Run this program to acquire a PHPer token. - */</span> + /** + * @todo + * Run this program to acquire a PHPer token. + */ -<span class="n">https</span><span class="o">://</span><span class="n">creativecommons</span><span class="mf">.</span><span class="n">org</span><span class="o">/</span><span class="n">publicdomain</span><span class="o">/</span><span class="n">zero</span><span class="o">/</span><span class="mf">1.0</span><span class="o">/</span> + https://creativecommons.org/publicdomain/zero/1.0/ -<span class="err">\</span><span class="nb">error_reporting</span><span class="p">(</span><span class="o">~+!</span><span class="s1">'We are hiring!'</span><span class="p">);</span> + \error_reporting(~+!'We are hiring!'); -<span class="nv">$z</span> <span class="o">=</span> <span class="k">fn</span><span class="p">(</span><span class="nv">$f</span><span class="p">)</span> <span class="o">=></span> <span class="p">(</span><span class="k">fn</span><span class="p">(</span><span class="nv">$x</span><span class="p">)</span> <span class="o">=></span> <span class="nv">$f</span><span class="p">(</span><span class="k">fn</span><span class="p">(...</span><span class="nv">$xs</span><span class="p">)</span> <span class="o">=></span> <span class="nv">$x</span><span class="p">(</span><span class="nv">$x</span><span class="p">)(</span><span class="mf">...</span><span class="nv">$xs</span><span class="p">)))(</span><span class="k">fn</span><span class="p">(</span><span class="nv">$x</span><span class="p">)</span> <span class="o">=></span> <span class="nv">$f</span><span class="p">(</span><span class="k">fn</span><span class="p">(...</span><span class="nv">$xs</span><span class="p">)</span> <span class="o">=></span> <span class="nv">$x</span><span class="p">(</span><span class="nv">$x</span><span class="p">)(</span><span class="mf">...</span><span class="nv">$xs</span><span class="p">)));</span> -<span class="nv">$id</span> <span class="o">=</span> <span class="err">\</span><span class="nb">spl_object_id</span><span class="p">(</span><span class="mf">...</span><span class="p">);</span> -<span class="nv">$put</span> <span class="o">=</span> <span class="k">fn</span><span class="p">(</span><span class="nv">$c</span><span class="p">)</span> <span class="o">=></span> <span class="err">\</span><span class="nb">printf</span><span class="p">(</span><span class="s1">'%c'</span><span class="p">,</span> <span class="nv">$c</span><span class="p">);</span> -<span class="nv">$mm</span> <span class="o">=</span> <span class="k">fn</span><span class="p">(</span><span class="nv">$p</span><span class="p">,</span> <span class="nv">$n</span><span class="p">)</span> <span class="o">=></span> <span class="k">new</span> <span class="err">\</span><span class="nf">ArrayObject</span><span class="p">(</span><span class="err">\</span><span class="nb">array_fill</span><span class="p">(</span><span class="o">+!!</span><span class="p">[],</span> <span class="nv">$n</span><span class="p">,</span> <span class="nv">$p</span><span class="p">));</span> + $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)); -<span class="err">$👉</span> <span class="o">=</span> <span class="k">fn</span><span class="p">(</span><span class="nv">$m</span><span class="p">,</span> <span class="nv">$p</span><span class="p">,</span> <span class="nv">$b</span><span class="p">,</span> <span class="nv">$e</span><span class="p">,</span> <span class="nv">$mp</span><span class="p">,</span> <span class="nv">$pc</span><span class="p">)</span> <span class="o">=></span> <span class="p">[</span><span class="o">++</span><span class="nv">$mp</span><span class="p">,</span> <span class="o">++</span><span class="nv">$pc</span><span class="p">];</span> -<span class="err">$👈</span> <span class="o">=</span> <span class="k">fn</span><span class="p">(</span><span class="nv">$m</span><span class="p">,</span> <span class="nv">$p</span><span class="p">,</span> <span class="nv">$b</span><span class="p">,</span> <span class="nv">$e</span><span class="p">,</span> <span class="nv">$mp</span><span class="p">,</span> <span class="nv">$pc</span><span class="p">)</span> <span class="o">=></span> <span class="p">[</span><span class="o">--</span><span class="nv">$mp</span><span class="p">,</span> <span class="o">++</span><span class="nv">$pc</span><span class="p">];</span> -<span class="err">$👍</span> <span class="o">=</span> <span class="k">fn</span><span class="p">(</span><span class="nv">$m</span><span class="p">,</span> <span class="nv">$p</span><span class="p">,</span> <span class="nv">$b</span><span class="p">,</span> <span class="nv">$e</span><span class="p">,</span> <span class="nv">$mp</span><span class="p">,</span> <span class="nv">$pc</span><span class="p">)</span> <span class="o">=></span> <span class="p">[</span><span class="nv">$mp</span><span class="p">,</span> <span class="o">++</span><span class="nv">$pc</span><span class="p">,</span> <span class="o">++</span><span class="nv">$m</span><span class="p">[</span><span class="nv">$mp</span><span class="p">]];</span> -<span class="err">$👎</span> <span class="o">=</span> <span class="k">fn</span><span class="p">(</span><span class="nv">$m</span><span class="p">,</span> <span class="nv">$p</span><span class="p">,</span> <span class="nv">$b</span><span class="p">,</span> <span class="nv">$e</span><span class="p">,</span> <span class="nv">$mp</span><span class="p">,</span> <span class="nv">$pc</span><span class="p">)</span> <span class="o">=></span> <span class="p">[</span><span class="nv">$mp</span><span class="p">,</span> <span class="o">++</span><span class="nv">$pc</span><span class="p">,</span> <span class="o">--</span><span class="nv">$m</span><span class="p">[</span><span class="nv">$mp</span><span class="p">]];</span> -<span class="err">$📝</span> <span class="o">=</span> <span class="k">fn</span><span class="p">(</span><span class="nv">$m</span><span class="p">,</span> <span class="nv">$p</span><span class="p">,</span> <span class="nv">$b</span><span class="p">,</span> <span class="nv">$e</span><span class="p">,</span> <span class="nv">$mp</span><span class="p">,</span> <span class="nv">$pc</span><span class="p">)</span> <span class="o">=></span> <span class="p">[</span><span class="nv">$mp</span><span class="p">,</span> <span class="o">++</span><span class="nv">$pc</span><span class="p">,</span> <span class="nv">$put</span><span class="p">(</span><span class="nv">$m</span><span class="p">[</span><span class="nv">$mp</span><span class="p">])];</span> -<span class="err">$🤡</span> <span class="o">=</span> <span class="k">fn</span><span class="p">(</span><span class="nv">$m</span><span class="p">,</span> <span class="nv">$p</span><span class="p">,</span> <span class="nv">$b</span><span class="p">,</span> <span class="nv">$e</span><span class="p">,</span> <span class="nv">$mp</span><span class="p">,</span> <span class="nv">$pc</span><span class="p">)</span> <span class="o">=></span> <span class="k">match</span> <span class="p">(</span><span class="nv">$m</span><span class="p">[</span><span class="nv">$mp</span><span class="p">])</span> <span class="p">{</span> - <span class="o">+!!</span><span class="p">[]</span> <span class="o">=></span> <span class="p">[</span><span class="nv">$mp</span><span class="p">,</span> <span class="nv">$z</span><span class="p">(</span><span class="k">fn</span><span class="p">(</span><span class="nv">$loop</span><span class="p">)</span> <span class="o">=></span> <span class="k">fn</span><span class="p">(</span><span class="nv">$pc</span><span class="p">,</span> <span class="nv">$n</span><span class="p">)</span> <span class="o">=></span> <span class="k">match</span> <span class="p">(</span><span class="nv">$id</span><span class="p">(</span><span class="nv">$p</span><span class="p">[</span><span class="nv">$pc</span><span class="p">]))</span> <span class="p">{</span> - <span class="nv">$b</span> <span class="o">=></span> <span class="nv">$loop</span><span class="p">(</span><span class="o">++</span><span class="nv">$pc</span><span class="p">,</span> <span class="o">++</span><span class="nv">$n</span><span class="p">),</span> - <span class="nv">$e</span> <span class="o">=></span> <span class="nv">$n</span> <span class="o">===</span> <span class="o">+!!</span><span class="p">[]</span> <span class="o">?</span> <span class="o">++</span><span class="nv">$pc</span> <span class="o">:</span> <span class="nv">$loop</span><span class="p">(</span><span class="o">++</span><span class="nv">$pc</span><span class="p">,</span> <span class="o">--</span><span class="nv">$n</span><span class="p">),</span> - <span class="k">default</span> <span class="o">=></span> <span class="nv">$loop</span><span class="p">(</span><span class="o">++</span><span class="nv">$pc</span><span class="p">,</span> <span class="nv">$n</span><span class="p">),</span> - <span class="p">})(</span><span class="nv">$pc</span><span class="p">,</span> <span class="o">-!</span><span class="p">[])],</span> - <span class="k">default</span> <span class="o">=></span> <span class="p">[</span><span class="nv">$mp</span><span class="p">,</span> <span class="o">++</span><span class="nv">$pc</span><span class="p">],</span> -<span class="p">};</span> -<span class="err">$🎪</span> <span class="o">=</span> <span class="k">fn</span><span class="p">(</span><span class="nv">$m</span><span class="p">,</span> <span class="nv">$p</span><span class="p">,</span> <span class="nv">$b</span><span class="p">,</span> <span class="nv">$e</span><span class="p">,</span> <span class="nv">$mp</span><span class="p">,</span> <span class="nv">$pc</span><span class="p">)</span> <span class="o">=></span> <span class="k">match</span> <span class="p">(</span><span class="nv">$m</span><span class="p">[</span><span class="nv">$mp</span><span class="p">])</span> <span class="p">{</span> - <span class="o">+!!</span><span class="p">[]</span> <span class="o">=></span> <span class="p">[</span><span class="nv">$mp</span><span class="p">,</span> <span class="o">++</span><span class="nv">$pc</span><span class="p">],</span> - <span class="k">default</span> <span class="o">=></span> <span class="p">[</span><span class="nv">$mp</span><span class="p">,</span> <span class="nv">$z</span><span class="p">(</span><span class="k">fn</span><span class="p">(</span><span class="nv">$loop</span><span class="p">)</span> <span class="o">=></span> <span class="k">fn</span><span class="p">(</span><span class="nv">$pc</span><span class="p">,</span> <span class="nv">$n</span><span class="p">)</span> <span class="o">=></span> <span class="k">match</span> <span class="p">(</span><span class="nv">$id</span><span class="p">(</span><span class="nv">$p</span><span class="p">[</span><span class="nv">$pc</span><span class="p">]))</span> <span class="p">{</span> - <span class="nv">$e</span> <span class="o">=></span> <span class="nv">$loop</span><span class="p">(</span><span class="o">--</span><span class="nv">$pc</span><span class="p">,</span> <span class="o">++</span><span class="nv">$n</span><span class="p">),</span> - <span class="nv">$b</span> <span class="o">=></span> <span class="nv">$n</span> <span class="o">===</span> <span class="o">+!!</span><span class="p">[]</span> <span class="o">?</span> <span class="nv">$pc</span><span class="o">+!</span><span class="p">[]</span> <span class="o">:</span> <span class="nv">$loop</span><span class="p">(</span><span class="o">--</span><span class="nv">$pc</span><span class="p">,</span> <span class="o">--</span><span class="nv">$n</span><span class="p">),</span> - <span class="k">default</span> <span class="o">=></span> <span class="nv">$loop</span><span class="p">(</span><span class="o">--</span><span class="nv">$pc</span><span class="p">,</span> <span class="nv">$n</span><span class="p">),</span> - <span class="p">})(</span><span class="nv">$pc</span><span class="p">,</span> <span class="o">-!</span><span class="p">[])],</span> -<span class="p">};</span> -<span class="err">$🐘</span> <span class="o">=</span> <span class="k">fn</span><span class="p">(</span><span class="nv">$p</span><span class="p">)</span> <span class="o">=></span> <span class="nv">$z</span><span class="p">(</span><span class="k">fn</span><span class="p">(</span><span class="nv">$loop</span><span class="p">)</span> <span class="o">=></span> <span class="k">fn</span><span class="p">(</span><span class="nv">$m</span><span class="p">,</span> <span class="nv">$p</span><span class="p">,</span> <span class="nv">$b</span><span class="p">,</span> <span class="nv">$e</span><span class="p">,</span> <span class="nv">$mp</span><span class="p">,</span> <span class="nv">$pc</span><span class="p">)</span> <span class="o">=></span> - <span class="k">isset</span><span class="p">(</span><span class="nv">$p</span><span class="p">[</span><span class="nv">$pc</span><span class="p">])</span> <span class="o">&&</span> <span class="nv">$loop</span><span class="p">(</span><span class="nv">$m</span><span class="p">,</span> <span class="nv">$p</span><span class="p">,</span> <span class="nv">$b</span><span class="p">,</span> <span class="nv">$e</span><span class="p">,</span> <span class="mf">...</span><span class="p">(</span><span class="nv">$p</span><span class="p">[</span><span class="nv">$pc</span><span class="p">](</span><span class="nv">$m</span><span class="p">,</span> <span class="nv">$p</span><span class="p">,</span> <span class="nv">$b</span><span class="p">,</span> <span class="nv">$e</span><span class="p">,</span> <span class="nv">$mp</span><span class="p">,</span> <span class="nv">$pc</span><span class="p">)))</span> -<span class="p">)(</span><span class="nv">$mm</span><span class="p">(</span><span class="o">+!!</span><span class="p">[],</span> <span class="o">+</span><span class="p">(</span><span class="o">!</span><span class="p">[]</span><span class="mf">.</span><span class="o">!</span><span class="p">[])),</span> <span class="nv">$p</span><span class="p">,</span> <span class="nv">$id</span><span class="p">(</span><span class="err">$🤡</span><span class="p">),</span> <span class="nv">$id</span><span class="p">(</span><span class="err">$🎪</span><span class="p">),</span> <span class="o">+!!</span><span class="p">[],</span> <span class="o">+!!</span><span class="p">[]);</span> + $👉 = 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($🎪), +!![], +!![]); -<span class="err">$🐘</span><span class="p">([</span> - <span class="err">$👍</span><span class="p">,</span> <span class="err">$👍</span><span class="p">,</span> <span class="err">$👍</span><span class="p">,</span> <span class="err">$👍</span><span class="p">,</span> <span class="err">$👍</span><span class="p">,</span> <span class="err">$👍</span><span class="p">,</span> <span class="err">$👍</span><span class="p">,</span> <span class="err">$👍</span><span class="p">,</span> <span class="err">$👍</span><span class="p">,</span> <span class="err">$👍</span><span class="p">,</span> - <span class="err">$🤡</span><span class="p">,</span> - <span class="err">$👉</span><span class="p">,</span> <span class="err">$👍</span><span class="p">,</span> <span class="err">$👍</span><span class="p">,</span> <span class="err">$👍</span><span class="p">,</span> - <span class="err">$👉</span><span class="p">,</span> <span class="err">$👍</span><span class="p">,</span> <span class="err">$👍</span><span class="p">,</span> <span class="err">$👍</span><span class="p">,</span> <span class="err">$👍</span><span class="p">,</span> <span class="err">$👍</span><span class="p">,</span> - <span class="err">$👉</span><span class="p">,</span> <span class="err">$👍</span><span class="p">,</span> <span class="err">$👍</span><span class="p">,</span> <span class="err">$👍</span><span class="p">,</span> <span class="err">$👍</span><span class="p">,</span> <span class="err">$👍</span><span class="p">,</span> <span class="err">$👍</span><span class="p">,</span> <span class="err">$👍</span><span class="p">,</span> <span class="err">$👍</span><span class="p">,</span> <span class="err">$👍</span><span class="p">,</span> <span class="err">$👍</span><span class="p">,</span> <span class="err">$👍</span><span class="p">,</span> <span class="err">$👍</span><span class="p">,</span> - <span class="err">$👉</span><span class="p">,</span> <span class="err">$👍</span><span class="p">,</span> <span class="err">$👍</span><span class="p">,</span> <span class="err">$👍</span><span class="p">,</span> <span class="err">$👍</span><span class="p">,</span> <span class="err">$👍</span><span class="p">,</span> <span class="err">$👍</span><span class="p">,</span> <span class="err">$👍</span><span class="p">,</span> <span class="err">$👍</span><span class="p">,</span> <span class="err">$👍</span><span class="p">,</span> <span class="err">$👍</span><span class="p">,</span> - <span class="err">$👈</span><span class="p">,</span> <span class="err">$👈</span><span class="p">,</span> <span class="err">$👈</span><span class="p">,</span> <span class="err">$👈</span><span class="p">,</span> <span class="err">$👎</span><span class="p">,</span> - <span class="err">$🎪</span><span class="p">,</span> - <span class="err">$👉</span><span class="p">,</span> <span class="err">$👍</span><span class="p">,</span> <span class="err">$👍</span><span class="p">,</span> <span class="err">$👍</span><span class="p">,</span> <span class="err">$👍</span><span class="p">,</span> <span class="err">$👍</span><span class="p">,</span> <span class="err">$📝</span><span class="p">,</span> - <span class="err">$👎</span><span class="p">,</span> <span class="err">$👎</span><span class="p">,</span> <span class="err">$📝</span><span class="p">,</span> - <span class="err">$👉</span><span class="p">,</span> <span class="err">$👎</span><span class="p">,</span> <span class="err">$👎</span><span class="p">,</span> <span class="err">$👎</span><span class="p">,</span> <span class="err">$📝</span><span class="p">,</span> - <span class="err">$👉</span><span class="p">,</span> <span class="err">$👎</span><span class="p">,</span> <span class="err">$👎</span><span class="p">,</span> <span class="err">$👎</span><span class="p">,</span> <span class="err">$📝</span><span class="p">,</span> - <span class="err">$👎</span><span class="p">,</span> <span class="err">$👎</span><span class="p">,</span> <span class="err">$📝</span><span class="p">,</span> - <span class="err">$👎</span><span class="p">,</span> <span class="err">$📝</span><span class="p">,</span> - <span class="err">$👈</span><span class="p">,</span> <span class="err">$📝</span><span class="p">,</span> - <span class="err">$👉</span><span class="p">,</span> <span class="err">$👉</span><span class="p">,</span> <span class="err">$👎</span><span class="p">,</span> <span class="err">$👎</span><span class="p">,</span> <span class="err">$📝</span><span class="p">,</span> - <span class="err">$👍</span><span class="p">,</span> <span class="err">$👍</span><span class="p">,</span> <span class="err">$👍</span><span class="p">,</span> <span class="err">$👍</span><span class="p">,</span> <span class="err">$👍</span><span class="p">,</span> <span class="err">$👍</span><span class="p">,</span> <span class="err">$👍</span><span class="p">,</span> <span class="err">$📝</span><span class="p">,</span> - <span class="err">$👈</span><span class="p">,</span> <span class="err">$👎</span><span class="p">,</span> <span class="err">$👎</span><span class="p">,</span> <span class="err">$👎</span><span class="p">,</span> <span class="err">$👎</span><span class="p">,</span> <span class="err">$📝</span><span class="p">,</span> - <span class="err">$👈</span><span class="p">,</span> <span class="err">$📝</span><span class="p">,</span> - <span class="err">$👉</span><span class="p">,</span> <span class="err">$👍</span><span class="p">,</span> <span class="err">$👍</span><span class="p">,</span> <span class="err">$📝</span><span class="p">,</span> - <span class="err">$👉</span><span class="p">,</span> <span class="err">$👎</span><span class="p">,</span> <span class="err">$📝</span><span class="p">,</span> - <span class="err">$👈</span><span class="p">,</span> <span class="err">$📝</span><span class="p">,</span> -<span class="p">]);</span></code></pre> -</div> -</div> -<div class="paragraph"> -<p>この問題は、単に適切なバージョンの PHP で動かせばトークンが得られる。</p> -</div> -<section class="section-2"> - <h3 id="" class="section-header"> - - 解説 - - </h3> - <div class="section-body"> - <section class="section-3"> - <h4 id="" class="section-header"> - - 絵文字 - - </h4> - <div class="section-body"> - <div class="paragraph"> -<p>まず目につくのは大量の絵文字だろう。 PHP -は識別子に使用できる文字の範囲が広く、絵文字も使うことができる。</p> -</div> - </div> -</section> -<section class="section-3"> - <h4 id="" class="section-header"> - - プログラム全体 - - </h4> - <div class="section-body"> - <div class="paragraph"> -<p>Brainf*ck のインタプリタとプログラムになっている。 Brainf*ck -とは、難解プログラミング言語のひとつであり、ここで説明するよりも -Wikipedia の該当ページを読んだ方がよい。</p> -</div> -<div class="paragraph"> -<p><a href="https://ja.wikipedia.org/wiki/Brainfuck" class="bare">https://ja.wikipedia.org/wiki/Brainfuck</a></p> -</div> -<div class="paragraph"> -<p>なお、brainf*ck プログラムを普通の書き方で書くと、次のようになる。</p> -</div> -<div class="literalblock"> -<div class="content"> -<pre>+ + + + + + + + + + + $🐘([ + $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, + $🤡, + $👉, $👍, $👍, $👍, + $👉, $👍, $👍, $👍, $👍, $👍, + $👉, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, + $👉, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, + $👈, $👈, $👈, $👈, $👎, + $🎪, + $👉, $👍, $👍, $👍, $👍, $👍, $📝, + $👎, $👎, $📝, + $👉, $👎, $👎, $👎, $📝, + $👉, $👎, $👎, $👎, $📝, + $👎, $👎, $📝, + $👎, $📝, + $👈, $📝, + $👉, $👉, $👎, $👎, $📝, + $👍, $👍, $👍, $👍, $👍, $👍, $👍, $📝, + $👈, $👎, $👎, $👎, $👎, $📝, + $👈, $📝, + $👉, $👍, $👍, $📝, + $👉, $👎, $📝, + $👈, $📝, + ]);</code> + </pre> + + <p> + この問題は、単に適切なバージョンの PHP で動かせばトークンが得られる。 + </p> + + <section id="section--_解説"> + <h3><a href="#section--_解説">解説</a></h3> + <section id="section--_絵文字"> + <h4><a href="#section--_絵文字">絵文字</a></h4> + <p> + まず目につくのは大量の絵文字だろう。 PHP は識別子に使用できる文字の範囲が広く、絵文字も使うことができる。 + </p> + </section> + + <section id="section--_プログラム全体"> + <h4><a href="#section--_プログラム全体">プログラム全体</a></h4> + <p> + Brainf*ck のインタプリタとプログラムになっている。 Brainf*ck とは、難解プログラミング言語のひとつであり、ここで説明するよりも Wikipedia の該当ページを読んだ方がよい。 + </p> + + <p> + <a xl:href="https://ja.wikipedia.org/wiki/Brainfuck">https://ja.wikipedia.org/wiki/Brainfuck</a> + </p> + + <p> + なお、brainf*ck プログラムを普通の書き方で書くと、次のようになる。 + </p> + + <pre class="monospaced"> + <code>+ + + + + + + + + + [ - > + + + - > + + + + + - > + + + + + + + + + + + + - > + + + + + + + + + + - < < < < - +> + + + +> + + + + + +> + + + + + + + + + + + + +> + + + + + + + + + + +< < < < - ] > + + + + + . - - . @@ -227,541 +187,454 @@ Wikipedia の該当ページを読んだ方がよい。</p> < . > + + . > - . -< .</pre> -</div> -</div> -<div class="paragraph"> -<p>実行結果はこちら: <a href="https://ideone.com/22VWmb" class="bare">https://ideone.com/22VWmb</a></p> -</div> -<div class="paragraph"> -<p>それぞれの絵文字で表された関数が、各命令に対応している。</p> -</div> -<div class="ulist"> -<ul> -<li> -<p><code>$👉</code>: <code>></code></p> -</li> -<li> -<p><code>$👈</code>: <code><</code></p> -</li> -<li> -<p><code>$👍</code>: <code>+</code></p> -</li> -<li> -<p><code>$👎</code>: <code>-</code></p> -</li> -<li> -<p><code>$📝</code>: <code>.</code></p> -</li> -<li> -<p><code>$🤡</code>: <code>[</code></p> -</li> -<li> -<p><code>$🎪</code>: <code>]</code></p> -</li> -</ul> -</div> -<div class="paragraph"> -<p><code>,</code> (入力) に対応する関数はない -(このプログラムでは使わないので用意していない)。</p> -</div> -<div class="paragraph"> -<p>なお、<code>$🐘</code> はいわゆる main 関数であり、プログラムの実行部分である。</p> -</div> - </div> -</section> -<section class="section-3"> - <h4 id="" class="section-header"> - - 絵文字の選択 - - </h4> - <div class="section-body"> - <div class="paragraph"> -<p>おおよそ意味に合致するよう選んでいるが、<code>$🤡</code> と <code>$🎪</code> -は弊社デジタルサーカスにちなんでいる。 また、<code>$🐘</code> は PHP -のマスコットの象に由来する。</p> -</div> - </div> -</section> -<section class="section-3"> - <h4 id="" class="section-header"> - - strict_types - - </h4> - <div class="section-body"> - <div class="paragraph"> -<p><code>declare</code> 文の <code>strict_types</code> に指定できるのは、<code>0</code> か <code>1</code> -の数値リテラルだが、 <code>0x0</code> や <code>0b1</code> のような値も受け付ける。 今回は、PHP -8.1 から追加された、<code>0O</code> または <code>0o</code> から始まる八進数リテラルを使った。</p> -</div> - </div> -</section> -<section class="section-3"> - <h4 id="" class="section-header"> - - URL - - </h4> - <div class="section-body"> - <div class="paragraph"> -<p>ソースコードのライセンスを示したこの部分だが、</p> -</div> -<div id="source." class="listingblock"> -<div class="content"> -<pre class="rouge highlight"><code data-lang="php"><span class="n">https</span><span class="o">://</span><span class="n">creativecommons</span><span class="mf">.</span><span class="n">org</span><span class="o">/</span><span class="n">publicdomain</span><span class="o">/</span><span class="n">zero</span><span class="o">/</span><span class="mf">1.0</span><span class="o">/</span></code></pre> -</div> -</div> -<div class="paragraph"> -<p>完全に合法な PHP のコードである。 <code>https:</code> 部分はラベル、<code>//</code> -以降は行コメントになっている。</p> -</div> - </div> -</section> -<section class="section-3"> - <h4 id="" class="section-header"> - - リテラルなしで数値を生成する - - </h4> - <div class="section-body"> - <div class="paragraph"> -<p>ソースコード中に、ほとんど数値リテラルが書かれていないことにお気づきだろうか。 -PHP では、型変換を利用することで任意の整数を作り出すことができる。</p> -</div> -<div id="source." class="listingblock"> -<div class="content"> -<pre class="rouge highlight"><code data-lang="php"><span class="nb">assert</span><span class="p">(</span><span class="mi">0</span> <span class="o">===</span> <span class="o">+!!</span><span class="p">[]);</span> -<span class="nb">assert</span><span class="p">(</span><span class="mi">1</span> <span class="o">===</span> <span class="o">+!</span><span class="p">[]);</span> -<span class="nb">assert</span><span class="p">(</span><span class="mi">2</span> <span class="o">===</span> <span class="o">!</span><span class="p">[]</span><span class="o">+!</span><span class="p">[]);</span> -<span class="nb">assert</span><span class="p">(</span><span class="mi">3</span> <span class="o">===</span> <span class="o">!</span><span class="p">[]</span><span class="o">+!</span><span class="p">[]</span><span class="o">+!</span><span class="p">[]);</span> -<span class="nb">assert</span><span class="p">(</span><span class="mi">10</span> <span class="o">===</span> <span class="o">+</span><span class="p">(</span><span class="o">!</span><span class="p">[]</span><span class="mf">.</span><span class="o">+!!</span><span class="p">[]));</span></code></pre> -</div> -</div> -<div class="paragraph"> -<p><code>[]</code> に <code>!</code> を適用すると <code>true</code> が返ってくる。それに <code>+</code> -を適用すると、<code>bool</code> から <code>int</code> ヘの型変換が走り、<code>1</code> が生成される。<code>10</code> -はさらにトリッキーだ。まず <code>1</code> と <code>0</code> を作り、<code>.</code> で文字列として結合する -(<code>'10'</code>)。これに <code>+</code> を適用すると、<code>string</code> から <code>int</code> -への型変換が走り、<code>10</code> が生まれる (コード量に頓着しないなら、<code>1</code> を 10 -個足し合わせてももちろん 10 が作れる)。</p> -</div> -<div class="paragraph"> -<p>また、<code>error_reporting</code> に指定しているのは <code>-1</code> である。 これは、<code>!</code> -によって文字列を <code>false</code> にし、<code>+</code> によって <code>false</code> を <code>0</code> -にし、さらにビット反転して <code>-1</code> にしている。</p> -</div> - </div> -</section> -<section class="section-3"> - <h4 id="" class="section-header"> - - <code>if</code> 文なしで条件分岐 - - </h4> - <div class="section-body"> - <div class="paragraph"> -<p>三項演算子ないし <code>match</code> 式を使うことで、<code>if</code> -を一切書かずに条件分岐ができる。 また、<code>&&</code> / <code>||</code> も使えることがある。 -遅延評価が不要なケースでは、<code>[$t, $f][$cond]</code> -のような形で分岐することもできる。</p> -</div> - </div> -</section> -<section class="section-3"> - <h4 id="" class="section-header"> - - <code>while</code>、<code>for</code> 文なしでループ - - </h4> - <div class="section-body"> - <div class="paragraph"> -<p>不動点コンビネータを使って無名再帰する -(詳しい説明は省略する。これらの単語で検索してほしい)。 ここでは、一般に -Z コンビネータとして知られるものを使った (<code>$z</code>)。</p> -</div> -<div class="paragraph"> -<p>実際のところ、<code>$🤡</code> や <code>$🎪</code>、<code>$🐘</code> は、一度 Scheme (Lisp の一種) -で書いてから PHP に翻訳する形で記述した。</p> -</div> -<div class="paragraph"> -<p>なお、PHP は末尾再帰の最適化をおこなわない (少なくとも今のところは) -ので、 あまりに長い brainf*ck -プログラムを書くとスタックオーバーフローする。</p> -</div> - </div> -</section> - </div> -</section> - </div> -</section> -<section class="section-1"> - <h2 id="" class="section-header"> - - 第2問 riddle.php - - </h2> - <div class="section-body"> - <div class="paragraph"> -<p>ソースコードはこちら。実行には PHP 8.0 以上が必要なので注意。</p> -</div> -<div id="source." class="listingblock"> -<div class="content"> -<pre class="rouge highlight"><code data-lang="php"><span class="o"><?</span><span class="n">php</span> +< .</code> + </pre> + + <p> + 実行結果はこちら:<a xl:href="https://ideone.com/22VWmb">https://ideone.com/22VWmb</a> + </p> + + <p> + それぞれの絵文字で表された関数が、各命令に対応している。 + </p> + + <ul> + <li> + <p> + <code>$👉</code>:<code>></code> + </p> + </li> + + <li> + <p> + <code>$👈</code>:<code><</code> + </p> + </li> + + <li> + <p> + <code>$👍</code>:<code>+</code> + </p> + </li> + + <li> + <p> + <code>$👎</code>:<code>-</code> + </p> + </li> + + <li> + <p> + <code>$📝</code>:<code>.</code> + </p> + </li> + + <li> + <p> + <code>$🤡</code>:<code>[</code> + </p> + </li> + + <li> + <p> + <code>$🎪</code>:<code>]</code> + </p> + </li> + </ul> + + <p> + <code>,</code>(入力) に対応する関数はない (このプログラムでは使わないので用意していない)。 + </p> + + <p> + なお、<code>$🐘</code>はいわゆる main 関数であり、プログラムの実行部分である。 + </p> + </section> + + <section id="section--_絵文字の選択"> + <h4><a href="#section--_絵文字の選択">絵文字の選択</a></h4> + <p> + おおよそ意味に合致するよう選んでいるが、<code>$🤡</code>と<code>$🎪</code>は弊社デジタルサーカスにちなんでいる。 また、<code>$🐘</code>は PHP のマスコットの象に由来する。 + </p> + </section> + + <section id="section--_strict_types"> + <h4><a href="#section--_strict_types">strict_types</a></h4> + <p> + <code>declare</code>文の<code>strict_types</code>に指定できるのは、<code>0</code>か<code>1</code>の数値リテラルだが、<code>0x0</code>や<code>0b1</code>のような値も受け付ける。 今回は、PHP 8.1 から追加された、<code>0O</code>または<code>0o</code>から始まる八進数リテラルを使った。 + </p> + </section> + + <section id="section--_url"> + <h4><a href="#section--_url">URL</a></h4> + <p> + ソースコードのライセンスを示したこの部分だが、 + </p> + + <pre language="php" linenumbering="unnumbered"> + <code>https://creativecommons.org/publicdomain/zero/1.0/</code> + </pre> + + <p> + 完全に合法な PHP のコードである。<code>https:</code>部分はラベル、<code>//</code>以降は行コメントになっている。 + </p> + </section> + + <section id="section--_リテラルなしで数値を生成する"> + <h4><a href="#section--_リテラルなしで数値を生成する">リテラルなしで数値を生成する</a></h4> + <p> + ソースコード中に、ほとんど数値リテラルが書かれていないことにお気づきだろうか。 PHP では、型変換を利用することで任意の整数を作り出すことができる。 + </p> + + <pre language="php" linenumbering="unnumbered"> + <code>assert(0 === +!![]); +assert(1 === +![]); +assert(2 === ![]+![]); +assert(3 === ![]+![]+![]); +assert(10 === +(![].+!![]));</code> + </pre> + + <p> + <code>[]</code>に<code>!</code>を適用すると<code>true</code>が返ってくる。それに<code>+</code>を適用すると、<code>bool</code>から<code>int</code>ヘの型変換が走り、<code>1</code>が生成される。<code>10</code>はさらにトリッキーだ。まず<code>1</code>と<code>0</code>を作り、<code>.</code>で文字列として結合する (<code>'10'</code>)。これに<code>+</code>を適用すると、<code>string</code>から<code>int</code>への型変換が走り、<code>10</code>が生まれる (コード量に頓着しないなら、<code>1</code>を 10 個足し合わせてももちろん 10 が作れる)。 + </p> + + <p> + また、<code>error_reporting</code>に指定しているのは<code>-1</code>である。 これは、<code>!</code>によって文字列を<code>false</code>にし、<code>+</code>によって<code>false</code>を<code>0</code>にし、さらにビット反転して<code>-1</code>にしている。 + </p> + </section> + + <section id="section--_if_文なしで条件分岐"> + <h4><a href="#section--_if_文なしで条件分岐"><code>if</code>文なしで条件分岐</a></h4> + <p> + 三項演算子ないし<code>match</code>式を使うことで、<code>if</code>を一切書かずに条件分岐ができる。 また、<code>&&</code>/<code>||</code>も使えることがある。 遅延評価が不要なケースでは、<code>[$t, $f][$cond]</code>のような形で分岐することもできる。 + </p> + </section> + + <section id="section--_whilefor_文なしでループ"> + <h4><a href="#section--_whilefor_文なしでループ"><code>while</code>、<code>for</code>文なしでループ</a></h4> + <p> + 不動点コンビネータを使って無名再帰する (詳しい説明は省略する。これらの単語で検索してほしい)。 ここでは、一般に Z コンビネータとして知られるものを使った (<code>$z</code>)。 + </p> + + <p> + 実際のところ、<code>$🤡</code>や<code>$🎪</code>、<code>$🐘</code>は、一度 Scheme (Lisp の一種) で書いてから PHP に翻訳する形で記述した。 + </p> + + <p> + なお、PHP は末尾再帰の最適化をおこなわない (少なくとも今のところは) ので、 あまりに長い brainf*ck プログラムを書くとスタックオーバーフローする。 + </p> + </section> + </section> + </section> + + <section id="section--_第2問_riddle_php"> + <h2><a href="#section--_第2問_riddle_php">第2問 riddle.php</a></h2> + <p> + ソースコードはこちら。実行には PHP 8.0 以上が必要なので注意。 + </p> + + <pre language="php" linenumbering="unnumbered"> + <code><?php -<span class="cd">/********************************************************* - * 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/ * - *********************************************************/</span> -<span class="k">const</span> <span class="no">N</span> <span class="o">=</span> <span class="mi">0</span> <span class="cm">/* Change it to your answer. */</span><span class="p">;</span> -<span class="nb">assert</span><span class="p">(</span><span class="mi">0</span> <span class="o"><=</span> <span class="nc">N</span> <span class="o">&&</span> <span class="nc">N</span> <span class="o"><=</span> <span class="mb">0b11111_11111_11111_11111_11111</span><span class="p">);</span> + /********************************************************* + * 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); -<span class="nv">$token</span> <span class="o">=</span> <span class="p">[</span> - <span class="mh">0x14B499C</span><span class="p">,</span> - <span class="mh">0x0BE34CC</span><span class="p">,</span> <span class="mh">0x01C9C69</span><span class="p">,</span> - <span class="mh">0x0ECA069</span><span class="p">,</span> <span class="mh">0x01C2449</span><span class="p">,</span> <span class="mh">0x0FDB166</span><span class="p">,</span> <span class="mh">0x01C9C69</span><span class="p">,</span> - <span class="mh">0x01C1C66</span><span class="p">,</span> <span class="mh">0x0FC1C47</span><span class="p">,</span> <span class="mh">0x01C1C66</span><span class="p">,</span> - <span class="mh">0x10C5858</span><span class="p">,</span> <span class="mh">0x1E4E3B8</span><span class="p">,</span> <span class="mh">0x1A2F2F8</span><span class="p">,</span> -<span class="p">];</span> -<span class="k">foreach</span> <span class="p">(</span><span class="nv">$token</span> <span class="k">as</span> <span class="nv">$x</span><span class="p">)</span> <span class="p">{</span> - <span class="nv">$x</span> <span class="o">=</span> <span class="nv">$x</span> <span class="o">^</span> <span class="nc">N</span><span class="p">;</span> + $token = [ + 0x14B499C, + 0x0BE34CC, 0x01C9C69, + 0x0ECA069, 0x01C2449, 0x0FDB166, 0x01C9C69, + 0x01C1C66, 0x0FC1C47, 0x01C1C66, + 0x10C5858, 0x1E4E3B8, 0x1A2F2F8, + ]; + foreach ($token as $x) { + $x = $x ^ N; - <span class="nv">$x</span> <span class="o">=</span> <span class="nb">sprintf</span><span class="p">(</span><span class="s1">'%025b'</span><span class="p">,</span> <span class="nv">$x</span><span class="p">);</span> - <span class="nv">$x</span> <span class="o">=</span> <span class="nb">str_replace</span><span class="p">(</span><span class="n">search</span><span class="o">:</span> <span class="p">[</span><span class="s1">'0'</span><span class="p">,</span> <span class="s1">'1'</span><span class="p">],</span> <span class="n">replace</span><span class="o">:</span> <span class="p">[</span><span class="s1">' '</span><span class="p">,</span> <span class="s1">'#'</span><span class="p">],</span> <span class="n">subject</span><span class="o">:</span> <span class="nv">$x</span><span class="p">);</span> - <span class="nv">$x</span> <span class="o">=</span> <span class="nb">implode</span><span class="p">(</span><span class="s2">"</span><span class="se">\n</span><span class="s2">"</span><span class="p">,</span> <span class="nb">str_split</span><span class="p">(</span><span class="nv">$x</span><span class="p">,</span> <span class="n">length</span><span class="o">:</span> <span class="mi">5</span><span class="p">));</span> - <span class="k">echo</span> <span class="s2">"</span><span class="si">{</span><span class="nv">$x</span><span class="si">}</span><span class="se">\n\n</span><span class="s2">"</span><span class="p">;</span> -<span class="p">}</span></code></pre> -</div> -</div> -<div class="paragraph"> -<p>さて、この問題はさきほどのように単純に実行しただけでは、謎のブロックが表示されるだけでトークンは得られない。 -トークンを得るためには、ソースコードを読み、定数 <code>N</code> -を特定する必要がある。</p> -</div> -<div class="paragraph"> -<p>ここでは、私の想定解を解説する。</p> -</div> -<section class="section-2"> - <h3 id="" class="section-header"> - - 読解 - - </h3> - <div class="section-body"> - <div class="paragraph"> -<p>まずはソースコードを読んでいく。</p> -</div> -<div id="source." class="listingblock"> -<div class="content"> -<pre class="rouge highlight"><code data-lang="php"><span class="nv">$token</span> <span class="o">=</span> <span class="p">[</span> - <span class="c1">// 略</span> -<span class="p">];</span></code></pre> -</div> -</div> -<div class="paragraph"> -<p>数値からなる <code>$token</code> があり、各要素をループしている。</p> -</div> -<div id="source." class="listingblock"> -<div class="content"> -<pre class="rouge highlight"><code data-lang="php"> <span class="nv">$x</span> <span class="o">=</span> <span class="nv">$x</span> <span class="o">^</span> <span class="nc">N</span><span class="p">;</span></code></pre> -</div> -</div> -<div class="paragraph"> -<p>まずは排他的論理和 (xor) を取り、</p> -</div> -<div id="source." class="listingblock"> -<div class="content"> -<pre class="rouge highlight"><code data-lang="php"> <span class="nv">$x</span> <span class="o">=</span> <span class="nb">sprintf</span><span class="p">(</span><span class="s1">'%025b'</span><span class="p">,</span> <span class="nv">$x</span><span class="p">);</span></code></pre> -</div> -</div> -<div class="paragraph"> -<p>二進数に変換して、</p> -</div> -<div id="source." class="listingblock"> -<div class="content"> -<pre class="rouge highlight"><code data-lang="php"> <span class="nv">$x</span> <span class="o">=</span> <span class="nb">str_replace</span><span class="p">(</span><span class="n">search</span><span class="o">:</span> <span class="p">[</span><span class="s1">'0'</span><span class="p">,</span> <span class="s1">'1'</span><span class="p">],</span> <span class="n">replace</span><span class="o">:</span> <span class="p">[</span><span class="s1">' '</span><span class="p">,</span> <span class="s1">'#'</span><span class="p">],</span> <span class="n">subject</span><span class="o">:</span> <span class="nv">$x</span><span class="p">);</span></code></pre> -</div> -</div> -<div class="paragraph"> -<p>0 を空白に、1 を <code>#</code> にし、</p> -</div> -<div id="source." class="listingblock"> -<div class="content"> -<pre class="rouge highlight"><code data-lang="php"> <span class="nv">$x</span> <span class="o">=</span> <span class="nb">implode</span><span class="p">(</span><span class="s2">"</span><span class="se">\n</span><span class="s2">"</span><span class="p">,</span> <span class="nb">str_split</span><span class="p">(</span><span class="nv">$x</span><span class="p">,</span> <span class="n">length</span><span class="o">:</span> <span class="mi">5</span><span class="p">));</span></code></pre> -</div> -</div> -<div class="paragraph"> -<p>5文字ごとに区切ったあと、改行で結合している。</p> -</div> - </div> -</section> -<section class="section-2"> - <h3 id="" class="section-header"> - - ヒント - - </h3> - <div class="section-body"> - <div class="paragraph"> -<p>次に、ソースコードに書いてあるヒントを読んでいく。</p> -</div> -<div class="ulist"> -<ul> -<li> -<p><code>N</code> それ自体は、42 や 8128 -といったような特別な意味を持たず、ランダムに決められている</p> -</li> -<li> -<p><code>$token</code> の各要素は、1文字を表す</p> -</li> -<li> -<p>1文字は 5x5 のセルからなる</p> -</li> -<li> -<p>出力されるのは、完全な PHPer トークンである</p> -</li> -</ul> -</div> -<div class="paragraph"> -<p>ここで、PHPer トークンは必ず <code><mark></code> 記号から始まることを思いだすと、 -<code>$token</code> の最初の数字 <code>0x14B499C</code> は、変換の結果 <code></mark></code> -になるのではないかと予想される (なお、このことは、リポジトリの README -ファイルに追加ヒントとして書かれている)。</p> -</div> - </div> -</section> -<section class="section-2"> - <h3 id="" class="section-header"> - - 解く - - </h3> - <div class="section-body"> - <div class="paragraph"> -<p>ここまでわかれば、あと一歩で解ける。すなわち、<code>0x14B499C</code> が <code>#</code> -に変換されるような <code>N</code> を見つければよい。</p> -</div> -<div class="paragraph"> -<p><code>N</code> は高々</p> -</div> -<div id="source." class="listingblock"> -<div class="content"> -<pre class="rouge highlight"><code data-lang="php"><span class="nb">assert</span><span class="p">(</span><span class="mi">0</span> <span class="o"><=</span> <span class="nc">N</span> <span class="o">&&</span> <span class="nc">N</span> <span class="o"><=</span> <span class="mb">0b11111_11111_11111_11111_11111</span><span class="p">);</span></code></pre> -</div> -</div> -<div class="paragraph"> -<p>なのでブルートフォースしてもよいが、ここではブルートフォースしない方法を紹介する。</p> -</div> -<div id="source." class="listingblock"> -<div class="content"> -<pre class="rouge highlight"><code data-lang="php"><span class="o"><?</span><span class="n">php</span> + $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"; + }</code> + </pre> + + <p> + さて、この問題はさきほどのように単純に実行しただけでは、謎のブロックが表示されるだけでトークンは得られない。 トークンを得るためには、ソースコードを読み、定数<code>N</code>を特定する必要がある。 + </p> + + <p> + ここでは、私の想定解を解説する。 + </p> + + <section id="section--_読解"> + <h3><a href="#section--_読解">読解</a></h3> + <p> + まずはソースコードを読んでいく。 + </p> + + <pre language="php" linenumbering="unnumbered"> + <code>$token = [ + // 略 + ];</code> + </pre> + + <p> + 数値からなる<code>$token</code>があり、各要素をループしている。 + </p> + + <pre language="php" linenumbering="unnumbered"> + <code> $x = $x ^ N;</code> + </pre> + + <p> + まずは排他的論理和 (xor) を取り、 + </p> + + <pre language="php" linenumbering="unnumbered"> + <code> $x = sprintf('%025b', $x);</code> + </pre> + + <p> + 二進数に変換して、 + </p> + + <pre language="php" linenumbering="unnumbered"> + <code> $x = str_replace(search: ['0', '1'], replace: [' ', '#'], subject: $x);</code> + </pre> + + <p> + 0 を空白に、1 を<code>#</code>にし、 + </p> + + <pre language="php" linenumbering="unnumbered"> + <code> $x = implode("\n", str_split($x, length: 5));</code> + </pre> + + <p> + 5文字ごとに区切ったあと、改行で結合している。 + </p> + </section> + + <section id="section--_ヒント"> + <h3><a href="#section--_ヒント">ヒント</a></h3> + <p> + 次に、ソースコードに書いてあるヒントを読んでいく。 + </p> + + <ul> + <li> + <p> + <code>N</code>それ自体は、42 や 8128 といったような特別な意味を持たず、ランダムに決められている + </p> + </li> + + <li> + <p> + <code>$token</code>の各要素は、1文字を表す + </p> + </li> + + <li> + <p> + 1文字は 5x5 のセルからなる + </p> + </li> + + <li> + <p> + 出力されるのは、完全な PHPer トークンである + </p> + </li> + </ul> + + <p> + ここで、PHPer トークンは必ず<code>#</code>記号から始まることを思いだすと、<code>$token</code>の最初の数字<code>0x14B499C</code>は、変換の結果<code>#</code>になるのではないかと予想される (なお、このことは、リポジトリの README ファイルに追加ヒントとして書かれている)。 + </p> + </section> + + <section id="section--_解く"> + <h3><a href="#section--_解く">解く</a></h3> + <p> + ここまでわかれば、あと一歩で解ける。すなわち、<code>0x14B499C</code>が<code>#</code>に変換されるような<code>N</code>を見つければよい。 + </p> + + <p> + <code>N</code>は高々 + </p> + + <pre language="php" linenumbering="unnumbered"> + <code>assert(0 <= N && N <= 0b11111_11111_11111_11111_11111);</code> + </pre> + + <p> + なのでブルートフォースしてもよいが、ここではブルートフォースしない方法を紹介する。 + </p> + + <pre language="php" linenumbering="unnumbered"> + <code><?php -<span class="nv">$x</span> <span class="o">=</span> <span class="mh">0x14B499C</span><span class="p">;</span> + $x = 0x14B499C; -<span class="nv">$x</span> <span class="o">=</span> <span class="nv">$x</span> <span class="o">^</span> <span class="nc">N</span><span class="p">;</span> + $x = $x ^ N; -<span class="nv">$x</span> <span class="o">=</span> <span class="nb">sprintf</span><span class="p">(</span><span class="s1">'%025b'</span><span class="p">,</span> <span class="nv">$x</span><span class="p">);</span> -<span class="nv">$x</span> <span class="o">=</span> <span class="nb">str_replace</span><span class="p">(</span><span class="n">search</span><span class="o">:</span> <span class="p">[</span><span class="s1">'0'</span><span class="p">,</span> <span class="s1">'1'</span><span class="p">],</span> <span class="n">replace</span><span class="o">:</span> <span class="p">[</span><span class="s1">' '</span><span class="p">,</span> <span class="s1">'#'</span><span class="p">],</span> <span class="n">subject</span><span class="o">:</span> <span class="nv">$x</span><span class="p">);</span> -<span class="nv">$x</span> <span class="o">=</span> <span class="nb">implode</span><span class="p">(</span><span class="s2">"</span><span class="se">\n</span><span class="s2">"</span><span class="p">,</span> <span class="nb">str_split</span><span class="p">(</span><span class="nv">$x</span><span class="p">,</span> <span class="n">length</span><span class="o">:</span> <span class="mi">5</span><span class="p">));</span> + $x = sprintf('%025b', $x); + $x = str_replace(search: ['0', '1'], replace: [' ', '#'], subject: $x); + $x = implode("\n", str_split($x, length: 5)); -<span class="nb">assert</span><span class="p">(</span><span class="nv">$x</span> <span class="o">===</span> - <span class="s2">" # # </span><span class="se">\n</span><span class="s2">"</span> <span class="mf">.</span> - <span class="s2">"#####</span><span class="se">\n</span><span class="s2">"</span> <span class="mf">.</span> - <span class="s2">" # # </span><span class="se">\n</span><span class="s2">"</span> <span class="mf">.</span> - <span class="s2">"#####</span><span class="se">\n</span><span class="s2">"</span> <span class="mf">.</span> - <span class="s2">" # # "</span><span class="p">);</span></code></pre> -</div> -</div> -<div class="paragraph"> -<p>この一連の変換に対する逆変換を考えると、次のようになる。</p> -</div> -<div id="source." class="listingblock"> -<div class="content"> -<pre class="rouge highlight"><code data-lang="php"><span class="o"><?</span><span class="n">php</span> + assert($x === + " # # \n" . + "#####\n" . + " # # \n" . + "#####\n" . + " # # ");</code> + </pre> + + <p> + この一連の変換に対する逆変換を考えると、次のようになる。 + </p> + + <pre language="php" linenumbering="unnumbered"> + <code><?php -<span class="nv">$x</span> <span class="o">=</span> - <span class="s2">" # # </span><span class="se">\n</span><span class="s2">"</span> <span class="mf">.</span> - <span class="s2">"#####</span><span class="se">\n</span><span class="s2">"</span> <span class="mf">.</span> - <span class="s2">" # # </span><span class="se">\n</span><span class="s2">"</span> <span class="mf">.</span> - <span class="s2">"#####</span><span class="se">\n</span><span class="s2">"</span> <span class="mf">.</span> - <span class="s2">" # # "</span><span class="p">;</span> +$x = +" # # \n" . +"#####\n" . +" # # \n" . +"#####\n" . +" # # "; -<span class="nv">$x</span> <span class="o">=</span> <span class="nb">implode</span><span class="p">(</span><span class="s1">''</span><span class="p">,</span> <span class="nb">explode</span><span class="p">(</span><span class="s2">"</span><span class="se">\n</span><span class="s2">"</span><span class="p">,</span> <span class="nv">$x</span><span class="p">));</span> -<span class="nv">$x</span> <span class="o">=</span> <span class="nb">str_replace</span><span class="p">(</span><span class="n">search</span><span class="o">:</span> <span class="p">[</span><span class="s1">' '</span><span class="p">,</span> <span class="s1">'#'</span><span class="p">],</span> <span class="n">replace</span><span class="o">:</span> <span class="p">[</span><span class="s1">'0'</span><span class="p">,</span> <span class="s1">'1'</span><span class="p">],</span> <span class="n">subject</span><span class="o">:</span> <span class="nv">$x</span><span class="p">);</span> -<span class="nv">$x</span> <span class="o">=</span> <span class="nb">bindec</span><span class="p">(</span><span class="nv">$x</span><span class="p">);</span> +$x = implode('', explode("\n", $x)); +$x = str_replace(search: [' ', '#'], replace: ['0', '1'], subject: $x); +$x = bindec($x); -<span class="nv">$n</span> <span class="o">=</span> <span class="nv">$x</span> <span class="o">^</span> <span class="mh">0x14B499C</span><span class="p">;</span> +$n = $x ^ 0x14B499C; -<span class="k">echo</span> <span class="s2">"N = </span><span class="nv">$n</span><span class="se">\n</span><span class="s2">"</span><span class="p">;</span></code></pre> -</div> -</div> -<div class="paragraph"> -<p>これを実行すると、<code>N</code> が得られる。</p> -</div> - </div> -</section> - </div> -</section> -<section class="section-1"> - <h2 id="" class="section-header"> - - 第3問 toquine.php - - </h2> - <div class="section-body"> - <div class="paragraph"> -<p>ソースコードはこちら。</p> -</div> -<div id="source." class="listingblock"> -<div class="content"> -<pre class="rouge highlight"><code data-lang="php"><span class="o"><?</span><span class="n">php</span> +echo "N = $n\n";</code> + </pre> + + <p> + これを実行すると、<code>N</code>が得られる。 + </p> + </section> + </section> + + <section id="section--_第3問_toquine_php"> + <h2><a href="#section--_第3問_toquine_php">第3問 toquine.php</a></h2> + <p> + ソースコードはこちら。 + </p> + + <pre language="php" linenumbering="unnumbered"> + <code><?php -<span class="c1">// License: https://creativecommons.org/publicdomain/zero/1.0/</span> -<span class="c1">// This is a quine-like program to generate a PHPer token.</span> -<span class="c1">// Execute it like this: php toquine.php | php | php | php | ...</span> + // 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 | ... -<span class="nv">$s</span> <span class="o">=</span> <span class="sh"><<<'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;</span> -<span class="nv">$s</span> <span class="o">=</span> <span class="nb">str_rot13</span><span class="p">(</span><span class="nv">$s</span><span class="p">);</span> <span class="nv">$xs</span> <span class="o">=</span> <span class="p">[</span> -<span class="mh">0x0AFABEA</span><span class="p">,</span> <span class="mh">0x1F294A7</span><span class="p">,</span> <span class="mh">0x1F2109F</span><span class="p">,</span> <span class="mh">0x1F294A7</span><span class="p">,</span> <span class="mh">0x0002800</span><span class="p">,</span> <span class="mh">0x1F2109F</span><span class="p">,</span> <span class="mh">0x0117041</span><span class="p">,</span> <span class="mh">0x1F294A7</span><span class="p">,</span> <span class="mh">0x1FAD6B5</span><span class="p">,</span> <span class="mh">0x1F295B7</span><span class="p">,</span> -<span class="mh">0x010FC21</span><span class="p">,</span> <span class="mh">0x1FAD6B5</span><span class="p">,</span> <span class="mh">0x1151151</span><span class="p">,</span> <span class="mh">0x010FC21</span><span class="p">,</span> <span class="mh">0x1F294A7</span><span class="p">,</span> <span class="mh">0x1F295B7</span><span class="p">,</span> <span class="mh">0x1FAD6B5</span><span class="p">,</span> <span class="mh">0x1F294A7</span><span class="p">,</span> <span class="mh">0x1F295B7</span><span class="p">,</span> <span class="mh">0x1F8C63F</span><span class="p">,</span> -<span class="mh">0x1F8C631</span><span class="p">,</span> <span class="mh">0x1FAD6B5</span><span class="p">,</span> <span class="mh">0x17AD6BD</span><span class="p">,</span> <span class="mh">0x17AD6BD</span><span class="p">,</span> <span class="mh">0x1F8C63F</span><span class="p">,</span> <span class="mh">0x1F295B7</span><span class="p">,</span> -<span class="p">];</span> -<span class="nv">$t</span> <span class="o">=</span> <span class="kc">null</span><span class="mf">.</span><span class="kc">false</span><span class="p">;</span> <span class="k">for</span> <span class="p">(</span><span class="nv">$i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nv">$i</span> <span class="o"><=</span> <span class="nb">intdiv</span><span class="p">(</span><span class="k">__LINE__</span><span class="o">-</span><span class="mo">035</span><span class="p">,</span><span class="mi">6</span><span class="p">);</span> <span class="o">++</span><span class="nv">$i</span><span class="p">)</span> <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="k">isset</span><span class="p">(</span><span class="nv">$xs</span><span class="p">[</span><span class="nv">$i</span><span class="p">]))</span> <span class="k">break</span><span class="p">;</span> <span class="k">else</span> -<span class="nv">$t</span> <span class="mf">.</span><span class="o">=</span> <span class="nb">implode</span><span class="p">(</span><span class="s2">"</span><span class="se">\n</span><span class="s2">"</span><span class="p">,</span> <span class="nb">str_split</span><span class="p">(</span><span class="nb">str_replace</span><span class="p">([</span><span class="s1">'0'</span><span class="p">,</span><span class="s1">'1'</span><span class="p">],</span> <span class="p">[</span><span class="s1">' '</span><span class="p">,</span><span class="s1">'##'</span><span class="p">],</span> <span class="nb">sprintf</span><span class="p">(</span><span class="nb">chr</span><span class="p">(</span><span class="mi">37</span><span class="p">)</span> <span class="mf">.</span> <span class="s1">'025b'</span><span class="p">,</span> <span class="nv">$xs</span><span class="p">[</span><span class="nv">$i</span><span class="p">])),</span> <span class="mo">012</span><span class="p">))</span> <span class="mf">.</span> <span class="s2">"</span><span class="se">\n\n</span><span class="s2">"</span><span class="p">;</span> -<span class="nv">$ws</span> <span class="o">=</span> <span class="nb">array_map</span><span class="p">(</span><span class="k">fn</span><span class="p">(</span><span class="nv">$w</span><span class="p">)</span> <span class="o">=></span> <span class="nb">implode</span><span class="p">(</span><span class="s1">', '</span><span class="p">,</span> <span class="nv">$w</span><span class="p">),</span> <span class="nb">array_chunk</span><span class="p">(</span><span class="nb">array_map</span><span class="p">(</span><span class="k">fn</span><span class="p">(</span><span class="nv">$x</span><span class="p">)</span> <span class="o">=></span> <span class="nb">sprintf</span><span class="p">(</span><span class="s1">'0x'</span> <span class="mf">.</span> <span class="nb">chr</span><span class="p">(</span><span class="mi">37</span><span class="p">)</span> <span class="mf">.</span> <span class="s1">'07X'</span><span class="p">,</span> <span class="nv">$x</span><span class="p">),</span> <span class="nv">$xs</span><span class="p">),</span> <span class="mi">10</span><span class="p">));</span> -<span class="nb">printf</span><span class="p">(</span><span class="nv">$s</span><span class="p">,</span> <span class="nv">$t</span><span class="p">,</span> <span class="nb">str_rot13</span><span class="p">(</span><span class="s2">"<<<'D'</span><span class="se">\n</span><span class="si">{</span><span class="nv">$s</span><span class="si">}</span><span class="se">\n</span><span class="s2">D"</span><span class="p">),</span> <span class="nb">implode</span><span class="p">(</span><span class="s2">",</span><span class="se">\n</span><span class="s2">"</span><span class="p">,</span> <span class="nv">$ws</span><span class="p">));</span></code></pre> -</div> -</div> -<div class="paragraph"> -<p>コメントにもあるとおり、次のようにして実行すれば答えがでてくる。</p> -</div> -<div id="source." class="listingblock"> -<div class="content"> -<pre class="rouge highlight"><code data-lang="shell-session"><span class="gp">$</span><span class="w"> </span>php toquine.php | php | php | php | ...</code></pre> -</div> -</div> -<div class="paragraph"> -<p>実際にはもう少しパイプで繋げなければならない。</p> -</div> -<section class="section-2"> - <h3 id="" class="section-header"> - - 解説 - - </h3> - <div class="section-body"> - <section class="section-3"> - <h4 id="" class="section-header"> - - プログラム全体 - - </h4> - <div class="section-body"> - <div class="paragraph"> -<p>コメントにもあるとおり、これは quine (風) のプログラムになっている。 -Quine -とは、自分のソースコードをそっくりそのまま出力するようなプログラムのことである。</p> -</div> -<div class="paragraph"> -<p>このプログラムは、実行すると自身とほとんど同じプログラムを出力する。 -異なるのはトークンになっている部分のみである。</p> -</div> - </div> -</section> -<section class="section-3"> - <h4 id="" class="section-header"> - - トークン - - </h4> - <div class="section-body"> - <div class="paragraph"> -<p><code>$xs</code> がトークンに対応している。変換のロジックは <code>riddle.php</code> -とほぼ同じなので省略する。</p> -</div> - </div> -</section> -<section class="section-3"> - <h4 id="" class="section-header"> - - 状態保持 - - </h4> - <div class="section-body"> - <div class="paragraph"> -<p>トークンの何文字目まで出力したかを、ソースコードを変えずに (quine -なので) 覚えておく必要がある。 -このプログラムでは、トークンが出力されるとソースコードがだんだんと長くなっていくのを利用して、<code><em>LINE</em></code> -から情報を取得している。</p> -</div> - </div> -</section> -<section class="section-3"> - <h4 id="" class="section-header"> - - ROT 13 - - </h4> - <div class="section-body"> - <div class="paragraph"> -<p>Quine は、素朴に書くとプログラムの一部が 2回記述されてしまう。 -これがあまり美しくないので、<code>toquine.php</code> では、ROT 13 -変換を使って難読化した。</p> -</div> -<div class="paragraph"> -<p>それにしてもなぜこんなものが標準ライブラリに……。</p> -</div> - </div> -</section> - </div> -</section> - </div> -</section> -<section class="section-1"> - <h2 id="" class="section-header"> - - おわりに - - </h2> - <div class="section-body"> - <div class="paragraph"> -<p>解いていただいたみなさん、また、難易度調整につきあっていただいた社内のみなさん、ありがとうございました。</p> -</div> -<div class="paragraph"> -<p>今回は直前に作りはじめたのもあり、3問だけかつ使い古されたネタばかりになってしまいましたが、 -来年は 5問、より面白い問題を持っていきます。</p> -</div> -<div class="paragraph"> -<p>実はもう作りはじめているので、どうか来年もありますように……。</p> -</div> - </div> -</section> + $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));</code> + </pre> + + <p> + コメントにもあるとおり、次のようにして実行すれば答えがでてくる。 + </p> + + <pre language="shell-session" linenumbering="unnumbered"> + <code>$ php toquine.php | php | php | php | ...</code> + </pre> + + <p> + 実際にはもう少しパイプで繋げなければならない。 + </p> + + <section id="section--_解説_2"> + <h3><a href="#section--_解説_2">解説</a></h3> + <section id="section--_プログラム全体_2"> + <h4><a href="#section--_プログラム全体_2">プログラム全体</a></h4> + <p> + コメントにもあるとおり、これは quine (風) のプログラムになっている。 Quine とは、自分のソースコードをそっくりそのまま出力するようなプログラムのことである。 + </p> + + <p> + このプログラムは、実行すると自身とほとんど同じプログラムを出力する。 異なるのはトークンになっている部分のみである。 + </p> + </section> + + <section id="section--_トークン"> + <h4><a href="#section--_トークン">トークン</a></h4> + <p> + <code>$xs</code>がトークンに対応している。変換のロジックは<code>riddle.php</code>とほぼ同じなので省略する。 + </p> + </section> + + <section id="section--_状態保持"> + <h4><a href="#section--_状態保持">状態保持</a></h4> + <p> + トークンの何文字目まで出力したかを、ソースコードを変えずに (quine なので) 覚えておく必要がある。 このプログラムでは、トークンが出力されるとソースコードがだんだんと長くなっていくのを利用して、<code><em>LINE</em></code>から情報を取得している。 + </p> + </section> + + <section id="section--_rot_13"> + <h4><a href="#section--_rot_13">ROT 13</a></h4> + <p> + Quine は、素朴に書くとプログラムの一部が 2回記述されてしまう。 これがあまり美しくないので、<code>toquine.php</code>では、ROT 13 変換を使って難読化した。 + </p> + + <p> + それにしてもなぜこんなものが標準ライブラリに……。 + </p> + </section> + </section> + </section> + + <section id="section--_おわりに"> + <h2><a href="#section--_おわりに">おわりに</a></h2> + <p> + 解いていただいたみなさん、また、難易度調整につきあっていただいた社内のみなさん、ありがとうございました。 + </p> + + <p> + 今回は直前に作りはじめたのもあり、3問だけかつ使い古されたネタばかりになってしまいましたが、 来年は 5問、より面白い問題を持っていきます。 + </p> + + <p> + 実はもう作りはじめているので、どうか来年もありますように……。 + </p> + </section> </div> - </article> </main> <footer class="footer"> diff --git a/public/posts/2022-04-24/term-banner-write-tool-showing-banner-in-terminal/index.html b/public/posts/2022-04-24/term-banner-write-tool-showing-banner-in-terminal/index.html index c3e430c..109a523 100644 --- a/public/posts/2022-04-24/term-banner-write-tool-showing-banner-in-terminal/index.html +++ b/public/posts/2022-04-24/term-banner-write-tool-showing-banner-in-terminal/index.html @@ -4,18 +4,13 @@ <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="author" content="nsfisis"> - <meta name="copyright" content="© nsfisis"> + <meta name="copyright" content="© 2022 nsfisis"> <meta name="description" content="ターミナルに任意の文字のバナーを表示するためのツールを Go で書いた。"> - <meta name="keywords" content=""> <link rel="icon" type="image/svg+xml" href="/favicon.svg"> <title>term-banner: ターミナルにバナーを表示するツールを書いた | REPL: Rest-Eat-Program Loop</title> - - <link rel="stylesheet" href="/hl.css?208c52e3b7c9db1cad782c5d30b4698f"> - - <link rel="stylesheet" href="/style.css?779b1a3debcaeba619f6fe500e93d525"> - - <link rel="stylesheet" href="/custom.css?a649ea3528d4b626fb636505d94c1144"> - + <link rel="stylesheet" href="/hl.css?h=208c52e3b7c9db1cad782c5d30b4698f"> + <link rel="stylesheet" href="/style.css?h=779b1a3debcaeba619f6fe500e93d525"> + <link rel="stylesheet" href="/custom.css?h=a649ea3528d4b626fb636505d94c1144"> </head> <body class="single"> <header class="header"> @@ -29,170 +24,134 @@ <article class="post-single"> <header class="post-header"> <h1 class="post-title">term-banner: ターミナルにバナーを表示するツールを書いた</h1> - </header> <div class="post-content"> <section> <h2 id="changelog">更新履歴</h2> <ol> + <li class="revision"> + <time datetime="2022-04-24">2022-04-24</time>: 公開 + </li> + <li class="revision"> + <time datetime="2022-04-27">2022-04-27</time>: -f オプションについて追記 + </li> + </ol> + </section> + <section id="section--_はじめに"> + <h2><a href="#section--_はじめに">はじめに</a></h2> + <p> + こんなものを作った。 + </p> + + <pre class="monospaced"> + <code>$ term-banner 'Hello, World!' 'こんにちは、' '世界!'</code> + </pre> + + <p> + image::https://raw.githubusercontent.com/nsfisis/term-banner/main/screenshot.png[term-banner のスクリーンショット] + </p> + + <p> + コマンドライン引数として渡した文字列をターミナルに大きく表示する。 + </p> + + <p> + リポジトリはこちら:<a xl:href="https://github.com/nsfisis/term-banner">https://github.com/nsfisis/term-banner</a> + </p> + </section> + + <section id="section--_motivation"> + <h2><a href="#section--_motivation">Motivation</a></h2> + <p> + 以前、https://github.com/nsfisis/big-clock-mode[big-clock-mode] という似たようなプログラムを書いた。 これは tmux の<code>:clock-mode</code>コマンドに着想を得たもので、<code>:clock-mode</code>よりも大きく現在時刻を表示する。 + </p> + + <p> + <code>big-clock-mode</code>を開発したのは、次のようなシチュエーションで使うためである。 弊社では現在リモートワークが基本だが、web 会議などで画面共有しているときに、休憩を挟んで特定の時刻から再開する、ということがある。 こういったケースで、画面上に現在の時刻を大きめに表示しておくと、モニタから離れても遠くから時刻がわかるので便利である。 + </p> + + <p> + それこそタイマアプリか何かを使えばいいのだが、ターミナルに棲むいきものとしては、住処から離れたくないわけだ。 + </p> + + <p> + しばらく便利に使っていたのだが、ひとつ不満点が出てきた。それは、再開する時刻がいつだったかを覚えておかなければならないということだ。 どこかにメモしておいてもいいが、せっかくなら現在時刻とともに表示させておきたい。 + </p> + + <p> + そんなわけで、「任意の文字列をターミナルに表示する」プログラムを書く運びとなった。 まあ、作らなくても探せばあると思うが、作りたいものは作りたいので知ったことではない。 + </p> + </section> + + <section id="section--_プログラム"> + <h2><a href="#section--_プログラム">プログラム</a></h2> + <p> + 全体の流れは次のようになっている。 + </p> + + <ol numeration="arabic"> + <li> + <p> + フォントファイルを読み込む + </p> + </li> - <li class="revision"> - <time datetime="2022-04-24">2022-04-24</time>: 公開 - </li> - - <li class="revision"> - <time datetime="2022-04-27">2022-04-27</time>: -f オプションについて追記 - </li> + <li> + <p> + コマンドライン引数を Shift-JIS に変換する (フォントが Shift-JIS 基準で並んでいるため) + </p> + </li> + <li> + <p> + 1文字ずつレンダリングしていく + </p> + </li> </ol> + + <p> + <code>big-clock-mode</code>が Go 製なので、今回も Go で書いた。 PNG が標準ライブラリにあったり、Shift-JIS のエンコーディングが準標準ライブラリにあったりしたのは助かった。 + </p> + + <p> + フォントファイルは<code>go:embed</code>で実行ファイルに埋め込んでいるので、ビルド後はワンバイナリで動く。 仕事ではスクリプト言語ばかり書いているが、やはりコンパイル言語はいい。 + </p> + </section> + + <section id="section--_フォント"> + <h2><a href="#section--_フォント">フォント</a></h2> + <p> + フリーの 8x8 ビットマップフォントである、https://littlelimit.net/misaki.htm[美咲フォント 2021-05-05a 版] を使わせていただいた。 + </p> + + <p> + はじめは自分でポチポチ打っていたのだが、「き」くらいまでやって挫折した。 同じく 8x8 で作っていたのだが、平仮名でさえも、この小さなキャンバスにはとても収められない。 + </p> + + <p> + 美咲フォントは、平仮名・片仮名に留まらず、JIS 第一・第二水準の漢字までサポートしている。 第二水準ともなると一生お目にかかることのない字の方が多いくらいだが、これをこの大きさで書くというのは、もはや芸術の域である。 + </p> + + <p> + さらに言うと、実のところ美咲フォントは実サイズ 7x7 で作られており、余白が設けられている。 これは、単純にそのまま並べても字間・行間を確保できるようにという配慮である。 おかげでコーディングまで楽になった。 + </p> + + <p> + ゴシック体と明朝体があったが、私の好みで明朝体の方にした。 ただ、ゴシック体の方が見やすい気がするので、フォントを選べるように後ほど拡張するかもしれない。 + </p> + + <p> + 2022-04-27 追記:<code>-f</code>オプションで選べるようにした。 + </p> + </section> + + <section id="section--_おわりに"> + <h2><a href="#section--_おわりに">おわりに</a></h2> + <p> + あなたもターミナルに住んでみませんか? + </p> </section> - <section class="section-1"> - <h2 id="" class="section-header"> - - はじめに - - </h2> - <div class="section-body"> - <div class="paragraph"> -<p>こんなものを作った。</p> -</div> -<div class="literalblock"> -<div class="content"> -<pre>$ term-banner 'Hello, World!' 'こんにちは、' '世界!'</pre> -</div> -</div> -<div class="paragraph"> -<p>image::https://raw.githubusercontent.com/nsfisis/term-banner/main/screenshot.png[term-banner -のスクリーンショット]</p> -</div> -<div class="paragraph"> -<p>コマンドライン引数として渡した文字列をターミナルに大きく表示する。</p> -</div> -<div class="paragraph"> -<p>リポジトリはこちら: <a href="https://github.com/nsfisis/term-banner" class="bare">https://github.com/nsfisis/term-banner</a></p> -</div> - </div> -</section> -<section class="section-1"> - <h2 id="" class="section-header"> - - Motivation - - </h2> - <div class="section-body"> - <div class="paragraph"> -<p>以前、https://github.com/nsfisis/big-clock-mode[big-clock-mode] -という似たようなプログラムを書いた。 これは tmux の <code>:clock-mode</code> -コマンドに着想を得たもので、<code>:clock-mode</code> -よりも大きく現在時刻を表示する。</p> -</div> -<div class="paragraph"> -<p><code>big-clock-mode</code> -を開発したのは、次のようなシチュエーションで使うためである。 -弊社では現在リモートワークが基本だが、web -会議などで画面共有しているときに、休憩を挟んで特定の時刻から再開する、ということがある。 -こういったケースで、画面上に現在の時刻を大きめに表示しておくと、モニタから離れても遠くから時刻がわかるので便利である。</p> -</div> -<div class="paragraph"> -<p>それこそタイマアプリか何かを使えばいいのだが、ターミナルに棲むいきものとしては、住処から離れたくないわけだ。</p> -</div> -<div class="paragraph"> -<p>しばらく便利に使っていたのだが、ひとつ不満点が出てきた。それは、再開する時刻がいつだったかを覚えておかなければならないということだ。 -どこかにメモしておいてもいいが、せっかくなら現在時刻とともに表示させておきたい。</p> -</div> -<div class="paragraph"> -<p>そんなわけで、「任意の文字列をターミナルに表示する」プログラムを書く運びとなった。 -まあ、作らなくても探せばあると思うが、作りたいものは作りたいので知ったことではない。</p> -</div> - </div> -</section> -<section class="section-1"> - <h2 id="" class="section-header"> - - プログラム - - </h2> - <div class="section-body"> - <div class="paragraph"> -<p>全体の流れは次のようになっている。</p> -</div> -<div class="olist arabic"> -<ol class="arabic"> -<li> -<p>フォントファイルを読み込む</p> -</li> -<li> -<p>コマンドライン引数を Shift-JIS に変換する (フォントが Shift-JIS -基準で並んでいるため)</p> -</li> -<li> -<p>1文字ずつレンダリングしていく</p> -</li> -</ol> -</div> -<div class="paragraph"> -<p><code>big-clock-mode</code> が Go 製なので、今回も Go で書いた。 PNG -が標準ライブラリにあったり、Shift-JIS -のエンコーディングが準標準ライブラリにあったりしたのは助かった。</p> -</div> -<div class="paragraph"> -<p>フォントファイルは <code>go:embed</code> -で実行ファイルに埋め込んでいるので、ビルド後はワンバイナリで動く。 -仕事ではスクリプト言語ばかり書いているが、やはりコンパイル言語はいい。</p> -</div> - </div> -</section> -<section class="section-1"> - <h2 id="" class="section-header"> - - フォント - - </h2> - <div class="section-body"> - <div class="paragraph"> -<p>フリーの 8x8 -ビットマップフォントである、https://littlelimit.net/misaki.htm[美咲フォント -2021-05-05a 版] を使わせていただいた。</p> -</div> -<div class="paragraph"> -<p>はじめは自分でポチポチ打っていたのだが、「き」くらいまでやって挫折した。 -同じく 8x8 -で作っていたのだが、平仮名でさえも、この小さなキャンバスにはとても収められない。</p> -</div> -<div class="paragraph"> -<p>美咲フォントは、平仮名・片仮名に留まらず、JIS -第一・第二水準の漢字までサポートしている。 -第二水準ともなると一生お目にかかることのない字の方が多いくらいだが、これをこの大きさで書くというのは、もはや芸術の域である。</p> -</div> -<div class="paragraph"> -<p>さらに言うと、実のところ美咲フォントは実サイズ 7x7 -で作られており、余白が設けられている。 -これは、単純にそのまま並べても字間・行間を確保できるようにという配慮である。 -おかげでコーディングまで楽になった。</p> -</div> -<div class="paragraph"> -<p>ゴシック体と明朝体があったが、私の好みで明朝体の方にした。 -ただ、ゴシック体の方が見やすい気がするので、フォントを選べるように後ほど拡張するかもしれない。</p> -</div> -<div class="paragraph"> -<p>2022-04-27 追記: <code>-f</code> オプションで選べるようにした。</p> -</div> - </div> -</section> -<section class="section-1"> - <h2 id="" class="section-header"> - - おわりに - - </h2> - <div class="section-body"> - <div class="paragraph"> -<p>あなたもターミナルに住んでみませんか?</p> -</div> - </div> -</section> </div> - </article> </main> <footer class="footer"> diff --git a/public/posts/2022-05-01/phperkaigi-2022/index.html b/public/posts/2022-05-01/phperkaigi-2022/index.html index edb70c2..ef2a0fb 100644 --- a/public/posts/2022-05-01/phperkaigi-2022/index.html +++ b/public/posts/2022-05-01/phperkaigi-2022/index.html @@ -4,18 +4,14 @@ <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="author" content="nsfisis"> - <meta name="copyright" content="© nsfisis"> + <meta name="copyright" content="© 2022 nsfisis"> <meta name="description" content="2022-04-09 から 2022-04-11 にかけて開催された、PHPerKaigi 2022 に参加した。"> <meta name="keywords" content="カンファレンス,PHP,PHPerKaigi"> <link rel="icon" type="image/svg+xml" href="/favicon.svg"> <title>PHPerKaigi 2022 | REPL: Rest-Eat-Program Loop</title> - - <link rel="stylesheet" href="/hl.css?208c52e3b7c9db1cad782c5d30b4698f"> - - <link rel="stylesheet" href="/style.css?779b1a3debcaeba619f6fe500e93d525"> - - <link rel="stylesheet" href="/custom.css?a649ea3528d4b626fb636505d94c1144"> - + <link rel="stylesheet" href="/hl.css?h=208c52e3b7c9db1cad782c5d30b4698f"> + <link rel="stylesheet" href="/style.css?h=779b1a3debcaeba619f6fe500e93d525"> + <link rel="stylesheet" href="/custom.css?h=a649ea3528d4b626fb636505d94c1144"> </head> <body class="single"> <header class="header"> @@ -29,262 +25,235 @@ <article class="post-single"> <header class="post-header"> <h1 class="post-title">PHPerKaigi 2022</h1> + <ul class="post-tags"> + <li class="tag"> + <a href="/tags/conference">カンファレンス</a> + </li> + <li class="tag"> + <a href="/tags/php">PHP</a> + </li> + <li class="tag"> + <a href="/tags/phperkaigi">PHPerKaigi</a> + </li> + </ul> + </header> + <div class="post-content"> + <section> + <h2 id="changelog">更新履歴</h2> + <ol> + <li class="revision"> + <time datetime="2022-05-01">2022-05-01</time>: 公開 + </li> + </ol> + </section> + <section id="section--_はじめに"> + <h2><a href="#section--_はじめに">はじめに</a></h2> + <p> + 2022-04-09 から 2022-04-11 にかけて開催された、https://phperkaigi.jp/2022/[PHPerKaigi 2022] に、一般参加者として参加した。 弊社https://www.dgcircus.com/[デジタルサーカス株式会社] はダイヤモンドスポンサーとなっており、スポンサー枠のチケットを使わせていただいた。 + </p> + + <p> + 昨年のレポートは<a xl:href="/posts/2021-03-30/phperkaigi-2021">こちら</a>。 + </p> + </section> - <ul class="post-tags"> + <section id="section--_感想"> + <h2><a href="#section--_感想">感想</a></h2> + <section id="section--_厳選おすすめトーク"> + <h3><a href="#section--_厳選おすすめトーク">厳選おすすめトーク</a></h3> + <p> + 多くの素晴らしいトークの中から、特におすすめのものを 5つ選んだ。是非聞いてほしい。引用部分は、リンク先プロポーザルから引用している。 + </p> - <li class="tag"> - <a href="/tags/conference/">カンファレンス</a> - </li> + <p> + <a xl:href="https://fortee.jp/phperkaigi-2022/proposal/ef8cf4ed-63fe-42f8-8145-b3e70054458b">予防に勝る防御なし - 堅牢なコードを導く様々な設計のヒント</a> + </p> - <li class="tag"> - <a href="/tags/php/">PHP</a> - </li> + <blockquote> + <p> + PHP はバージョンを追う毎に型宣言、例外、表明、列挙型などの機能が大幅に強化され、堅牢なコードを書くための機能が充実してきました。それらの機能はどう使うと効果的なのでしょうか。 + </p> + + <p> + 本講演では PHP 8.1 をベースにして、誤りを想定してチェックするのではなく、そもそも誤りにくい設計とはどのようなものか、つまり「予防」の観点を軸足に、堅牢なコードを導くための様々な設計のヒントをご紹介します。 + </p> + </blockquote> - <li class="tag"> - <a href="/tags/phperkaigi/">PHPerKaigi</a> - </li> + <p> + <a xl:href="https://fortee.jp/phperkaigi-2022/proposal/db00d49e-0dd6-453f-b54b-f731d112f10e">PHPのエラーを理解して適切なエラーハンドリングを学ぼう</a> + </p> - </ul> + <blockquote> + <p> + PHPを使ってるとよく遭遇する Fatal error / Parse error / Warning / Notice 理解していますか? + <br> + これらのエラー文を理解することで、すぐにエラーの原因に気付き適切に対象できる様になります! + <br> + またそれらを理解した上でのエラーハンドリングを学びましょう。 + </p> + </blockquote> + + <p> + <a xl:href="https://fortee.jp/phperkaigi-2022/proposal/4a7e3ded-9134-4919-955c-ec7bf4491c0d">エラー監視とテスト体制への改善作戦</a> + </p> + + <blockquote> + <p> + 毎日流れてくるエラーに皆さんはどう向き合ってますか? + <br> + エラーを出さない事が一番ですが、完全に塞ぐ事は難しいと考えます。 + <br> + サービス運用の中で本番環境から発生するエラー(サーバー・クライアントサイド・サードパーティ起因のエラー)への監視体制と、 + <br> + エラー・バグ防御のためチームで行っているテストコード文化づくりの話をします。 + </p> + </blockquote> + + <p> + <a xl:href="https://fortee.jp/phperkaigi-2022/proposal/6f47daf8-c78f-4fb1-9b99-e9656e6fe7f7">ISUCON11のPHP実装は、何を考え、どのようにして作られていたのか</a> + </p> + + <blockquote> + <p> + 昨年開催されたISUCON11にて問題(参考実装)のPHPへの移植を担当させていただきました。 + </p> + + <p> + 最終的なソースコードこそシンプルなWebアプリケーションではありますが、その裏には + <br> + ・「(私の思う)良い設計」を実現するための意思決定 + <br> + ・「ISUCONの問題」という位置付けに由来する取捨選択 + <br> + ・移植中に遭遇したトラブルとその解決策 + <br> + といった文脈や葛藤が存在しています。 + </p> + + <p> + 本発表はそれらを共有することで + <br> + ・PHPアプリケーションの設計、実装事例として役立ててもらう + <br> + ・ISUCONの言語移植に興味を持ってもらう + <br> + ・ISUCON問題移植の「実装や設計の練習をする教材」としての可能性を知ってもらう + <br> + ことを目的とします。 + </p> + </blockquote> + + <p> + <a xl:href="https://fortee.jp/phperkaigi-2022/proposal/5a260e4e-542d-4d82-849d-ef3d6cb7c854">チームの仕事はまわっていたけど、メンバーはそれぞれモヤモヤを抱えていた話──40名の大規模開発チームで1on1ログを公開してみた</a> + </p> + + <blockquote> + <p> + サイボウズの大企業向けグループウェアのGaroon(ガルーン)は、PHPで開発されている20年目の製品です。ガルーン開発チームは日本で40名、ベトナムで50名の計90名ほどのチームになっています。また、コロナ禍でフルリモートでの活動がこの2年ほど継続してきました。 + </p> + + <p> + フルリモートになっても仕事はまわっており、継続的にリリースはしていましたが、一方でお互いの考えていることや感じている問題意識が見えづらくなり、モヤモヤを抱えているメンバーが増えていました。 + </p> + + <p> + このセッションでは、そういう状況で私がチーム外からジョインし、聴き役に徹しながら見える化することで状況を改善していった取り組みを紹介します。同じように大きなチームやリモートワークで難しさを感じている人に、難しさの原因への気づきや取り組みへのヒントがあれば幸いです。 + </p> + </blockquote> + </section> + + <section id="section--_トークン問題の作成"> + <h3><a href="#section--_トークン問題の作成">トークン問題の作成</a></h3> + <p> + 今回は、PHPer チャレンジ用に弊社のトークン問題を 3題作成した。こちらについては<a xl:href="/posts/2022-04-09/phperkaigi-2022-tokens">別途記事にしている</a>ので、そちらを参照されたい。 + </p> + </section> + + <section id="section--_phper_チャレンジ"> + <h3><a href="#section--_phper_チャレンジ">PHPer チャレンジ</a></h3> + <p> + <a xl:href="https://fortee.jp/phperkaigi-2022/challenge">1位</a>になった。 + <br> + また、賞品として<a xl:href="https://www.amazon.co.jp/dp/B08MQNJC9Z">Echo Show 15</a>をいただいた。 + </p> + </section> + + <section id="section--_カンファレンス全体への感想"> + <h3><a href="#section--_カンファレンス全体への感想">カンファレンス全体への感想</a></h3> + <p> + <a xl:href="/posts/2021-03-30/phperkaigi-2021">去年の参加レポ</a>では、こんなことを書いた。 + </p> + + <blockquote> + <p> + 1つ個人的な反省点としては、(中略) Discord しかりアンカンファレンスしかり「このイベントのこの瞬間にしかないコンテンツ」に触れずに、後から見返せる発表やスライドに注力してしまった、ということだ。発表の詳細な見直しはあとからできるのだから、今しかできないことを考えるべきだった。 + <br> + まあ初カンファレンスだし、とお茶を濁しておこう。 + </p> + </blockquote> + + <p> + この反省を踏まえ、今年は積極的にほかの場 (公式の Discord サーバや、アンカンファレンス) にも参加した。 + <br> + これにより、参加体験の質がはるかに向上した。特に Discord に関しては、登壇者ご本人による補足や、質問への回答などがおこなわれる (ことが多い) ため、特別な理由のない限り、発言はしないまでも参加はしておいたほうが良いと思われる。 + </p> + + <p> + なお、アンカンファレンスについては、1日目の終わりにhttps://fortee.jp/phperkaigi-2022/unconference/view/d332797a-8921-4706-a7e2-ee72640c9b5e[トークン問題の解説放送]もおこなった。 + </p> + + <p> + また、今年はオフラインとオンラインのハイブリッド開催であったが、去年の全オンラインと比べて、オンライン参加の体験が落ちていなかったのは、特筆すべきであろう。 今年は 3回目のワクチン接種が間に合わなかったこともあり現地参加は見送ったのだが、来年は是非オフラインで参加したい。 + </p> + </section> + </section> - </header> - <div class="post-content"> - <section> - <h2 id="changelog">更新履歴</h2> - <ol> + <section id="section--_そして来年へ"> + <h2><a href="#section--_そして来年へ">そして来年へ……?</a></h2> + <p> + PHPerKaigi 2023 があるかどうか存じ上げないが、あるとすれば、次の 4つを目標としたい。 + </p> + + <ul> + <li> + <p> + プロポーザルを出す + </p> + </li> - <li class="revision"> - <time datetime="2022-05-01">2022-05-01</time>: 公開 - </li> + <li> + <p> + PHPer チャレンジのトークン問題を 5題作成する + </p> + </li> - </ol> + <li> + <p> + 現地に行く + </p> + </li> + + <li> + <p> + PHPer チャレンジで圧勝する + </p> + </li> + </ul> + + <p> + <hr> + </hr> + </p> + + <p> + 最後になりましたが、PHPerKaigi のスタッフ、スポンサー、スピーカーのみなさん、素敵な時間をありがとうございました。 + </p> + + <p> + ではまた来年。 + </p> </section> - <section class="section-1"> - <h2 id="" class="section-header"> - - はじめに - - </h2> - <div class="section-body"> - <div class="paragraph"> -<p>2022-04-09 から 2022-04-11 -にかけて開催された、https://phperkaigi.jp/2022/[PHPerKaigi 2022] -に、一般参加者として参加した。 -弊社https://www.dgcircus.com/[デジタルサーカス株式会社] -はダイヤモンドスポンサーとなっており、スポンサー枠のチケットを使わせていただいた。</p> -</div> -<div class="paragraph"> -<p>昨年のレポートは<a href="/posts/2021-03-30/phperkaigi-2021">こちら</a>。</p> -</div> - </div> -</section> -<section class="section-1"> - <h2 id="" class="section-header"> - - 感想 - - </h2> - <div class="section-body"> - <section class="section-2"> - <h3 id="" class="section-header"> - - 厳選おすすめトーク - - </h3> - <div class="section-body"> - <div class="paragraph"> -<p>多くの素晴らしいトークの中から、特におすすめのものを -5つ選んだ。是非聞いてほしい。引用部分は、リンク先プロポーザルから引用している。</p> -</div> -<div class="paragraph"> -<p><a href="https://fortee.jp/phperkaigi-2022/proposal/ef8cf4ed-63fe-42f8-8145-b3e70054458b">予防に勝る防御なし -- 堅牢なコードを導く様々な設計のヒント</a></p> -</div> -<div class="quoteblock"> -<blockquote> -<div class="paragraph"> -<p>PHP -はバージョンを追う毎に型宣言、例外、表明、列挙型などの機能が大幅に強化され、堅牢なコードを書くための機能が充実してきました。それらの機能はどう使うと効果的なのでしょうか。</p> -</div> -<div class="paragraph"> -<p>本講演では PHP 8.1 -をベースにして、誤りを想定してチェックするのではなく、そもそも誤りにくい設計とはどのようなものか、つまり「予防」の観点を軸足に、堅牢なコードを導くための様々な設計のヒントをご紹介します。</p> -</div> -</blockquote> -</div> -<div class="paragraph"> -<p><a href="https://fortee.jp/phperkaigi-2022/proposal/db00d49e-0dd6-453f-b54b-f731d112f10e">PHPのエラーを理解して適切なエラーハンドリングを学ぼう</a></p> -</div> -<div class="quoteblock"> -<blockquote> -<div class="paragraph"> -<p>PHPを使ってるとよく遭遇する Fatal error / Parse error / Warning / Notice -理解していますか?<br> -これらのエラー文を理解することで、すぐにエラーの原因に気付き適切に対象できる様になります!<br> -またそれらを理解した上でのエラーハンドリングを学びましょう。</p> -</div> -</blockquote> -</div> -<div class="paragraph"> -<p><a href="https://fortee.jp/phperkaigi-2022/proposal/4a7e3ded-9134-4919-955c-ec7bf4491c0d">エラー監視とテスト体制への改善作戦</a></p> -</div> -<div class="quoteblock"> -<blockquote> -<div class="paragraph"> -<p>毎日流れてくるエラーに皆さんはどう向き合ってますか?<br> -エラーを出さない事が一番ですが、完全に塞ぐ事は難しいと考えます。<br> -サービス運用の中で本番環境から発生するエラー(サーバー・クライアントサイド・サードパーティ起因のエラー)への監視体制と、<br> -エラー・バグ防御のためチームで行っているテストコード文化づくりの話をします。</p> -</div> -</blockquote> -</div> -<div class="paragraph"> -<p><a href="https://fortee.jp/phperkaigi-2022/proposal/6f47daf8-c78f-4fb1-9b99-e9656e6fe7f7">ISUCON11のPHP実装は、何を考え、どのようにして作られていたのか</a></p> -</div> -<div class="quoteblock"> -<blockquote> -<div class="paragraph"> -<p>昨年開催されたISUCON11にて問題(参考実装)のPHPへの移植を担当させていただきました。</p> -</div> -<div class="paragraph"> -<p>最終的なソースコードこそシンプルなWebアプリケーションではありますが、その裏には<br> -・「(私の思う)良い設計」を実現するための意思決定<br> -・「ISUCONの問題」という位置付けに由来する取捨選択<br> -・移植中に遭遇したトラブルとその解決策<br> -といった文脈や葛藤が存在しています。</p> -</div> -<div class="paragraph"> -<p>本発表はそれらを共有することで<br> -・PHPアプリケーションの設計、実装事例として役立ててもらう<br> -・ISUCONの言語移植に興味を持ってもらう<br> -・ISUCON問題移植の「実装や設計の練習をする教材」としての可能性を知ってもらう<br> -ことを目的とします。</p> -</div> -</blockquote> -</div> -<div class="paragraph"> -<p><a href="https://fortee.jp/phperkaigi-2022/proposal/5a260e4e-542d-4d82-849d-ef3d6cb7c854">チームの仕事はまわっていたけど、メンバーはそれぞれモヤモヤを抱えていた話──40名の大規模開発チームで1on1ログを公開してみた</a></p> -</div> -<div class="quoteblock"> -<blockquote> -<div class="paragraph"> -<p>サイボウズの大企業向けグループウェアのGaroon(ガルーン)は、PHPで開発されている20年目の製品です。ガルーン開発チームは日本で40名、ベトナムで50名の計90名ほどのチームになっています。また、コロナ禍でフルリモートでの活動がこの2年ほど継続してきました。</p> -</div> -<div class="paragraph"> -<p>フルリモートになっても仕事はまわっており、継続的にリリースはしていましたが、一方でお互いの考えていることや感じている問題意識が見えづらくなり、モヤモヤを抱えているメンバーが増えていました。</p> -</div> -<div class="paragraph"> -<p>このセッションでは、そういう状況で私がチーム外からジョインし、聴き役に徹しながら見える化することで状況を改善していった取り組みを紹介します。同じように大きなチームやリモートワークで難しさを感じている人に、難しさの原因への気づきや取り組みへのヒントがあれば幸いです。</p> -</div> -</blockquote> -</div> - </div> -</section> -<section class="section-2"> - <h3 id="" class="section-header"> - - トークン問題の作成 - - </h3> - <div class="section-body"> - <div class="paragraph"> -<p>今回は、PHPer チャレンジ用に弊社のトークン問題を -3題作成した。こちらについては<a href="/posts/2022-04-09/phperkaigi-2022-tokens">別途記事にしている</a>ので、そちらを参照されたい。</p> -</div> - </div> -</section> -<section class="section-2"> - <h3 id="" class="section-header"> - - PHPer チャレンジ - - </h3> - <div class="section-body"> - <div class="paragraph"> -<p><a href="https://fortee.jp/phperkaigi-2022/challenge">1位</a>になった。<br> -また、賞品として <a href="https://www.amazon.co.jp/dp/B08MQNJC9Z">Echo Show 15</a> -をいただいた。</p> -</div> - </div> -</section> -<section class="section-2"> - <h3 id="" class="section-header"> - - カンファレンス全体への感想 - - </h3> - <div class="section-body"> - <div class="paragraph"> -<p><a href="/posts/2021-03-30/phperkaigi-2021">去年の参加レポ</a> -では、こんなことを書いた。</p> -</div> -<div class="quoteblock"> -<blockquote> -<div class="paragraph"> -<p>1つ個人的な反省点としては、(中略) Discord -しかりアンカンファレンスしかり「このイベントのこの瞬間にしかないコンテンツ」に触れずに、後から見返せる発表やスライドに注力してしまった、ということだ。発表の詳細な見直しはあとからできるのだから、今しかできないことを考えるべきだった。<br> -まあ初カンファレンスだし、とお茶を濁しておこう。</p> -</div> -</blockquote> -</div> -<div class="paragraph"> -<p>この反省を踏まえ、今年は積極的にほかの場 (公式の Discord -サーバや、アンカンファレンス) にも参加した。<br> -これにより、参加体験の質がはるかに向上した。特に Discord -に関しては、登壇者ご本人による補足や、質問への回答などがおこなわれる -(ことが多い) -ため、特別な理由のない限り、発言はしないまでも参加はしておいたほうが良いと思われる。</p> -</div> -<div class="paragraph"> -<p>なお、アンカンファレンスについては、1日目の終わりにhttps://fortee.jp/phperkaigi-2022/unconference/view/d332797a-8921-4706-a7e2-ee72640c9b5e[トークン問題の解説放送]もおこなった。</p> -</div> -<div class="paragraph"> -<p>また、今年はオフラインとオンラインのハイブリッド開催であったが、去年の全オンラインと比べて、オンライン参加の体験が落ちていなかったのは、特筆すべきであろう。 -今年は -3回目のワクチン接種が間に合わなかったこともあり現地参加は見送ったのだが、来年は是非オフラインで参加したい。</p> -</div> - </div> -</section> - </div> -</section> -<section class="section-1"> - <h2 id="" class="section-header"> - - そして来年へ……? - - </h2> - <div class="section-body"> - <div class="paragraph"> -<p>PHPerKaigi 2023 があるかどうか存じ上げないが、あるとすれば、次の -4つを目標としたい。</p> -</div> -<div class="ulist"> -<ul> -<li> -<p>プロポーザルを出す</p> -</li> -<li> -<p>PHPer チャレンジのトークン問題を 5題作成する</p> -</li> -<li> -<p>現地に行く</p> -</li> -<li> -<p>PHPer チャレンジで圧勝する</p> -</li> -</ul> -</div> -<hr> -<div class="paragraph"> -<p>最後になりましたが、PHPerKaigi -のスタッフ、スポンサー、スピーカーのみなさん、素敵な時間をありがとうございました。</p> -</div> -<div class="paragraph"> -<p>ではまた来年。</p> -</div> - </div> -</section> </div> - </article> </main> <footer class="footer"> diff --git a/public/posts/2022-08-27/php-conference-okinawa-code-golf/index.html b/public/posts/2022-08-27/php-conference-okinawa-code-golf/index.html index 6d0e261..f35bd10 100644 --- a/public/posts/2022-08-27/php-conference-okinawa-code-golf/index.html +++ b/public/posts/2022-08-27/php-conference-okinawa-code-golf/index.html @@ -4,18 +4,14 @@ <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="author" content="nsfisis"> - <meta name="copyright" content="© nsfisis"> + <meta name="copyright" content="© 2022 nsfisis"> <meta name="description" content="PHP カンファレンス沖縄の懇親会 LT で出題されたコードゴルフの問題を解いてみた。"> <meta name="keywords" content="カンファレンス,PHP,PHP カンファレンス"> <link rel="icon" type="image/svg+xml" href="/favicon.svg"> <title>PHP カンファレンス沖縄で出題されたコードゴルフの問題を解いてみた | REPL: Rest-Eat-Program Loop</title> - - <link rel="stylesheet" href="/hl.css?208c52e3b7c9db1cad782c5d30b4698f"> - - <link rel="stylesheet" href="/style.css?779b1a3debcaeba619f6fe500e93d525"> - - <link rel="stylesheet" href="/custom.css?a649ea3528d4b626fb636505d94c1144"> - + <link rel="stylesheet" href="/hl.css?h=208c52e3b7c9db1cad782c5d30b4698f"> + <link rel="stylesheet" href="/style.css?h=779b1a3debcaeba619f6fe500e93d525"> + <link rel="stylesheet" href="/custom.css?h=a649ea3528d4b626fb636505d94c1144"> </head> <body class="single"> <header class="header"> @@ -29,209 +25,163 @@ <article class="post-single"> <header class="post-header"> <h1 class="post-title">PHP カンファレンス沖縄で出題されたコードゴルフの問題を解いてみた</h1> + <ul class="post-tags"> + <li class="tag"> + <a href="/tags/conference">カンファレンス</a> + </li> + <li class="tag"> + <a href="/tags/php">PHP</a> + </li> + <li class="tag"> + <a href="/tags/phpcon">PHP カンファレンス</a> + </li> + </ul> + </header> + <div class="post-content"> + <section> + <h2 id="changelog">更新履歴</h2> + <ol> + <li class="revision"> + <time datetime="2022-08-27">2022-08-27</time>: 公開 + </li> + </ol> + </section> + <section id="section--_はじめに"> + <h2><a href="#section--_はじめに">はじめに</a></h2> + <p> + 本日<a xl:href="https://phpcon.okinawa.jp/">PHP カンファレンス沖縄 2022</a>が開催された (らしい)。 + </p> + + <p> + カンファレンスには参加できなかったものの、懇親会の LT で出題されたコードゴルフの問題が Twitter に流れてきたので、解いてみた。 + </p> + + <p> + ツイート:<a xl:href="https://twitter.com/m3m0r7/status/1563397620231712772">https://twitter.com/m3m0r7/status/1563397620231712772</a> + <br> + スライド:<a xl:href="https://speakerdeck.com/memory1994/php-conference-okinawa-2022-extra?slide=3">https://speakerdeck.com/memory1994/php-conference-okinawa-2022-extra?slide=3</a> + </p> + </section> - <ul class="post-tags"> + <section id="section--_解"> + <h2><a href="#section--_解">解</a></h2> + <p> + 細かいレギュレーションは不明だったので、勝手に定めた。 + </p> + + <ul> + <li> + <p> + コマンドライン引数の第1引数で受けとる + </p> + </li> + + <li> + <p> + 結果は標準出力に出す + </p> + </li> - <li class="tag"> - <a href="/tags/conference/">カンファレンス</a> - </li> + <li> + <p> + コンマの直後にはスペースを1つ置く + </p> + </li> - <li class="tag"> - <a href="/tags/php/">PHP</a> - </li> + <li> + <p> + 末尾コンマは禁止 + </p> + </li> - <li class="tag"> - <a href="/tags/phpcon/">PHP カンファレンス</a> - </li> + <li> + <p> + 数字でないものは入ってこないものとする + </p> + </li> + <li> + <p> + 負数は入ってこないものとする + </p> + </li> </ul> + + <p> + 書いたものがこちら: + </p> + + <pre language="php" linenumbering="unnumbered"> + <code>[<?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??[]);?>]</code> + </pre> + + <p> + しめて 123 バイトとなった (末尾改行を含めずにカウント)。 + </p> + + <p> + こちらは改行とスペースを追加したバージョン: + </p> + + <pre language="php" linenumbering="unnumbered"> + <code>[<?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 ?? []); + + ?>]</code> + </pre> + </section> - </header> - <div class="post-content"> - <section> - <h2 id="changelog">更新履歴</h2> - <ol> + <section id="section--_使用したテクニック"> + <h2><a href="#section--_使用したテクニック">使用したテクニック</a></h2> + <section id="section--_指数表記"> + <h3><a href="#section--_指数表記">指数表記</a></h3> + <p> + 割と多くの言語のゴルフで使えるテクニック。<code>e</code>を用いた指数表記で、大きな数を短く表す。このコードでは<code>10000</code>、<code>5000</code>、<code>2000</code>、<code>1000</code>を指数表記している。 + </p> + </section> + + <section id="section--_foreach_や_for_の中身を1つの文に"> + <h3><a href="#section--_foreach_や_for_の中身を1つの文に">foreach や for の中身を1つの文に</a></h3> + <p> + <code>foreach</code>、<code>for</code>、<code>if</code>などの後ろには、通常<code>{</code>を続けて複数の文を連ねるが、中身の文を1つにしてしまえば、<code>{</code>と<code>}</code>を省略できる。C言語などでも使える。 + </p> + </section> + + <section id="section--_r_に初期値を入れない"> + <h3><a href="#section--_r_に初期値を入れない">$r に初期値を入れない</a></h3> + <p> + PHP では、<code>$r[] = &#8230;&#8203;</code>のような配列の末尾に追加する式を実行したとき、<code>$r</code>が未定義だった場合は<code>$r</code>を勝手に定義して空の配列で初期化してくれる。これを利用すると、<code>$r = [];</code>のような初期化が不要になる。 + </p> - <li class="revision"> - <time datetime="2022-08-27">2022-08-27</time>: 公開 - </li> + <p> + ただし、プログラムに 0 が渡されるとループを一度も回らないので、<code>$r</code>が未定義になってしまい、<code>implode()</code>に渡すところでエラーになる。それを防ぐために<code>$r ?? []</code>を使っている。 + </p> - </ol> + <p> + もし 0 が渡されたケースを無視するなら、これが不要になるので 4 バイト縮む。 + </p> + </section> + + <section id="section--_php_タグの外に文字列を置く"> + <h3><a href="#section--_php_タグの外に文字列を置く">PHP タグの外に文字列を置く</a></h3> + <p> + PHP では、<code><?php</code><code>?></code>で囲われた部分の外側にある文字列は、そのまま出力される。今回のケースでは、先頭と末尾に必ず<code>[</code>と<code>]</code>を出力するので、そのまま書いてやればよい。 + </p> + </section> + </section> + + <section id="section--_おわりに"> + <h2><a href="#section--_おわりに">おわりに</a></h2> + <p> + 最後になりましたが、https://twitter.com/m3m0r7[めもりー] さん、楽しい問題をありがとうございました。 + </p> </section> - <section class="section-1"> - <h2 id="" class="section-header"> - - はじめに - - </h2> - <div class="section-body"> - <div class="paragraph"> -<p>本日 <a href="https://phpcon.okinawa.jp/">PHP カンファレンス沖縄 2022</a> -が開催された (らしい)。</p> -</div> -<div class="paragraph"> -<p>カンファレンスには参加できなかったものの、懇親会の LT -で出題されたコードゴルフの問題が Twitter に流れてきたので、解いてみた。</p> -</div> -<div class="paragraph"> -<p>ツイート: <a href="https://twitter.com/m3m0r7/status/1563397620231712772" class="bare">https://twitter.com/m3m0r7/status/1563397620231712772</a><br> -スライド: -<a href="https://speakerdeck.com/memory1994/php-conference-okinawa-2022-extra?slide=3" class="bare">https://speakerdeck.com/memory1994/php-conference-okinawa-2022-extra?slide=3</a></p> -</div> - </div> -</section> -<section class="section-1"> - <h2 id="" class="section-header"> - - 解 - - </h2> - <div class="section-body"> - <div class="paragraph"> -<p>細かいレギュレーションは不明だったので、勝手に定めた。</p> -</div> -<div class="ulist"> -<ul> -<li> -<p>コマンドライン引数の第1引数で受けとる</p> -</li> -<li> -<p>結果は標準出力に出す</p> -</li> -<li> -<p>コンマの直後にはスペースを1つ置く</p> -</li> -<li> -<p>末尾コンマは禁止</p> -</li> -<li> -<p>数字でないものは入ってこないものとする</p> -</li> -<li> -<p>負数は入ってこないものとする</p> -</li> -</ul> -</div> -<div class="paragraph"> -<p>書いたものがこちら:</p> -</div> -<div id="source." class="listingblock"> -<div class="content"> -<pre class="rouge highlight"><code data-lang="php"><span class="p">[</span><span class="o"><?</span><span class="n">php</span> <span class="nv">$n</span><span class="o">=</span><span class="nv">$argv</span><span class="p">[</span><span class="mi">1</span><span class="p">];</span><span class="k">foreach</span><span class="p">([</span><span class="mi">1</span><span class="n">e4</span><span class="p">,</span><span class="mi">5</span><span class="n">e3</span><span class="p">,</span><span class="mi">2</span><span class="n">e3</span><span class="p">,</span><span class="mi">1</span><span class="n">e3</span><span class="p">,</span><span class="mi">500</span><span class="p">,</span><span class="mi">100</span><span class="p">,</span><span class="mi">50</span><span class="p">,</span><span class="mi">10</span><span class="p">,</span><span class="mi">5</span><span class="p">,</span><span class="mi">1</span><span class="p">]</span><span class="k">as</span><span class="nv">$x</span><span class="p">)</span><span class="k">for</span><span class="p">(;</span><span class="nv">$n</span><span class="o">>=</span><span class="nv">$x</span><span class="p">;</span><span class="nv">$n</span><span class="o">-=</span><span class="nv">$x</span><span class="p">)</span><span class="nv">$r</span><span class="p">[]</span><span class="o">=</span><span class="nv">$x</span><span class="p">;</span><span class="k">echo</span> <span class="nb">implode</span><span class="p">(</span><span class="s1">', '</span><span class="p">,</span><span class="nv">$r</span><span class="o">??</span><span class="p">[]);</span><span class="cp">?></span>]</code></pre> -</div> -</div> -<div class="paragraph"> -<p>しめて 123 バイトとなった (末尾改行を含めずにカウント)。</p> -</div> -<div class="paragraph"> -<p>こちらは改行とスペースを追加したバージョン:</p> -</div> -<div id="source." class="listingblock"> -<div class="content"> -<pre class="rouge highlight"><code data-lang="php"><span class="p">[</span><span class="o"><?</span><span class="n">php</span> - -<span class="nv">$n</span> <span class="o">=</span> <span class="nv">$argv</span><span class="p">[</span><span class="mi">1</span><span class="p">];</span> -<span class="k">foreach</span> <span class="p">([</span><span class="mi">1</span><span class="n">e4</span><span class="p">,</span> <span class="mi">5</span><span class="n">e3</span><span class="p">,</span> <span class="mi">2</span><span class="n">e3</span><span class="p">,</span> <span class="mi">1</span><span class="n">e3</span><span class="p">,</span> <span class="mi">500</span><span class="p">,</span> <span class="mi">100</span><span class="p">,</span> <span class="mi">50</span><span class="p">,</span> <span class="mi">10</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">1</span><span class="p">]</span> <span class="k">as</span> <span class="nv">$x</span><span class="p">)</span> - <span class="k">for</span> <span class="p">(;</span> <span class="nv">$n</span> <span class="o">>=</span> <span class="nv">$x</span><span class="p">;</span> <span class="nv">$n</span> <span class="o">-=</span> <span class="nv">$x</span><span class="p">)</span> - <span class="nv">$r</span><span class="p">[]</span> <span class="o">=</span> <span class="nv">$x</span><span class="p">;</span> -<span class="k">echo</span> <span class="nb">implode</span><span class="p">(</span><span class="s1">', '</span><span class="p">,</span> <span class="nv">$r</span> <span class="o">??</span> <span class="p">[]);</span> - -<span class="cp">?></span>]</code></pre> -</div> -</div> - </div> -</section> -<section class="section-1"> - <h2 id="" class="section-header"> - - 使用したテクニック - - </h2> - <div class="section-body"> - <section class="section-2"> - <h3 id="" class="section-header"> - - 指数表記 - - </h3> - <div class="section-body"> - <div class="paragraph"> -<p>割と多くの言語のゴルフで使えるテクニック。<code>e</code> -を用いた指数表記で、大きな数を短く表す。このコードでは -<code>10000</code>、<code>5000</code>、<code>2000</code>、<code>1000</code> を指数表記している。</p> -</div> - </div> -</section> -<section class="section-2"> - <h3 id="" class="section-header"> - - foreach や for の中身を1つの文に - - </h3> - <div class="section-body"> - <div class="paragraph"> -<p><code>foreach</code>、<code>for</code>、<code>if</code> などの後ろには、通常 <code>{</code> -を続けて複数の文を連ねるが、中身の文を1つにしてしまえば、<code>{</code> と <code>}</code> -を省略できる。C言語などでも使える。</p> -</div> - </div> -</section> -<section class="section-2"> - <h3 id="" class="section-header"> - - $r に初期値を入れない - - </h3> - <div class="section-body"> - <div class="paragraph"> -<p>PHP では、<code>$r[] = …​</code> -のような配列の末尾に追加する式を実行したとき、<code>$r</code> が未定義だった場合は -<code>$r</code> -を勝手に定義して空の配列で初期化してくれる。これを利用すると、<code>$r = [];</code> -のような初期化が不要になる。</p> -</div> -<div class="paragraph"> -<p>ただし、プログラムに 0 が渡されるとループを一度も回らないので、<code>$r</code> -が未定義になってしまい、<code>implode()</code> -に渡すところでエラーになる。それを防ぐために <code>$r ?? []</code> を使っている。</p> -</div> -<div class="paragraph"> -<p>もし 0 が渡されたケースを無視するなら、これが不要になるので 4 -バイト縮む。</p> -</div> - </div> -</section> -<section class="section-2"> - <h3 id="" class="section-header"> - - PHP タグの外に文字列を置く - - </h3> - <div class="section-body"> - <div class="paragraph"> -<p>PHP では、<code><?php</code> <code>?></code> -で囲われた部分の外側にある文字列は、そのまま出力される。今回のケースでは、先頭と末尾に必ず -<code>[</code> と <code>]</code> を出力するので、そのまま書いてやればよい。</p> -</div> - </div> -</section> - </div> -</section> -<section class="section-1"> - <h2 id="" class="section-header"> - - おわりに - - </h2> - <div class="section-body"> - <div class="paragraph"> -<p>最後になりましたが、https://twitter.com/m3m0r7[めもりー] -さん、楽しい問題をありがとうございました。</p> -</div> - </div> -</section> </div> - </article> </main> <footer class="footer"> diff --git a/public/posts/2022-08-31/support-for-communty-is-employee-benefits/index.html b/public/posts/2022-08-31/support-for-communty-is-employee-benefits/index.html index e5690b5..fde05ad 100644 --- a/public/posts/2022-08-31/support-for-communty-is-employee-benefits/index.html +++ b/public/posts/2022-08-31/support-for-communty-is-employee-benefits/index.html @@ -4,18 +4,13 @@ <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="author" content="nsfisis"> - <meta name="copyright" content="© nsfisis"> - <meta name="description" content="先日、私の勤めるデジタルサーカス株式会社が、PHP Foundation へ寄付をおこないました。 本件を社内でしつこく推進した1人として、推進の理由等を書き残しておきます。"> - <meta name="keywords" content=""> + <meta name="copyright" content="© 2022 nsfisis"> + <meta name="description" content="先日、私の勤めるデジタルサーカス株式会社が、PHP Foundation へ寄付をおこないました。本件を社内でしつこく推進した1人として、推進の理由等を書き残しておきます。"> <link rel="icon" type="image/svg+xml" href="/favicon.svg"> <title>弊社の PHP Foundation への寄付に寄せて | REPL: Rest-Eat-Program Loop</title> - - <link rel="stylesheet" href="/hl.css?208c52e3b7c9db1cad782c5d30b4698f"> - - <link rel="stylesheet" href="/style.css?779b1a3debcaeba619f6fe500e93d525"> - - <link rel="stylesheet" href="/custom.css?a649ea3528d4b626fb636505d94c1144"> - + <link rel="stylesheet" href="/hl.css?h=208c52e3b7c9db1cad782c5d30b4698f"> + <link rel="stylesheet" href="/style.css?h=779b1a3debcaeba619f6fe500e93d525"> + <link rel="stylesheet" href="/custom.css?h=a649ea3528d4b626fb636505d94c1144"> </head> <body class="single"> <header class="header"> @@ -29,98 +24,75 @@ <article class="post-single"> <header class="post-header"> <h1 class="post-title">弊社の PHP Foundation への寄付に寄せて</h1> - </header> <div class="post-content"> <section> <h2 id="changelog">更新履歴</h2> <ol> - - <li class="revision"> - <time datetime="2022-08-31">2022-08-31</time>: 公開 - </li> - + <li class="revision"> + <time datetime="2022-08-31">2022-08-31</time>: 公開 + </li> </ol> </section> - <section class="section-1"> - <h2 id="" class="section-header"> - - はじめに - - </h2> - <div class="section-body"> - <div class="paragraph"> -<p><strong>注: -これは私個人の意見であり、所属する組織を代表するものではありません。</strong></p> -</div> -<div class="paragraph"> -<p>先日、私の勤める <a href="https://www.dgcircus.com/">デジタルサーカス株式会社</a> が -<a href="https://opencollective.com/phpfoundation">PHP Foundation</a> へ $2,000 -の寄付をおこないました。</p> -</div> -<div class="paragraph"> -<p>記事: <a href="https://www.dgcircus.com/news/581" class="bare">https://www.dgcircus.com/news/581</a></p> -</div> -<div class="paragraph"> -<p>本件を社内でしつこく推進した1人として、推進の理由等を書き残しておきます。</p> -</div> - </div> -</section> -<section class="section-1"> - <h2 id="" class="section-header"> - - なぜ? - - </h2> - <div class="section-body"> - <div class="paragraph"> -<p>組織としての寄付理由は前掲した記事に譲るとして、ここでは、私が社内でこの件を推進した理由について書くことにします。</p> -</div> -<div class="paragraph"> -<p>当時の考えを端的にまとめた社内チャットの投稿があったので、それを引用します:</p> -</div> -<div class="quoteblock"> -<blockquote> -<div class="paragraph"> -<p>結局これを通したい (私の中での) -最大の理由が、「自分の勤める会社が、これをやる会社であってほしい」というのがあり、↑にしても、感情ベースの理由しか出せていないというのが説得力に欠けている理由なのだと思いますが、寄付の報告が流れてきたり、OSS -のフリーライドの話が流れてきたりするたびに、自尊心が毀損される、というか -(これは大袈裟すぎる表現で、実際にはそこまで明確に傷ついているわけではありませんが)。</p> -</div> -<div class="paragraph"> -<p>追記: 「肩身が狭くなる」というのがより適切でした。</p> -</div> -</blockquote> -</div> -<div class="paragraph"> -<p>※文中の「↑にしても」は、ここに載せていない別の投稿を指しています。</p> -</div> -<div class="paragraph"> -<p>OSS を金銭的に支援したり、技術カンファレンスへ協賛したり (あるいは -<a href="https://twitter.com/tomzoh">CTO</a> がカンファレンスを年2で主催したり: -<a href="https://iosdc.jp">iOSDC</a> <a href="https://phperkaigi.jp">PHPerKaigi</a>) -といった行為は、コミュニティへの貢献であると同時に、社員に対する精神的福利厚生でもあると言えるでしょう -(知らんけど)。これらは、技術や技術者を大切にする組織である、ということの、対外的にも対内的にも強力なメッセージなのです。</p> -</div> -<div class="paragraph"> -<p>以上が、私が社内で寄付の件を進めた (かなり私的な) 理由です。</p> -</div> - </div> -</section> -<section class="section-1"> - <h2 id="" class="section-header"> - - おわりに - - </h2> - <div class="section-body"> - <div class="paragraph"> -<p>最終的に社としての寄付まで漕ぎ着けられたのは、もちろん私の力ではなく役員の方々の決定によるものです。この場を借りて感謝申し上げます。</p> -</div> - </div> -</section> + <section id="section--_はじめに"> + <h2><a href="#section--_はじめに">はじめに</a></h2> + <p> + <em role="strong">注: これは私個人の意見であり、所属する組織を代表するものではありません。</em> + </p> + + <p> + 先日、私の勤める<a xl:href="https://www.dgcircus.com/">デジタルサーカス株式会社</a>が<a xl:href="https://opencollective.com/phpfoundation">PHP Foundation</a>へ $2,000 の寄付をおこないました。 + </p> + + <p> + 記事:<a xl:href="https://www.dgcircus.com/news/581">https://www.dgcircus.com/news/581</a> + </p> + + <p> + 本件を社内でしつこく推進した1人として、推進の理由等を書き残しておきます。 + </p> + </section> + + <section id="section--_なぜ"> + <h2><a href="#section--_なぜ">なぜ?</a></h2> + <p> + 組織としての寄付理由は前掲した記事に譲るとして、ここでは、私が社内でこの件を推進した理由について書くことにします。 + </p> + + <p> + 当時の考えを端的にまとめた社内チャットの投稿があったので、それを引用します: + </p> + + <blockquote> + <p> + 結局これを通したい (私の中での) 最大の理由が、「自分の勤める会社が、これをやる会社であってほしい」というのがあり、↑にしても、感情ベースの理由しか出せていないというのが説得力に欠けている理由なのだと思いますが、寄付の報告が流れてきたり、OSS のフリーライドの話が流れてきたりするたびに、自尊心が毀損される、というか (これは大袈裟すぎる表現で、実際にはそこまで明確に傷ついているわけではありませんが)。 + </p> + + <p> + 追記: 「肩身が狭くなる」というのがより適切でした。 + </p> + </blockquote> + + <p> + ※文中の「↑にしても」は、ここに載せていない別の投稿を指しています。 + </p> + + <p> + OSS を金銭的に支援したり、技術カンファレンスへ協賛したり (あるいは<a xl:href="https://twitter.com/tomzoh">CTO</a>がカンファレンスを年2で主催したり:<a xl:href="https://iosdc.jp">iOSDC</a><a xl:href="https://phperkaigi.jp">PHPerKaigi</a>) といった行為は、コミュニティへの貢献であると同時に、社員に対する精神的福利厚生でもあると言えるでしょう (知らんけど)。これらは、技術や技術者を大切にする組織である、ということの、対外的にも対内的にも強力なメッセージなのです。 + </p> + + <p> + 以上が、私が社内で寄付の件を進めた (かなり私的な) 理由です。 + </p> + </section> + + <section id="section--_おわりに"> + <h2><a href="#section--_おわりに">おわりに</a></h2> + <p> + 最終的に社としての寄付まで漕ぎ着けられたのは、もちろん私の力ではなく役員の方々の決定によるものです。この場を借りて感謝申し上げます。 + </p> + </section> </div> - </article> </main> <footer class="footer"> diff --git a/public/posts/2022-09-29/write-fizzbuzz-in-php-2-letters-per-line/index.html b/public/posts/2022-09-29/write-fizzbuzz-in-php-2-letters-per-line/index.html index 5a9bce7..53be0ae 100644 --- a/public/posts/2022-09-29/write-fizzbuzz-in-php-2-letters-per-line/index.html +++ b/public/posts/2022-09-29/write-fizzbuzz-in-php-2-letters-per-line/index.html @@ -4,18 +4,14 @@ <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="author" content="nsfisis"> - <meta name="copyright" content="© nsfisis"> + <meta name="copyright" content="© 2022 nsfisis"> <meta name="description" content="PHP で、fizzbuzz を書いた。ただし、1行あたりに使える文字数は2文字まで。"> <meta name="keywords" content="PHP"> <link rel="icon" type="image/svg+xml" href="/favicon.svg"> - <title>【PHP】fizzbuzz を書く。1行あたり2文字で。 | REPL: Rest-Eat-Program Loop</title> - - <link rel="stylesheet" href="/hl.css?208c52e3b7c9db1cad782c5d30b4698f"> - - <link rel="stylesheet" href="/style.css?779b1a3debcaeba619f6fe500e93d525"> - - <link rel="stylesheet" href="/custom.css?a649ea3528d4b626fb636505d94c1144"> - + <title>【PHP】 fizzbuzz を書く。1行あたり2文字で。 | REPL: Rest-Eat-Program Loop</title> + <link rel="stylesheet" href="/hl.css?h=208c52e3b7c9db1cad782c5d30b4698f"> + <link rel="stylesheet" href="/style.css?h=779b1a3debcaeba619f6fe500e93d525"> + <link rel="stylesheet" href="/custom.css?h=a649ea3528d4b626fb636505d94c1144"> </head> <body class="single"> <header class="header"> @@ -28,843 +24,763 @@ <main class="main"> <article class="post-single"> <header class="post-header"> - <h1 class="post-title">【PHP】fizzbuzz を書く。1行あたり2文字で。</h1> - - <ul class="post-tags"> - - <li class="tag"> - <a href="/tags/php/">PHP</a> - </li> - - </ul> - + <h1 class="post-title">【PHP】 fizzbuzz を書く。1行あたり2文字で。</h1> + <ul class="post-tags"> + <li class="tag"> + <a href="/tags/php">PHP</a> + </li> + </ul> </header> <div class="post-content"> <section> <h2 id="changelog">更新履歴</h2> <ol> + <li class="revision"> + <time datetime="2022-09-28">2022-09-28</time>: 公開 + </li> + <li class="revision"> + <time datetime="2022-09-29">2022-09-29</time>: 小さな文言の修正・変更 + </li> + </ol> + </section> + <section id="section--_記事の構成について"> + <h2><a href="#section--_記事の構成について">記事の構成について</a></h2> + <p> + この記事は、普通の fizzbuzz を徐々に変形して最終形にしていく、という構成で書かれている。最終形を見てどのような仕組みで動いているのか解読してから解説を読みたい、というかたがいれば、<a xl:href="https://gist.github.com/nsfisis/04c227d5a419867472a0b23a83ad2919#file-fizzbuzz-php-2-letters-per-line-and-supports-php-8-x-without-warnings">このページ</a>にソースコードがあるので、そちらを先に見てほしい。 + </p> + </section> + + <section id="section--_レギュレーション"> + <h2><a href="#section--_レギュレーション">レギュレーション</a></h2> + <p> + PHP で、次のような制約の下に fizzbuzz を書いた。 + </p> + + <ul> + <li> + <p> + 1行あたりの文字数は2文字までに収めること (ただし<code><?php</code>タグは除く) + </p> + + <ul> + <li> + <p> + 厳密な定義:<code><?php</code>タグ以降のソースコードが、2 byte ごとに ラインフィード (LF) で区切られること + </p> + </li> + </ul> + </li> - <li class="revision"> - <time datetime="2022-09-28">2022-09-28</time>: 公開 - </li> + <li> + <p> + スペースやタブを使用しないこと + </p> + </li> - <li class="revision"> - <time datetime="2022-09-29">2022-09-29</time>: 小さな文言の修正・変更 - </li> + <li> + <p> + ループのアンロールをしないこと + </p> + + <ul> + <li> + <p> + 100 回ループの代わりに 100 回コードをコピペ、というのは禁止 + </p> + </li> + </ul> + </li> - </ol> + <li> + <p> + PHP 7.4〜8.1 で動作すること + </p> + </li> + + <li> + <p> + 実行時に Notice や Warning が出ないこと + </p> + </li> + + <li> + <p> + 標準的なインストール構成の PHP で実現できること (デフォルトで有効になっていない拡張等を使わないこと) + </p> + </li> + </ul> + + <p> + 備考: PHP には<code>short_open_tag</code>というオプションがあり、これを有効にするとファイル冒頭の<code><?php</code>の代わりに<code><?</code>を使うことができ、文字どおり1行2文字で書ける。ただ、このオプションはデフォルト off になっている環境が多いようなので、今回は使わないことにした。 + </p> </section> - <section class="section-1"> - <h2 id="" class="section-header"> - - 記事の構成について - - </h2> - <div class="section-body"> - <div class="paragraph"> -<p>この記事は、普通の fizzbuzz -を徐々に変形して最終形にしていく、という構成で書かれている。最終形を見てどのような仕組みで動いているのか解読してから解説を読みたい、というかたがいれば、 -<a href="https://gist.github.com/nsfisis/04c227d5a419867472a0b23a83ad2919#file-fizzbuzz-php-2-letters-per-line-and-supports-php-8-x-without-warnings">このページ</a> -にソースコードがあるので、そちらを先に見てほしい。</p> -</div> - </div> -</section> -<section class="section-1"> - <h2 id="" class="section-header"> - - レギュレーション - - </h2> - <div class="section-body"> - <div class="paragraph"> -<p>PHP で、次のような制約の下に fizzbuzz を書いた。</p> -</div> -<div class="ulist"> -<ul> -<li> -<p>1行あたりの文字数は2文字までに収めること (ただし <code><?php</code> タグは除く)</p> -<div class="ulist"> -<ul> -<li> -<p>厳密な定義: <code><?php</code> タグ以降のソースコードが、2 byte ごとに -ラインフィード (LF) で区切られること</p> -</li> -</ul> -</div> -</li> -<li> -<p>スペースやタブを使用しないこと</p> -</li> -<li> -<p>ループのアンロールをしないこと</p> -<div class="ulist"> -<ul> -<li> -<p>100 回ループの代わりに 100 回コードをコピペ、というのは禁止</p> -</li> -</ul> -</div> -</li> -<li> -<p>PHP 7.4〜8.1 で動作すること</p> -</li> -<li> -<p>実行時に Notice や Warning が出ないこと</p> -</li> -<li> -<p>標準的なインストール構成の PHP で実現できること -(デフォルトで有効になっていない拡張等を使わないこと)</p> -</li> -</ul> -</div> -<div class="paragraph"> -<p>備考: PHP には <code>short_open_tag</code> -というオプションがあり、これを有効にするとファイル冒頭の <code><?php</code> -の代わりに <code><?</code> -を使うことができ、文字どおり1行2文字で書ける。ただ、このオプションはデフォルト -off になっている環境が多いようなので、今回は使わないことにした。</p> -</div> - </div> -</section> -<section class="section-1"> - <h2 id="" class="section-header"> - - 主な障害 - - </h2> - <div class="section-body"> - <div class="paragraph"> -<p>1行あたりの文字数など、適当に改行を挟めばいいだけではないのか?</p> -</div> -<div class="paragraph"> -<p>特に、C言語でこのような試みをおこなったことがあるかたならそう思うだろう。事実、Cでのこの制約はほとんど無意味に等しい。</p> -</div> -<div id="source." class="listingblock"> -<div class="content"> -<pre class="rouge highlight"><code data-lang="c"><span class="cp">#\ -i\ -n\ -c\ -l\ -u\ -d\ -e\ -<\ -s\ -t\ -d\ -i\ -o\ -.\ -h\ ->\ -</span><span class="cm">/* -*/</span><span class="cp"> -</span><span class="n">i</span>\ -<span class="n">n</span>\ -<span class="n">t</span>\ -<span class="cm">/* -*/</span> -<span class="n">m</span>\ -<span class="n">a</span>\ -<span class="n">i</span>\ -<span class="n">n</span><span class="p">(</span> -<span class="p">){</span> -<span class="n">f</span>\ -<span class="n">o</span>\ -<span class="n">r</span><span class="p">(</span> -<span class="n">i</span>\ -<span class="n">n</span>\ -<span class="n">t</span>\ -<span class="cm">/* -*/</span> -<span class="n">i</span><span class="o">=</span> -<span class="mi">1</span><span class="p">;</span> -<span class="n">i</span><span class="o"><</span> -<span class="mi">1</span>\ -<span class="mi">0</span>\ -<span class="mi">0</span><span class="p">;</span> -<span class="n">i</span>\ -<span class="o">+</span>\ -<span class="o">+</span><span class="p">)</span> -<span class="k">if</span> -<span class="p">(</span><span class="n">i</span> -<span class="o">%</span>\ -<span class="mi">15</span> -<span class="o">==</span> -<span class="mi">0</span><span class="p">)</span> -<span class="n">p</span>\ -<span class="n">r</span>\ -<span class="n">i</span>\ -<span class="n">n</span>\ -<span class="n">t</span>\ -<span class="n">f</span><span class="p">(</span> -<span class="s">"\ -F\ -i\ -z\ -z\ -B\ -u\ -z\ -z\ -%\ -c\ -"</span><span class="p">,</span> -<span class="mi">10</span> -<span class="p">);</span> + + <section id="section--_主な障害"> + <h2><a href="#section--_主な障害">主な障害</a></h2> + <p> + 1行あたりの文字数など、適当に改行を挟めばいいだけではないのか? + </p> + + <p> + 特に、C言語でこのような試みをおこなったことがあるかたならそう思うだろう。事実、Cでのこの制約はほとんど無意味に等しい。 + </p> + + <pre language="c" linenumbering="unnumbered"> + <code>#\ + 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 + ); -<span class="cm">/* あとは同じように普通のプログラムを変形するだけなので省略 */</span></code></pre> -</div> -</div> -<div class="paragraph"> -<p>バックスラッシュを使った行継続がトークンを区切らない、というのがポイントだ。</p> -</div> -<div class="paragraph"> -<p>さて、PHP -ではそもそもバックスラッシュを行継続に使うことができない。これにより、「3文字以上からなるトークンが一切使えない」という制約が課される。例えば、<code>echo</code> -で出力することや、<code>for</code> でループすること、<code>new</code> -でインスタンスを生成することができない。特に、出力は fizzbuzz -をどんなアルゴリズムで実装しようとおこなわなければならないので、できないのは致命的である。</p> -</div> -<div class="paragraph"> -<p>当然、名前が3文字以上ある関数も使えない。なお、標準 PHP -の範囲内において、名前が 2文字以下の関数は以下のとおりである:</p> -</div> -<div class="ulist"> -<ul> -<li> -<p><code>_</code>: <code>gettext</code> のエイリアス</p> -</li> -<li> -<p><code>dl</code>: 拡張モジュールをロードする</p> -</li> -<li> -<p><code>pi</code>: 円周率を返す</p> -</li> -</ul> -</div> -<div class="paragraph"> -<p>(環境によって多少は変わるかも)</p> -</div> -<div class="paragraph"> -<p>2文字の関数を定義しまくった拡張モジュールを用意しておいて <code>dl()</code> -で読み込む行為は、レギュレーションで定めた</p> -</div> -<div class="quoteblock"> -<blockquote> -<div class="ulist"> -<ul> -<li> -<p>標準的なインストール構成の PHP で実現できること -(デフォルトで有効になっていない拡張等を使わないこと)</p> -</li> -</ul> -</div> -</blockquote> -</div> -<div class="paragraph"> -<p>に反する -(というより、「それだとおもしろくもなんともないので、このルールを足した」というのが正しい)。</p> -</div> -<div class="paragraph"> -<p>また、2文字だと文字列がまともに書けないのも辛い。<code>''</code> だけで -2文字使うので、「1文字の文字列リテラル」というものを書くことができない。PHP -では文字列リテラル中に生の改行が書けるので</p> -</div> -<div id="source." class="listingblock"> -<div class="content"> -<pre class="rouge highlight"><code data-lang="php"><span class="nv">$a</span> -<span class="o">=</span><span class="s1">' -a'</span> -<span class="p">;;</span></code></pre> -</div> -</div> -<div class="paragraph"> -<p>とすると <code>$a</code> は <code>"\na"</code> になるのだが、余計な改行が入ってしまう。</p> -</div> -<div class="paragraph"> -<p>これらの障害をどのように乗り越えるのか、次節から見ていく。</p> -</div> - </div> -</section> -<section class="section-1"> - <h2 id="" class="section-header"> - - 解説 - - </h2> - <div class="section-body"> - <section class="section-2"> - <h3 id="" class="section-header"> - - 普通の (?) fizzbuzz - - </h3> - <div class="section-body"> - <div class="paragraph"> -<p>まずは普通に書くとしよう。</p> -</div> -<div class="literalblock"> -<div class="content"> -<pre><?php + /* あとは同じように普通のプログラムを変形するだけなので省略 */</code> + </pre> + + <p> + バックスラッシュを使った行継続がトークンを区切らない、というのがポイントだ。 + </p> + + <p> + さて、PHP ではそもそもバックスラッシュを行継続に使うことができない。これにより、「3文字以上からなるトークンが一切使えない」という制約が課される。例えば、<code>echo</code>で出力することや、<code>for</code>でループすること、<code>new</code>でインスタンスを生成することができない。特に、出力は fizzbuzz をどんなアルゴリズムで実装しようとおこなわなければならないので、できないのは致命的である。 + </p> + + <p> + 当然、名前が3文字以上ある関数も使えない。なお、標準 PHP の範囲内において、名前が 2文字以下の関数は以下のとおりである: + </p> + + <ul> + <li> + <p> + <code>_</code>:<code>gettext</code>のエイリアス + </p> + </li> + + <li> + <p> + <code>dl</code>: 拡張モジュールをロードする + </p> + </li> + + <li> + <p> + <code>pi</code>: 円周率を返す + </p> + </li> + </ul> + + <p> + (環境によって多少は変わるかも) + </p> + + <p> + 2文字の関数を定義しまくった拡張モジュールを用意しておいて<code>dl()</code>で読み込む行為は、レギュレーションで定めた + </p> + + <blockquote> + <ul> + <li> + <p> + 標準的なインストール構成の PHP で実現できること (デフォルトで有効になっていない拡張等を使わないこと) + </p> + </li> + </ul> + </blockquote> + + <p> + に反する (というより、「それだとおもしろくもなんともないので、このルールを足した」というのが正しい)。 + </p> + + <p> + また、2文字だと文字列がまともに書けないのも辛い。<code>''</code>だけで 2文字使うので、「1文字の文字列リテラル」というものを書くことができない。PHP では文字列リテラル中に生の改行が書けるので + </p> + + <pre language="php" linenumbering="unnumbered"> + <code>$a +=' +a' +;;</code> + </pre> + + <p> + とすると<code>$a</code>は<code>"\na"</code>になるのだが、余計な改行が入ってしまう。 + </p> + + <p> + これらの障害をどのように乗り越えるのか、次節から見ていく。 + </p> + </section> + + <section id="section--_解説"> + <h2><a href="#section--_解説">解説</a></h2> + <section id="section--_普通の_fizzbuzz"> + <h3><a href="#section--_普通の_fizzbuzz">普通の (?) fizzbuzz</a></h3> + <p> + まずは普通に書くとしよう。 + </p> + + <pre class="monospaced"> + <code><?php -for ($i = 1; $i < 100; $i++) { - echo (($i % 3 ? '' : 'Fizz') . ($i % 5 ? '' : 'Buzz') ?: $i) . "\n"; -}</pre> -</div> -</div> -<div class="paragraph"> -<p>素直に書いた fizzbuzz -とは言い難いが、このくらいは普通だということにしておかないと、この先がやっていられないので許してほしい。</p> -</div> - </div> -</section> -<section class="section-2"> - <h3 id="" class="section-header"> - - <code>for</code> の排除 - - </h3> - <div class="section-body"> - <div class="paragraph"> -<p><code>for</code> -は、3文字もある長いキーワードである。こんなものは使えない。<code>array_</code> -系の関数を使って、適当に置き換えるとしよう。</p> -</div> -<div id="source." class="listingblock"> -<div class="content"> -<pre class="rouge highlight"><code data-lang="php"><span class="o"><?</span><span class="n">php</span> + for ($i = 1; $i < 100; $i++) { + echo (($i % 3 ? '' : 'Fizz') . ($i % 5 ? '' : 'Buzz') ?: $i) . "\n"; + }</code> + </pre> + + <p> + 素直に書いた fizzbuzz とは言い難いが、このくらいは普通だということにしておかないと、この先がやっていられないので許してほしい。 + </p> + </section> + + <section id="section--_for_の排除"> + <h3><a href="#section--_for_の排除"><code>for</code>の排除</a></h3> + <p> + <code>for</code>は、3文字もある長いキーワードである。こんなものは使えない。<code>array_</code>系の関数を使って、適当に置き換えるとしよう。 + </p> + + <pre language="php" linenumbering="unnumbered"> + <code><?php -<span class="nv">$s</span> <span class="o">=</span> <span class="nb">range</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">100</span><span class="p">);</span> -<span class="nb">array_walk</span><span class="p">(</span> - <span class="nv">$s</span><span class="p">,</span> - <span class="k">fn</span><span class="p">(</span><span class="nv">$i</span><span class="p">)</span> <span class="o">=></span> - <span class="nb">printf</span><span class="p">(((</span><span class="nv">$i</span> <span class="o">%</span> <span class="mi">3</span> <span class="o">?</span> <span class="s1">''</span> <span class="o">:</span> <span class="s1">'Fizz'</span><span class="p">)</span> <span class="mf">.</span> <span class="p">(</span><span class="nv">$i</span> <span class="o">%</span> <span class="mi">5</span> <span class="o">?</span> <span class="s1">''</span> <span class="o">:</span> <span class="s1">'Buzz'</span><span class="p">)</span> <span class="o">?:</span> <span class="nv">$i</span><span class="p">)</span> <span class="mf">.</span> <span class="s2">"</span><span class="se">\n</span><span class="s2">"</span><span class="p">),</span> -<span class="p">);</span></code></pre> -</div> -</div> -<div class="paragraph"> -<p><code>array_walk</code> や <code>range</code>、<code>printf</code> といった <code>for</code> -よりも長いトークンが現れてしまったが、これは次節で直すことにする。なお、<code>echo</code> -は文 (statement) であり式 (expression) ではないので、式である <code>printf</code> -に置き換えた。</p> -</div> - </div> -</section> -<section class="section-2"> - <h3 id="" class="section-header"> - - 関数呼び出しの短縮 - - </h3> - <div class="section-body"> - <div class="paragraph"> -<p><code>range</code>、<code>array_walk</code>、<code>printf</code> -は長すぎるのでどうにかせねばならない。ここで、PHP -の可変関数を使う。可変関数とは、関数名が文字列として入った変数を経由して、関数を呼び出す機能である。</p> -</div> -<div id="source." class="listingblock"> -<div class="content"> -<pre class="rouge highlight"><code data-lang="php"><span class="o"><?</span><span class="n">php</span> + $s = range(1, 100); + array_walk( + $s, + fn($i) => + printf((($i % 3 ? '' : 'Fizz') . ($i % 5 ? '' : 'Buzz') ?: $i) . "\n"), + );</code> + </pre> + + <p> + <code>array_walk</code>や<code>range</code>、<code>printf</code>といった<code>for</code>よりも長いトークンが現れてしまったが、これは次節で直すことにする。なお、<code>echo</code>は文 (statement) であり式 (expression) ではないので、式である<code>printf</code>に置き換えた。 + </p> + </section> + + <section id="section--_関数呼び出しの短縮"> + <h3><a href="#section--_関数呼び出しの短縮">関数呼び出しの短縮</a></h3> + <p> + <code>range</code>、<code>array_walk</code>、<code>printf</code>は長すぎるのでどうにかせねばならない。ここで、PHP の可変関数を使う。可変関数とは、関数名が文字列として入った変数を経由して、関数を呼び出す機能である。 + </p> + + <pre language="php" linenumbering="unnumbered"> + <code><?php -<span class="nv">$r</span> <span class="o">=</span> <span class="s1">'range'</span><span class="p">;</span> -<span class="nv">$w</span> <span class="o">=</span> <span class="s1">'array_walk'</span><span class="p">;</span> -<span class="nv">$p</span> <span class="o">=</span> <span class="s1">'printf'</span><span class="p">;</span> + $r = 'range'; + $w = 'array_walk'; + $p = 'printf'; -<span class="nv">$s</span> <span class="o">=</span> <span class="nv">$r</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">100</span><span class="p">);</span> -<span class="nv">$w</span><span class="p">(</span> - <span class="nv">$s</span><span class="p">,</span> - <span class="k">fn</span><span class="p">(</span><span class="nv">$i</span><span class="p">)</span> <span class="o">=></span> - <span class="nv">$p</span><span class="p">(((</span><span class="nv">$i</span> <span class="o">%</span> <span class="mi">3</span> <span class="o">?</span> <span class="s1">''</span> <span class="o">:</span> <span class="s1">'Fizz'</span><span class="p">)</span> <span class="mf">.</span> <span class="p">(</span><span class="nv">$i</span> <span class="o">%</span> <span class="mi">5</span> <span class="o">?</span> <span class="s1">''</span> <span class="o">:</span> <span class="s1">'Buzz'</span><span class="p">)</span> <span class="o">?:</span> <span class="nv">$i</span><span class="p">)</span> <span class="mf">.</span> <span class="s2">"</span><span class="se">\n</span><span class="s2">"</span><span class="p">),</span> -<span class="p">);</span></code></pre> -</div> -</div> -<div class="paragraph"> -<p>これで関数を呼び出している所は短くなった。では、<code>$r</code> や <code>$w</code> や -<code>$p</code>、また <code>'Fizz'</code> や <code>'Buzz'</code> はどうやって -1行2文字に収めるのか。次のテクニックへ移ろう。</p> -</div> - </div> -</section> -<section class="section-2"> - <h3 id="" class="section-header"> - - 余談: PHP 8.x で動作しなくてもいいなら - - </h3> - <div class="section-body"> - <div class="paragraph"> -<p>今回使ったテクニックを説明する前に、余談として、文字列リテラルの短縮法として今回採用しなかったものを紹介する。</p> -</div> -<div class="quoteblock"> -<blockquote> -<div class="ulist"> -<ul> -<li> -<p>PHP 7.4〜8.1 で動作すること</p> -</li> -</ul> -</div> -</blockquote> -</div> -<div class="paragraph"> -<p>というルールがない場合、「未定義の定数が評価された場合、その定数の名前が値になる」という -PHP 7.x までの仕様が利用できる。例えば、 <code>Fizz</code> -という文字列が欲しければ、次のようにする。</p> -</div> -<div id="source." class="listingblock"> -<div class="content"> -<pre class="rouge highlight"><code data-lang="php"><span class="nv">$f</span> -<span class="o">=</span><span class="nc">F</span> -<span class="mf">.</span><span class="n">i</span> -<span class="mf">.</span><span class="n">z</span> -<span class="mf">.</span><span class="n">z</span> -<span class="p">;;</span></code></pre> -</div> -</div> -<div class="paragraph"> -<p>こうして簡単に文字列を作れる。なお、この仕様は 7.x -時点でも警告を受けるので、<code>@</code> 演算子を使って抑制してやるとよい。</p> -</div> -<div id="source." class="listingblock"> -<div class="content"> -<pre class="rouge highlight"><code data-lang="php"><span class="nv">$f</span> -<span class="o">=@</span> -<span class="nc">F</span><span class="mf">.</span> -<span class="o">@</span><span class="n">i</span> -<span class="mf">.</span><span class="c1">#</span> -<span class="o">@</span><span class="n">z</span> -<span class="mf">.</span><span class="c1">#</span> -<span class="o">@</span><span class="n">z</span> -<span class="p">;;</span></code></pre> -</div> -</div> -<div class="paragraph"> -<p>むしろ、このことがわかっていたからこそ PHP 8.x -での動作を要件に課したところがある。</p> -</div> - </div> -</section> -<section class="section-2"> - <h3 id="" class="section-header"> - - 文字列リテラルの短縮 - - </h3> - <div class="section-body"> - <div class="paragraph"> -<p>実際に使った手法の説明に移る。</p> -</div> -<div class="paragraph"> -<p>ずばり、文字列同士のビット演算を使う。PHP では、文字列同士でビット演算 -(<code>&</code>、<code>|</code>、<code>^</code>) -をした場合、文字列の各バイトごとに指定したビット演算がなされ、それを結合したものが演算結果となる。</p> -</div> -<div id="source." class="listingblock"> -<div class="content"> -<pre class="rouge highlight"><code data-lang="php"><span class="nv">$a</span> <span class="o">=</span> <span class="s2">"12345"</span><span class="p">;</span> -<span class="nv">$b</span> <span class="o">=</span> <span class="s2">"world"</span><span class="p">;</span> + $s = $r(1, 100); + $w( + $s, + fn($i) => + $p((($i % 3 ? '' : 'Fizz') . ($i % 5 ? '' : 'Buzz') ?: $i) . "\n"), + );</code> + </pre> + + <p> + これで関数を呼び出している所は短くなった。では、<code>$r</code>や<code>$w</code>や<code>$p</code>、また<code>'Fizz'</code>や<code>'Buzz'</code>はどうやって 1行2文字に収めるのか。次のテクニックへ移ろう。 + </p> + </section> + + <section id="section--_余談_php_8_x_で動作しなくてもいいなら"> + <h3><a href="#section--_余談_php_8_x_で動作しなくてもいいなら">余談: PHP 8.x で動作しなくてもいいなら</a></h3> + <p> + 今回使ったテクニックを説明する前に、余談として、文字列リテラルの短縮法として今回採用しなかったものを紹介する。 + </p> + + <blockquote> + <ul> + <li> + <p> + PHP 7.4〜8.1 で動作すること + </p> + </li> + </ul> + </blockquote> + + <p> + というルールがない場合、「未定義の定数が評価された場合、その定数の名前が値になる」という PHP 7.x までの仕様が利用できる。例えば、<code>Fizz</code>という文字列が欲しければ、次のようにする。 + </p> + + <pre language="php" linenumbering="unnumbered"> + <code>$f +=F +.i +.z +.z +;;</code> + </pre> + + <p> + こうして簡単に文字列を作れる。なお、この仕様は 7.x 時点でも警告を受けるので、<code>@</code>演算子を使って抑制してやるとよい。 + </p> + + <pre language="php" linenumbering="unnumbered"> + <code>$f +=@ +F. +@i +.# +@z +.# +@z +;;</code> + </pre> + + <p> + むしろ、このことがわかっていたからこそ PHP 8.x での動作を要件に課したところがある。 + </p> + </section> + + <section id="section--_文字列リテラルの短縮"> + <h3><a href="#section--_文字列リテラルの短縮">文字列リテラルの短縮</a></h3> + <p> + 実際に使った手法の説明に移る。 + </p> + + <p> + ずばり、文字列同士のビット演算を使う。PHP では、文字列同士でビット演算 (<code>&</code>、<code>|</code>、<code>^</code>) をした場合、文字列の各バイトごとに指定したビット演算がなされ、それを結合したものが演算結果となる。 + </p> + + <pre language="php" linenumbering="unnumbered"> + <code>$a = "12345"; +$b = "world"; -<span class="c1">// $a ^ $b は次のコードと同じ</span> -<span class="nv">$result</span> <span class="o">=</span> <span class="s1">''</span><span class="p">;</span> -<span class="k">for</span> <span class="p">(</span><span class="nv">$i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nv">$i</span> <span class="o"><</span> <span class="nb">min</span><span class="p">(</span><span class="nb">strlen</span><span class="p">(</span><span class="nv">$a</span><span class="p">),</span> <span class="nb">strlen</span><span class="p">(</span><span class="nv">$b</span><span class="p">));</span> <span class="nv">$i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span> - <span class="nv">$result</span> <span class="mf">.</span><span class="o">=</span> <span class="nv">$a</span><span class="p">[</span><span class="nv">$i</span><span class="p">]</span> <span class="o">^</span> <span class="nv">$b</span><span class="p">[</span><span class="nv">$i</span><span class="p">];</span> -<span class="p">}</span> +// $a ^ $b は次のコードと同じ +$result = ''; +for ($i = 0; $i < min(strlen($a), strlen($b)); $i++) { +$result .= $a[$i] ^ $b[$i]; +} -<span class="k">echo</span> <span class="nv">$result</span><span class="p">;</span> -<span class="c1">// => F]AXQ</span></code></pre> -</div> -</div> -<div class="paragraph"> -<p>これを踏まえ、次のコードを見てみよう。</p> -</div> -<div id="source." class="listingblock"> -<div class="content"> -<pre class="rouge highlight"><code data-lang="php"><span class="nv">$x</span> <span class="o">=</span> <span class="s2">"x</span><span class="se">\n</span><span class="s2">Om</span><span class="se">\n</span><span class="s2">"</span><span class="p">;</span> -<span class="nv">$y</span> <span class="o">=</span> <span class="s2">"</span><span class="se">\n</span><span class="s2">k!</span><span class="se">\n</span><span class="s2">o"</span><span class="p">;</span> -<span class="nv">$r</span> <span class="o">=</span> <span class="nv">$x</span> <span class="o">^</span> <span class="nv">$y</span><span class="p">;</span> -<span class="k">echo</span> <span class="s2">"</span><span class="nv">$r</span><span class="se">\n</span><span class="s2">"</span><span class="p">;</span></code></pre> -</div> -</div> -<div class="paragraph"> -<p>実行すると、<code>range</code> が表示される。さて、PHP -では文字列リテラル中に生の改行を直接書いてもよいのだった -(「主な障害」の節を参照のこと)。書きかえてみよう。</p> -</div> -<div id="source." class="listingblock"> -<div class="content"> -<pre class="rouge highlight"><code data-lang="php"><span class="nv">$x</span> -<span class="o">=</span><span class="s1">'x +echo $result; +// => F]AXQ</code> + </pre> + + <p> + これを踏まえ、次のコードを見てみよう。 + </p> + + <pre language="php" linenumbering="unnumbered"> + <code>$x = "x\nOm\n"; +$y = "\nk!\no"; +$r = $x ^ $y; +echo "$r\n";</code> + </pre> + + <p> + 実行すると、<code>range</code>が表示される。さて、PHP では文字列リテラル中に生の改行を直接書いてもよいのだった (「主な障害」の節を参照のこと)。書きかえてみよう。 + </p> + + <pre language="php" linenumbering="unnumbered"> + <code>$x +='x Om -'</span><span class="p">;</span> -<span class="nv">$y</span> -<span class="o">=</span><span class="s1">' +'; +$y +=' k! -o'</span> -<span class="p">;</span> +o' +; -<span class="nv">$r</span> <span class="o">=</span> <span class="nv">$x</span> <span class="o">^</span> <span class="nv">$y</span><span class="p">;</span> -<span class="k">echo</span> <span class="s2">"</span><span class="nv">$r</span><span class="se">\n</span><span class="s2">"</span><span class="p">;</span></code></pre> -</div> -</div> -<div class="paragraph"> -<p>さらに <code>#</code> を使って適当に調整すると、次のようになる。</p> -</div> -<div id="source." class="listingblock"> -<div class="content"> -<pre class="rouge highlight"><code data-lang="php"><span class="nv">$x</span> -<span class="o">=</span><span class="c1">#</span> -<span class="s1">'x +$r = $x ^ $y; +echo "$r\n";</code> + </pre> + + <p> + さらに<code>#</code>を使って適当に調整すると、次のようになる。 + </p> + + <pre language="php" linenumbering="unnumbered"> + <code>$x +=# +'x Om -'</span><span class="p">;</span> -<span class="nv">$y</span> -<span class="o">=</span><span class="s1">' +'; +$y +=' k! -o'</span> -<span class="p">;</span><span class="c1">#</span> -<span class="nv">$r</span> -<span class="o">=</span><span class="c1">#</span> -<span class="nv">$x</span> -<span class="o">^</span><span class="c1">#</span> -<span class="nv">$y</span> -<span class="p">;</span><span class="c1">#</span> +o' +;# +$r +=# +$x +^# +$y +;# -<span class="k">echo</span> <span class="s2">"</span><span class="nv">$r</span><span class="se">\n</span><span class="s2">"</span><span class="p">;</span></code></pre> -</div> -</div> -<div class="paragraph"> -<p>1行あたり2文字で、<code>range</code> -という文字列を生成することに成功した。他の必要な文字列にも、同様の処理をほどこす。</p> -</div> -<div class="paragraph"> -<p>備考: <code>Buzz</code> 中にある小文字の <code>u</code> は、このロジックだと non-printable -な文字になってしまう。ここまでのテクニックを駆使すれば回避するのはそう難しくないので、考えてみてほしい。</p> -</div> - </div> -</section> - </div> -</section> -<section class="section-1"> - <h2 id="" class="section-header"> - - 完成系 - - </h2> - <div class="section-body"> - <div class="paragraph"> -<p>完成したものがこちら。</p> -</div> -<div id="source." class="listingblock"> -<div class="content"> -<pre class="rouge highlight"><code data-lang="php"><span class="o"><?</span><span class="n">php</span> +echo "$r\n";</code> + </pre> + + <p> + 1行あたり2文字で、<code>range</code>という文字列を生成することに成功した。他の必要な文字列にも、同様の処理をほどこす。 + </p> + + <p> + 備考:<code>Buzz</code>中にある小文字の<code>u</code>は、このロジックだと non-printable な文字になってしまう。ここまでのテクニックを駆使すれば回避するのはそう難しくないので、考えてみてほしい。 + </p> + </section> + </section> + + <section id="section--_完成系"> + <h2><a href="#section--_完成系">完成系</a></h2> + <p> + 完成したものがこちら。 + </p> + + <pre language="php" linenumbering="unnumbered"> + <code><?php -<span class="nv">$x</span> -<span class="o">=</span><span class="c1">#</span> -<span class="s1">'i -S'</span> -<span class="p">;;</span> -<span class="nv">$y</span> -<span class="o">=</span><span class="s1">' -b! -'</span><span class="p">;</span> -<span class="nv">$c</span> -<span class="o">=</span><span class="c1">#</span> -<span class="nv">$x</span> -<span class="o">^</span><span class="c1">#</span> -<span class="nv">$y</span> -<span class="p">;</span><span class="c1">#</span> -<span class="nv">$x</span> -<span class="o">=</span><span class="c1">#</span> -<span class="s1">'x -Om -'</span><span class="p">;</span> -<span class="nv">$y</span> -<span class="o">=</span><span class="s1">' -k! -o'</span> -<span class="p">;</span><span class="c1">#</span> -<span class="nv">$r</span> -<span class="o">=</span><span class="c1">#</span> -<span class="nv">$x</span> -<span class="o">^</span><span class="c1">#</span> -<span class="nv">$y</span> -<span class="p">;</span><span class="c1">#</span> -<span class="nv">$x</span> -<span class="o">=</span><span class="c1">#</span> -<span class="s1">'k -Sk -~} -Ma -'</span><span class="p">;</span> -<span class="nv">$y</span> -<span class="o">=</span><span class="s1">' -x! -s! -k! -'</span><span class="p">;</span> -<span class="nv">$w</span> -<span class="o">=</span><span class="c1">#</span> -<span class="nv">$x</span> -<span class="o">^</span><span class="c1">#</span> -<span class="nv">$y</span> -<span class="p">;</span><span class="c1">#</span> -<span class="nv">$x</span> -<span class="o">=</span><span class="c1">#</span> -<span class="s1">'z -Hd -G'</span> -<span class="p">;</span><span class="c1">#</span> -<span class="nv">$y</span> -<span class="o">=</span><span class="s1">' -x! -~! -'</span><span class="p">;</span> -<span class="nv">$p</span> -<span class="o">=</span><span class="c1">#</span> -<span class="nv">$x</span> -<span class="o">^</span><span class="c1">#</span> -<span class="nv">$y</span> -<span class="p">;</span><span class="c1">#</span> -<span class="nv">$x</span> -<span class="o">=</span><span class="c1">#</span> -<span class="s1">'L -[p -'</span><span class="p">;</span> -<span class="nv">$y</span> -<span class="o">=</span><span class="s1">' -c! -'</span><span class="p">;</span> -<span class="nv">$f</span> -<span class="o">=</span><span class="c1">#</span> -<span class="nv">$x</span> -<span class="o">^</span><span class="c1">#</span> -<span class="nv">$y</span> -<span class="p">;</span><span class="c1">#</span> -<span class="nv">$x</span> -<span class="o">=</span><span class="c1">#</span> -<span class="s1">'H -[p -'</span><span class="p">;</span> -<span class="nv">$y</span> -<span class="o">=</span><span class="s1">' -_! -'</span><span class="p">;</span> -<span class="nv">$b</span> -<span class="o">=</span><span class="c1">#</span> -<span class="nv">$x</span> -<span class="o">^</span><span class="c1">#</span> -<span class="nv">$y</span> -<span class="p">;</span><span class="c1">#</span> -<span class="nv">$b</span> -<span class="p">[</span><span class="mi">1</span> -<span class="p">]</span><span class="o">=</span> -<span class="nv">$c</span> -<span class="p">(</span><span class="c1">#</span> -<span class="mi">13</span> -<span class="o">*</span><span class="mi">9</span> -<span class="p">);</span> -<span class="nv">$s</span> -<span class="o">=</span><span class="c1">#</span> -<span class="nv">$r</span> -<span class="p">(</span><span class="mi">1</span> -<span class="p">,(</span> -<span class="mi">10</span> -<span class="o">**</span> -<span class="mi">2</span><span class="p">)</span> -<span class="p">);</span> -<span class="nv">$w</span> -<span class="p">(</span><span class="c1">#</span> -<span class="nv">$s</span> -<span class="p">,</span><span class="c1">#</span> -<span class="k">fn</span> -<span class="p">(</span><span class="c1">#</span> -<span class="nv">$i</span> -<span class="p">)</span><span class="c1">#</span> -<span class="o">=></span> -<span class="nv">$p</span> -<span class="p">((</span> -<span class="p">(</span><span class="c1">#</span> -<span class="nv">$i</span> -<span class="o">%</span><span class="mi">3</span> -<span class="o">?</span><span class="c1">#</span> -<span class="s1">''</span> -<span class="o">:</span><span class="c1">#</span> -<span class="nv">$f</span> -<span class="p">)</span><span class="mf">.</span> -<span class="p">(</span><span class="c1">#</span> -<span class="nv">$i</span> -<span class="o">%</span><span class="mi">5</span> -<span class="o">?</span><span class="c1">#</span> -<span class="s1">''</span> -<span class="o">:</span><span class="c1">#</span> -<span class="nv">$b</span> -<span class="p">)</span><span class="o">?</span> -<span class="o">:</span><span class="c1">#</span> -<span class="nv">$i</span> -<span class="p">)</span><span class="c1">#</span> -<span class="mf">.</span><span class="s1">' -'</span><span class="p">)</span> -<span class="p">);</span></code></pre> -</div> -</div> - </div> -</section> -<section class="section-1"> - <h2 id="" class="section-header"> - - 感想など - - </h2> - <div class="section-body"> - <div class="paragraph"> -<p>PHP は、スクリプト言語の中だとシンタックスシュガーが少ない -(体感)。この挑戦は不可能に思われたが、PHP -マニュアルとにらめっこしていたらなんとかなった。</p> -</div> -<div class="paragraph"> -<p>みんなもプログラムを細長くしよう。</p> -</div> - </div> -</section> -<section class="section-1"> - <h2 id="" class="section-header"> - - 余談2: 別解 - - </h2> - <div class="section-body"> - <div class="paragraph"> -<p>PHP では、バッククォートを使ってシェルを呼び出せる。これは <code>shell_exec</code> -関数と等価である。さて、PHP -ではバックスラッシュによる行継続が使えないと書いたが、シェルでは使える -(当然だが、呼び出されるシェルに依存する。Bash -なら大丈夫だろう。知らんけど)。</p> -</div> -<div id="source." class="listingblock"> -<div class="content"> -<pre class="rouge highlight"><code data-lang="php"><span class="o"><?</span><span class="n">php</span> + $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 + )# + .' + ') + );</code> + </pre> + </section> + + <section id="section--_感想など"> + <h2><a href="#section--_感想など">感想など</a></h2> + <p> + PHP は、スクリプト言語の中だとシンタックスシュガーが少ない (体感)。この挑戦は不可能に思われたが、PHP マニュアルとにらめっこしていたらなんとかなった。 + </p> + + <p> + みんなもプログラムを細長くしよう。 + </p> + </section> + + <section id="section--_余談2_別解"> + <h2><a href="#section--_余談2_別解">余談2: 別解</a></h2> + <p> + PHP では、バッククォートを使ってシェルを呼び出せる。これは<code>shell_exec</code>関数と等価である。さて、PHP ではバックスラッシュによる行継続が使えないと書いたが、シェルでは使える (当然だが、呼び出されるシェルに依存する。Bash なら大丈夫だろう。知らんけど)。 + </p> + + <pre language="php" linenumbering="unnumbered"> + <code><?php -<span class="nb">printf</span><span class="p">(</span><span class="err">`</span> -<span class="n">e</span><span class="err">\</span> -<span class="n">c</span><span class="err">\</span> -<span class="n">h</span><span class="err">\</span> -<span class="n">o</span><span class="err">\</span> - <span class="err">\</span> -<span class="mi">1</span><span class="err">\</span> -<span class="mi">2</span><span class="err">\</span> -<span class="mi">3</span><span class="err">\</span> -<span class="err">`</span><span class="p">);</span></code></pre> -</div> -</div> -<div class="paragraph"> -<p>なお、ここでは簡単のため出力に <code>printf</code> をそのまま使っているが、実際には -<code>printf</code> という文字列を合成して可変関数で呼び出す。</p> -</div> -<div class="paragraph"> -<p>ただし、これでは</p> -</div> -<div class="quoteblock"> -<blockquote> -<div class="ulist"> -<ul> -<li> -<p>スペースやタブを使用しないこと</p> -</li> -</ul> -</div> -</blockquote> -</div> -<div class="paragraph"> -<p>に違反してしまう。スペースが使えないと引数とコマンドを区切れない。これは困った。</p> -</div> -<div class="paragraph"> -<p>もうこれ以上は不可能だと思っていたのだが、この記事の執筆中に解決する方法を思いついたので載せておく。</p> -</div> -<div id="source." class="listingblock"> -<div class="content"> -<pre class="rouge highlight"><code data-lang="php"><span class="o"><?</span><span class="n">php</span> + printf(` + e\ + c\ + h\ + o\ + \ + 1\ + 2\ + 3\ + `);</code> + </pre> + + <p> + なお、ここでは簡単のため出力に<code>printf</code>をそのまま使っているが、実際には<code>printf</code>という文字列を合成して可変関数で呼び出す。 + </p> + + <p> + ただし、これでは + </p> + + <blockquote> + <ul> + <li> + <p> + スペースやタブを使用しないこと + </p> + </li> + </ul> + </blockquote> + + <p> + に違反してしまう。スペースが使えないと引数とコマンドを区切れない。これは困った。 + </p> + + <p> + もうこれ以上は不可能だと思っていたのだが、この記事の執筆中に解決する方法を思いついたので載せておく。 + </p> + + <pre language="php" linenumbering="unnumbered"> + <code><?php -<span class="nv">$c</span> <span class="o">=</span> <span class="s1">'chr'</span><span class="p">;</span> +$c = 'chr'; -<span class="err">$</span><span class="p">{</span> -<span class="s1">'_ -'</span><span class="p">}</span> -<span class="o">=</span><span class="c1">#</span> -<span class="nv">$c</span> -<span class="p">(</span><span class="c1">#</span> -<span class="mi">32</span> -<span class="p">)</span><span class="mf">.</span> -<span class="nv">$c</span> -<span class="p">(</span><span class="c1">#</span> -<span class="mi">92</span> -<span class="p">);</span> +${ +'_ +'} +=# +$c +(# +32 +). +$c +(# +92 +); -<span class="nb">printf</span><span class="p">(</span><span class="err">`</span> -<span class="n">e</span><span class="err">\</span> -<span class="n">c</span><span class="err">\</span> -<span class="n">h</span><span class="err">\</span> -<span class="n">o</span><span class="err">\</span> -<span class="err">$</span><span class="p">{</span> -<span class="s1">'_ -'</span><span class="p">}</span> -<span class="mi">1</span><span class="err">\</span> -<span class="mi">2</span><span class="err">\</span> -<span class="mi">3</span><span class="err">\</span> -<span class="err">`</span><span class="p">);</span></code></pre> -</div> -</div> -<div class="paragraph"> -<p>先程と同じく、<code>chr</code> や <code>printf</code> を生成する部分は長くなるので省いた。</p> -</div> -<div class="literalblock"> -<div class="content"> -<pre>${ -'_ -'}</pre> -</div> -</div> -<div class="paragraph"> -<p>は変数で、中にはスペースとエスケープが入っている -(<code>chr(32) . chr(92)</code>)。シェルに渡されている文字列は次のようになる。</p> -</div> -<div class="literalblock"> -<div class="content"> -<pre>e\ +printf(` +e\ +c\ +h\ +o\ +${ +'_ +'} +1\ +2\ +3\ +`);</code> + </pre> + + <p> + 先程と同じく、<code>chr</code>や<code>printf</code>を生成する部分は長くなるので省いた。 + </p> + + <pre class="monospaced"> + <code>${ +'_ +'}</code> + </pre> + + <p> + は変数で、中にはスペースとエスケープが入っている (<code>chr(32) . chr(92)</code>)。シェルに渡されている文字列は次のようになる。 + </p> + + <pre class="monospaced"> + <code>e\ c\ h\ o\ - \ +\ 1\ 2\ -3\</pre> -</div> -</div> -<div class="paragraph"> -<p>これは、前掲したコマンドと同じだ。かくして、スペースを陽に書かずにシェルをおおよそ自由に扱えるようになった。Fizzbuzz -のワンライナーくらいすぐ書けるだろうから、あとはなんとかなるだろう -(試してないけど)。</p> -</div> -<div class="paragraph"> -<p>ということでこれは別解ということにしておく。</p> -</div> -<div class="paragraph"> -<p>ちなみに、PHP 8.2 からは、この記法で Warning が出るようになるようだ。</p> -</div> -<div class="literalblock"> -<div class="content"> -<pre>${ -'_ -'}</pre> -</div> -</div> -<div class="paragraph"> -<p>最新版で警告が出るというのも美しくないので、私としては本編の解法を推す。</p> -</div> - </div> -</section> +3\</code> + </pre> + + <p> + これは、前掲したコマンドと同じだ。かくして、スペースを陽に書かずにシェルをおおよそ自由に扱えるようになった。Fizzbuzz のワンライナーくらいすぐ書けるだろうから、あとはなんとかなるだろう (試してないけど)。 + </p> + + <p> + ということでこれは別解ということにしておく。 + </p> + + <p> + ちなみに、PHP 8.2 からは、この記法で Warning が出るようになるようだ。 + </p> + + <pre class="monospaced"> + <code>${ +'_ +'}</code> + </pre> + + <p> + 最新版で警告が出るというのも美しくないので、私としては本編の解法を推す。 + </p> + </section> </div> - </article> </main> <footer class="footer"> diff --git a/public/posts/2022-10-23/phperkaigi-2023-unused-token-quiz-1/index.html b/public/posts/2022-10-23/phperkaigi-2023-unused-token-quiz-1/index.html index 5184372..4801e5d 100644 --- a/public/posts/2022-10-23/phperkaigi-2023-unused-token-quiz-1/index.html +++ b/public/posts/2022-10-23/phperkaigi-2023-unused-token-quiz-1/index.html @@ -4,18 +4,14 @@ <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="author" content="nsfisis"> - <meta name="copyright" content="© nsfisis"> - <meta name="description" content="来年の PHPerKaigi 2023 でデジタルサーカス株式会社から出題予定のトークン問題のうち、 ボツになった問題を公開する (その 1)。"> + <meta name="copyright" content="© 2022 nsfisis"> + <meta name="description" content="来年の PHPerKaigi 2023 でデジタルサーカス株式会社から出題予定のトークン問題のうち、ボツになった問題を公開する (その 1)。"> <meta name="keywords" content="PHP,PHPerKaigi"> <link rel="icon" type="image/svg+xml" href="/favicon.svg"> <title>PHPerKaigi 2023: ボツになったトークン問題 その 1 | REPL: Rest-Eat-Program Loop</title> - - <link rel="stylesheet" href="/hl.css?208c52e3b7c9db1cad782c5d30b4698f"> - - <link rel="stylesheet" href="/style.css?779b1a3debcaeba619f6fe500e93d525"> - - <link rel="stylesheet" href="/custom.css?a649ea3528d4b626fb636505d94c1144"> - + <link rel="stylesheet" href="/hl.css?h=208c52e3b7c9db1cad782c5d30b4698f"> + <link rel="stylesheet" href="/style.css?h=779b1a3debcaeba619f6fe500e93d525"> + <link rel="stylesheet" href="/custom.css?h=a649ea3528d4b626fb636505d94c1144"> </head> <body class="single"> <header class="header"> @@ -29,239 +25,189 @@ <article class="post-single"> <header class="post-header"> <h1 class="post-title">PHPerKaigi 2023: ボツになったトークン問題 その 1</h1> - - <ul class="post-tags"> - - <li class="tag"> - <a href="/tags/php/">PHP</a> - </li> - - <li class="tag"> - <a href="/tags/phperkaigi/">PHPerKaigi</a> - </li> - - </ul> - + <ul class="post-tags"> + <li class="tag"> + <a href="/tags/php">PHP</a> + </li> + <li class="tag"> + <a href="/tags/phperkaigi">PHPerKaigi</a> + </li> + </ul> </header> <div class="post-content"> <section> <h2 id="changelog">更新履歴</h2> <ol> - - <li class="revision"> - <time datetime="2022-10-23">2022-10-23</time>: 公開 - </li> - + <li class="revision"> + <time datetime="2022-10-23">2022-10-23</time>: 公開 + </li> </ol> </section> - <section class="section-1"> - <h2 id="" class="section-header"> - - はじめに - - </h2> - <div class="section-body"> - <div class="paragraph"> -<p>2023 年 3 月 23 日から 25 日にかけて開催予定 (記事執筆時点) -の、 <a href="https://phperkaigi.jp/2023/">PHPerKaigi 2023</a> -において、昨年と同様に、弊社 <a href="https://www.dgcircus.com/">デジタルサーカス株式会社</a> -から、トークン問題を出題予定である。</p> -</div> -<div class="paragraph"> -<p>昨年のトークン問題の記事はこちら: -<a href="/posts/2022-04-09/phperkaigi-2022-tokens">PHPerKaigi 2022 -トークン問題の解説</a></p> -</div> -<div class="paragraph"> -<p>すでに 2023 -年用の問題は作成済みであるが、その制作過程の中でいくつかボツ問ができた。せっかくなので、PHPerKaigi -開催を待つ間に紹介しようと思う。</p> -</div> -<div class="paragraph"> -<p>10 月から 2 月まで、毎月 1 記事ずつ公開していく予定 (忘れていなければ)。</p> -</div> - </div> -</section> -<section class="section-1"> - <h2 id="" class="section-header"> - - 問題 - - </h2> - <div class="section-body"> - <div class="paragraph"> -<p>注意: これはボツ問なので、得られたトークンを PHPerKaigi -で入力してもポイントにはならない。</p> -</div> -<div id="source." class="listingblock"> -<div class="content"> -<pre class="rouge highlight"><code data-lang="php"><span class="o"><?</span><span class="n">php</span> + <section id="section--_はじめに"> + <h2><a href="#section--_はじめに">はじめに</a></h2> + <p> + 2023 年 3 月 23 日から 25 日にかけて開催予定 (記事執筆時点) の、<a xl:href="https://phperkaigi.jp/2023/">PHPerKaigi 2023</a>において、昨年と同様に、弊社<a xl:href="https://www.dgcircus.com/">デジタルサーカス株式会社</a>から、トークン問題を出題予定である。 + </p> + + <p> + 昨年のトークン問題の記事はこちら:<a xl:href="/posts/2022-04-09/phperkaigi-2022-tokens">PHPerKaigi 2022 トークン問題の解説</a> + </p> + + <p> + すでに 2023 年用の問題は作成済みであるが、その制作過程の中でいくつかボツ問ができた。せっかくなので、PHPerKaigi 開催を待つ間に紹介しようと思う。 + </p> + + <p> + 10 月から 2 月まで、毎月 1 記事ずつ公開していく予定 (忘れていなければ)。 + </p> + </section> + + <section id="section--_問題"> + <h2><a href="#section--_問題">問題</a></h2> + <p> + 注意: これはボツ問なので、得られたトークンを PHPerKaigi で入力してもポイントにはならない。 + </p> + + <pre language="php" linenumbering="unnumbered"> + <code><?php -<span class="nv">$π</span> <span class="o">=</span> <span class="nv">$argv</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="o">??</span> <span class="kc">null</span><span class="p">;</span> -<span class="k">if</span> <span class="p">(</span><span class="nv">$π</span> <span class="o">===</span> <span class="kc">null</span><span class="p">)</span> <span class="p">{</span> - <span class="k">exit</span><span class="p">(</span><span class="s1">'No input.'</span><span class="p">);</span> -<span class="p">}</span> -<span class="nv">$π</span> <span class="o">=</span> <span class="nb">trim</span><span class="p">(</span><span class="nv">$π</span><span class="p">);</span> -<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nb">is_numeric</span><span class="p">(</span><span class="nv">$π</span><span class="p">))</span> <span class="p">{</span> - <span class="k">exit</span><span class="p">(</span><span class="s1">'Invalid input.'</span><span class="p">);</span> -<span class="p">}</span> +$π = $argv[1] ?? null; +if ($π === null) { +exit('No input.'); +} +$π = trim($π); +if (!is_numeric($π)) { +exit('Invalid input.'); +} -<span class="nv">$s</span> <span class="o">=</span> <span class="nb">implode</span><span class="p">(</span><span class="nb">array_map</span><span class="p">(</span><span class="nb">chr</span><span class="p">(</span><span class="mf">...</span><span class="p">),</span> <span class="nb">str_split</span><span class="p">(</span><span class="nv">$π</span><span class="p">,</span> <span class="mi">2</span><span class="p">)));</span> +$s = implode(array_map(chr(...), str_split($π, 2))); -<span class="nb">preg_match</span><span class="p">(</span><span class="s1">'/(\x23.+?) /'</span><span class="p">,</span> <span class="nv">$s</span><span class="p">,</span> <span class="nv">$m</span><span class="p">);</span> -<span class="nv">$t</span> <span class="o">=</span> <span class="nv">$m</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="o">??</span> <span class="s1">''</span><span class="p">;</span> +preg_match('/(\x23.+?) /', $s, $m); +$t = $m[1] ?? ''; -<span class="k">if</span> <span class="p">(</span><span class="nb">md5</span><span class="p">(</span><span class="nv">$t</span><span class="p">)</span> <span class="o">===</span> <span class="s1">'056e831a4146bf123e8ea16613303d2e'</span><span class="p">)</span> <span class="p">{</span> - <span class="k">echo</span> <span class="s2">"Token: </span><span class="si">{</span><span class="nv">$t</span><span class="si">}</span><span class="se">\n</span><span class="s2">"</span><span class="p">;</span> -<span class="p">}</span> <span class="k">else</span> <span class="p">{</span> - <span class="k">echo</span> <span class="s2">"Failed.</span><span class="se">\n</span><span class="s2">"</span><span class="p">;</span> -<span class="p">}</span></code></pre> -</div> -</div> - </div> -</section> -<section class="section-1"> - <h2 id="" class="section-header"> - - トークン入手方法 - - </h2> - <div class="section-body"> - <div class="paragraph"> -<p>ソースを見るとわかるとおり、<code>$argv[1]</code> を参照している。それを <code>$π</code> -なる変数に代入しているので、円周率を渡してみる。</p> -</div> -<div id="source." class="listingblock"> -<div class="content"> -<pre class="rouge highlight"><code data-lang="shell-session"><span class="gp">$</span><span class="w"> </span>php Q.php 3.14 -<span class="go">Failed.</span></code></pre> -</div> -</div> -<div class="paragraph"> -<p>失敗してしまった。精度を上げてみる。</p> -</div> -<div id="source." class="listingblock"> -<div class="content"> -<pre class="rouge highlight"><code data-lang="shell-session"><span class="gp">$</span><span class="w"> </span>php Q.php 3.1415 -<span class="go">Failed.</span></code></pre> -</div> -</div> -<div class="paragraph"> -<p>だめだった。これを成功するまで繰り返す。</p> -</div> -<div class="paragraph"> -<p>最初にトークンが得られるのは、小数点以下 16 -桁目まで入力したときで、こうなる。</p> -</div> -<div id="source." class="listingblock"> -<div class="content"> -<pre class="rouge highlight"><code data-lang="shell-session"><span class="gp">$</span><span class="w"> </span>php Q.php 3.1415926535897932 -<span class="gp">Token: #</span>YO</code></pre> -</div> -</div> -<div class="paragraph"> -<p>めでたくトークン「#YO」が手に入った。</p> -</div> - </div> -</section> -<section class="section-1"> - <h2 id="" class="section-header"> - - 解説 - - </h2> - <div class="section-body"> - <div class="paragraph"> -<p>短いので頭から追っていく。</p> -</div> -<div id="source." class="listingblock"> -<div class="content"> -<pre class="rouge highlight"><code data-lang="php"><span class="nv">$π</span> <span class="o">=</span> <span class="nv">$argv</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="o">??</span> <span class="kc">null</span><span class="p">;</span> -<span class="k">if</span> <span class="p">(</span><span class="nv">$π</span> <span class="o">===</span> <span class="kc">null</span><span class="p">)</span> <span class="p">{</span> - <span class="k">exit</span><span class="p">(</span><span class="s1">'No input.'</span><span class="p">);</span> -<span class="p">}</span> -<span class="nv">$π</span> <span class="o">=</span> <span class="nb">trim</span><span class="p">(</span><span class="nv">$π</span><span class="p">);</span> -<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nb">is_numeric</span><span class="p">(</span><span class="nv">$π</span><span class="p">))</span> <span class="p">{</span> - <span class="k">exit</span><span class="p">(</span><span class="s1">'Invalid input.'</span><span class="p">);</span> -<span class="p">}</span></code></pre> -</div> -</div> -<div class="paragraph"> -<p>入力のバリデーション部分。数値のみ受け付ける。</p> -</div> -<div id="source." class="listingblock"> -<div class="content"> -<pre class="rouge highlight"><code data-lang="php"><span class="nv">$s</span> <span class="o">=</span> <span class="nb">implode</span><span class="p">(</span><span class="nb">array_map</span><span class="p">(</span><span class="nb">chr</span><span class="p">(</span><span class="mf">...</span><span class="p">),</span> <span class="nb">str_split</span><span class="p">(</span><span class="nv">$π</span><span class="p">,</span> <span class="mi">2</span><span class="p">)));</span></code></pre> -</div> -</div> -<div class="paragraph"> -<p><code>$π</code> を 2 文字ごとに区切り (<code>str_split</code>)、数値を ASCII -コードと見做して文字に変換 (<code>chr</code>) して結合 (<code>implode</code>) している。</p> -</div> -<div class="paragraph"> -<p>例えば、<code>$π</code> が <code>'656667'</code> だったとすると、<code>65</code>、<code>66</code>、<code>67</code> に対応した -<code>'A'</code>、<code>'B'</code>、<code>'C'</code> へと変換され、<code>'ABC'</code> になる。</p> -</div> -<div id="source." class="listingblock"> -<div class="content"> -<pre class="rouge highlight"><code data-lang="php"><span class="nv">$π</span> <span class="o">=</span> <span class="s1">'656667'</span><span class="p">;</span> -<span class="nv">$s</span> <span class="o">=</span> <span class="nb">implode</span><span class="p">(</span><span class="nb">array_map</span><span class="p">(</span><span class="nb">chr</span><span class="p">(</span><span class="mf">...</span><span class="p">),</span> <span class="nb">str_split</span><span class="p">(</span><span class="nv">$π</span><span class="p">,</span> <span class="mi">2</span><span class="p">)));</span> -<span class="k">echo</span> <span class="nv">$s</span><span class="p">;</span> -<span class="c1">// => ABC</span></code></pre> -</div> -</div> -<div id="source." class="listingblock"> -<div class="content"> -<pre class="rouge highlight"><code data-lang="php"><span class="nb">preg_match</span><span class="p">(</span><span class="s1">'/(\x23.+?) /'</span><span class="p">,</span> <span class="nv">$s</span><span class="p">,</span> <span class="nv">$m</span><span class="p">);</span> -<span class="nv">$t</span> <span class="o">=</span> <span class="nv">$m</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="o">??</span> <span class="s1">''</span><span class="p">;</span></code></pre> -</div> -</div> -<div class="paragraph"> -<p>正規表現でマッチングしている。<code>\x23</code> は <code>#</code> -と同じであることに留意すると、この正規表現は「<code>#</code> から始まる 2 -以上の長さ (含 <code>#</code>) -の文字列で、最初に現れるスペースまで」にマッチする。つまりこれは、PHPerKaigi -におけるトークンである。</p> -</div> -<div class="paragraph"> -<p>なお、<code>#</code> を直接書いていないのは、<code>/#.+?) /</code> と書くと、<code>#.+?)</code> -という意図せぬトークンが登録されてしまうからである。</p> -</div> -<div id="source." class="listingblock"> -<div class="content"> -<pre class="rouge highlight"><code data-lang="php"><span class="k">if</span> <span class="p">(</span><span class="nb">md5</span><span class="p">(</span><span class="nv">$t</span><span class="p">)</span> <span class="o">===</span> <span class="s1">'056e831a4146bf123e8ea16613303d2e'</span><span class="p">)</span> <span class="p">{</span> - <span class="k">echo</span> <span class="s2">"Token: </span><span class="si">{</span><span class="nv">$t</span><span class="si">}</span><span class="se">\n</span><span class="s2">"</span><span class="p">;</span> -<span class="p">}</span> <span class="k">else</span> <span class="p">{</span> - <span class="k">echo</span> <span class="s2">"Failed.</span><span class="se">\n</span><span class="s2">"</span><span class="p">;</span> -<span class="p">}</span></code></pre> -</div> -</div> -<div class="paragraph"> -<p>最後にトークンのハッシュ値を見て、想定解かどうかを確認する。</p> -</div> - </div> -</section> -<section class="section-1"> - <h2 id="" class="section-header"> - - おわりに - - </h2> - <div class="section-body"> - <div class="paragraph"> -<p>円周率を何桁も計算して ASCII -コード経由で文字列化すれば、トークンっぽいものがどこかで出てくるのではないか、と考えて生まれた作品。</p> -</div> -<div class="paragraph"> -<p>最初は真面目に円周率の計算プログラムを組んでいたのだが、いざ動かしてみるとやけに浅いところにあったので驚いた -(ちなみに、それでも <code>M_PI</code> や <code>pi()</code> -では精度が足りない)。見つけたときは狂喜したものの、冷静になってみると大して面白くなかったのでボツになった。むしろ、100 -万桁目くらいに埋まっていてくれたほうがよかったかもしれない。</p> -</div> - </div> -</section> +if (md5($t) === '056e831a4146bf123e8ea16613303d2e') { +echo "Token: {$t}\n"; +} else { +echo "Failed.\n"; +}</code> + </pre> + </section> + + <section id="section--_トークン入手方法"> + <h2><a href="#section--_トークン入手方法">トークン入手方法</a></h2> + <p> + ソースを見るとわかるとおり、<code>$argv[1]</code>を参照している。それを<code>$π</code>なる変数に代入しているので、円周率を渡してみる。 + </p> + + <pre language="shell-session" linenumbering="unnumbered"> + <code>$ php Q.php 3.14 + Failed.</code> + </pre> + + <p> + 失敗してしまった。精度を上げてみる。 + </p> + + <pre language="shell-session" linenumbering="unnumbered"> + <code>$ php Q.php 3.1415 +Failed.</code> + </pre> + + <p> + だめだった。これを成功するまで繰り返す。 + </p> + + <p> + 最初にトークンが得られるのは、小数点以下 16 桁目まで入力したときで、こうなる。 + </p> + + <pre language="shell-session" linenumbering="unnumbered"> + <code>$ php Q.php 3.1415926535897932 +Token: #YO</code> + </pre> + + <p> + めでたくトークン「#YO」が手に入った。 + </p> + </section> + + <section id="section--_解説"> + <h2><a href="#section--_解説">解説</a></h2> + <p> + 短いので頭から追っていく。 + </p> + + <pre language="php" linenumbering="unnumbered"> + <code>$π = $argv[1] ?? null; + if ($π === null) { + exit('No input.'); + } + $π = trim($π); + if (!is_numeric($π)) { + exit('Invalid input.'); + }</code> + </pre> + + <p> + 入力のバリデーション部分。数値のみ受け付ける。 + </p> + + <pre language="php" linenumbering="unnumbered"> + <code>$s = implode(array_map(chr(...), str_split($π, 2)));</code> + </pre> + + <p> + <code>$π</code>を 2 文字ごとに区切り (<code>str_split</code>)、数値を ASCII コードと見做して文字に変換 (<code>chr</code>) して結合 (<code>implode</code>) している。 + </p> + + <p> + 例えば、<code>$π</code>が<code>'656667'</code>だったとすると、<code>65</code>、<code>66</code>、<code>67</code>に対応した<code>'A'</code>、<code>'B'</code>、<code>'C'</code>へと変換され、<code>'ABC'</code>になる。 + </p> + + <pre language="php" linenumbering="unnumbered"> + <code>$π = '656667'; +$s = implode(array_map(chr(...), str_split($π, 2))); +echo $s; +// => ABC</code> + </pre> + + <pre language="php" linenumbering="unnumbered"> + <code>preg_match('/(\x23.+?) /', $s, $m); +$t = $m[1] ?? '';</code> + </pre> + + <p> + 正規表現でマッチングしている。<code>\x23</code>は<code>#</code>と同じであることに留意すると、この正規表現は「<code>#</code>から始まる 2 以上の長さ (含<code>#</code>) の文字列で、最初に現れるスペースまで」にマッチする。つまりこれは、PHPerKaigi におけるトークンである。 + </p> + + <p> + なお、<code>#</code>を直接書いていないのは、<code>/#.+?) /</code>と書くと、<code>#.+?)</code>という意図せぬトークンが登録されてしまうからである。 + </p> + + <pre language="php" linenumbering="unnumbered"> + <code>if (md5($t) === '056e831a4146bf123e8ea16613303d2e') { +echo "Token: {$t}\n"; +} else { +echo "Failed.\n"; +}</code> + </pre> + + <p> + 最後にトークンのハッシュ値を見て、想定解かどうかを確認する。 + </p> + </section> + + <section id="section--_おわりに"> + <h2><a href="#section--_おわりに">おわりに</a></h2> + <p> + 円周率を何桁も計算して ASCII コード経由で文字列化すれば、トークンっぽいものがどこかで出てくるのではないか、と考えて生まれた作品。 + </p> + + <p> + 最初は真面目に円周率の計算プログラムを組んでいたのだが、いざ動かしてみるとやけに浅いところにあったので驚いた (ちなみに、それでも<code>M_PI</code>や<code>pi()</code>では精度が足りない)。見つけたときは狂喜したものの、冷静になってみると大して面白くなかったのでボツになった。むしろ、100 万桁目くらいに埋まっていてくれたほうがよかったかもしれない。 + </p> + </section> </div> - </article> </main> <footer class="footer"> diff --git a/public/posts/2022-10-28/setup-server-for-this-site/index.html b/public/posts/2022-10-28/setup-server-for-this-site/index.html index e9e2c0b..7528251 100644 --- a/public/posts/2022-10-28/setup-server-for-this-site/index.html +++ b/public/posts/2022-10-28/setup-server-for-this-site/index.html @@ -4,18 +4,14 @@ <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="author" content="nsfisis"> - <meta name="copyright" content="© nsfisis"> - <meta name="description" content="GitHub Pages でホストしていたこのサイトを VPS へ移行したので、 そのときにやったことのメモ。99 % 自分用。"> + <meta name="copyright" content="© 2022 nsfisis"> + <meta name="description" content="GitHub Pages でホストしていたこのサイトを VPS へ移行したので、そのときにやったことのメモ。99 % 自分用。"> <meta name="keywords" content="備忘録"> <link rel="icon" type="image/svg+xml" href="/favicon.svg"> - <title>【備忘録】このサイト用の VPS をセットアップしたときのメモ | REPL: Rest-Eat-Program Loop</title> - - <link rel="stylesheet" href="/hl.css?208c52e3b7c9db1cad782c5d30b4698f"> - - <link rel="stylesheet" href="/style.css?779b1a3debcaeba619f6fe500e93d525"> - - <link rel="stylesheet" href="/custom.css?a649ea3528d4b626fb636505d94c1144"> - + <title>【備忘録】 このサイト用の VPS をセットアップしたときのメモ | REPL: Rest-Eat-Program Loop</title> + <link rel="stylesheet" href="/hl.css?h=208c52e3b7c9db1cad782c5d30b4698f"> + <link rel="stylesheet" href="/style.css?h=779b1a3debcaeba619f6fe500e93d525"> + <link rel="stylesheet" href="/custom.css?h=a649ea3528d4b626fb636505d94c1144"> </head> <body class="single"> <header class="header"> @@ -28,479 +24,316 @@ <main class="main"> <article class="post-single"> <header class="post-header"> - <h1 class="post-title">【備忘録】このサイト用の VPS をセットアップしたときのメモ</h1> - - <ul class="post-tags"> - - <li class="tag"> - <a href="/tags/note-to-self/">備忘録</a> - </li> - - </ul> - + <h1 class="post-title">【備忘録】 このサイト用の VPS をセットアップしたときのメモ</h1> + <ul class="post-tags"> + <li class="tag"> + <a href="/tags/note-to-self">備忘録</a> + </li> + </ul> </header> <div class="post-content"> <section> <h2 id="changelog">更新履歴</h2> <ol> + <li class="revision"> + <time datetime="2022-10-28">2022-10-28</time>: 公開 + </li> + </ol> + </section> + <section id="section--_はじめに"> + <h2><a href="#section--_はじめに">はじめに</a></h2> + <p> + これまでこの blog は GitHub Pages でホストしていたのだが、先日 VPS に移行した。そのときにおこなったサーバのセットアップ作業を書き残しておく。99 % 自分用の備忘録。別のベンダに移したりしたくなったら見に来る。 + </p> + + <p> + 未来の自分へ: 特に自動化してないので、せいぜい苦しんでくれ。 + </p> + </section> + + <section id="section--_vps"> + <h2><a href="#section--_vps">VPS</a></h2> + <p> + <a xl:href="https://vps.sakura.ad.jp/">さくらの VPS</a>の 2 GB プラン。そこまで真面目に選定していないので、困ったら移動するかも。 + </p> + </section> + + <section id="section--_事前準備"> + <h2><a href="#section--_事前準備">事前準備</a></h2> + <section id="section--_サーバのホスト名を決める"> + <h3><a href="#section--_サーバのホスト名を決める">サーバのホスト名を決める</a></h3> + <p> + モチベーションが上がるという効能がある。今回は藤原定家から取って ``teika'' にした。たいていいつも源氏物語の帖か小倉百人一首の歌人から選んでいる。 + </p> + </section> + + <section id="section--_ssh_の鍵生成"> + <h3><a href="#section--_ssh_の鍵生成">SSH の鍵生成</a></h3> + <p> + ローカルマシンで鍵を生成する。 + </p> + + <pre language="shell-session" linenumbering="unnumbered"> + <code>$ ssh-keygen -t ed25519 -b 521 -f ~/.ssh/teika.key + $ ssh-keygen -t ed25519 -b 521 -f ~/.ssh/github2teika.key</code> + </pre> + + <p> + <code>teika.key</code>はローカルからサーバへの接続用、<code>github2teika.key</code>は、GitHub Actions からサーバへのデプロイ用。 + </p> + </section> + + <section id="section--_ssh_の設定"> + <h3><a href="#section--_ssh_の設定">SSH の設定</a></h3> + <p> + <code>.ssh/config</code>に設定しておく。 + </p> + + <pre language="ssh_config" linenumbering="unnumbered"> + <code>Host teika + HostName ********** + User ********** + Port ********** + IdentityFile ~/.ssh/teika.key</code> + </pre> + </section> + </section> + + <section id="section--_基本のセットアップ"> + <h2><a href="#section--_基本のセットアップ">基本のセットアップ</a></h2> + <section id="section--_ssh_接続"> + <h3><a href="#section--_ssh_接続">SSH 接続</a></h3> + <p> + VPS 契約時に設定した管理者ユーザとパスワードを使ってログインする。 + </p> + </section> + + <section id="section--_ユーザを作成する"> + <h3><a href="#section--_ユーザを作成する">ユーザを作成する</a></h3> + <p> + 管理者ユーザで作業すると危ないので、メインで使うユーザを作成する。<code>sudo</code>グループに追加して<code>sudo</code>できるようにし、<code>su</code>で切り替え。 + </p> + + <pre language="shell-session" linenumbering="unnumbered"> + <code>$ sudo adduser ********** + $ sudo adduser ********** sudo + $ su ********** + $ cd</code> + </pre> + </section> + + <section id="section--_ホスト名を変える"> + <h3><a href="#section--_ホスト名を変える">ホスト名を変える</a></h3> + <pre language="shell-session" linenumbering="unnumbered"> + <code>$ sudo hostname teika</code> + </pre> + </section> + + <section id="section--_公開鍵を置く"> + <h3><a href="#section--_公開鍵を置く">公開鍵を置く</a></h3> + <pre language="shell-session" linenumbering="unnumbered"> + <code>$ mkdir ~/.ssh + $ chmod 700 ~/.ssh + $ vi ~/.ssh/authorized_keys</code> + </pre> + + <p> + <code>authorized_keys</code>には、ローカルで生成した<code>~/.ssh/teika.key.pub</code>と<code>~/.ssh/github2teika.key.pub</code>の内容をコピーする。 + </p> + </section> + + <section id="section--_ssh_の設定_2"> + <h3><a href="#section--_ssh_の設定_2">SSH の設定</a></h3> + <p> + SSH の設定を変更し、少しでも安全にしておく。 + </p> + + <pre language="shell-session" linenumbering="unnumbered"> + <code>$ sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak + $ sudo vi /etc/ssh/sshd_config</code> + </pre> - <li class="revision"> - <time datetime="2022-10-28">2022-10-28</time>: 公開 + <ul> + <li> + <p> + <code>Port</code>を変更 + </p> </li> + + <li> + <p> + <code>PermitRootLogin</code>を<code>no</code>に + </p> + </li> + + <li> + <p> + <code>PasswordAuthentication</code>を<code>no</code>に + </p> + </li> + </ul> - </ol> + <p> + そして設定を反映。 + </p> + + <pre language="shell-session" linenumbering="unnumbered"> + <code>$ sudo systemctl restart sshd +$ sudo systemctl status sshd</code> + </pre> + </section> + + <section id="section--_ssh_で接続確認"> + <h3><a href="#section--_ssh_で接続確認">SSH で接続確認</a></h3> + <p> + 今の SSH セッションは閉じずに、ターミナルを別途開いて疎通確認する。セッションを閉じてしまうと、SSH の設定に不備があった場合に締め出しをくらう。 + </p> + + <pre language="shell-session" linenumbering="unnumbered"> + <code>$ ssh teika</code> + </pre> + </section> + + <section id="section--_ポートの遮断"> + <h3><a href="#section--_ポートの遮断">ポートの遮断</a></h3> + <p> + デフォルトの 22 番を閉じ、設定したポートだけ空ける。 + </p> + + <pre language="shell-session" linenumbering="unnumbered"> + <code>$ sudo ufw deny ssh + $ sudo ufw allow ******* + $ sudo ufw enable + $ sudo ufw reload + $ sudo ufw status</code> + </pre> + + <p> + ここでもう一度 SSH の接続確認を挟む。 + </p> + </section> + + <section id="section--_github_用の_ssh_鍵"> + <h3><a href="#section--_github_用の_ssh_鍵">GitHub 用の SSH 鍵</a></h3> + <p> + GitHub に置いてある private リポジトリをサーバから clone したいので、SSH 鍵を生成して置いておく。 + </p> + + <pre language="shell-session" linenumbering="unnumbered"> + <code>$ ssh-keygen -t ed25519 -b 521 -f ~/.ssh/github.key +$ cat ~/.ssh/github.key.pub</code> + </pre> + + <p> + <a xl:href="https://github.com/settings/ssh">GitHub の設定画面</a>から、この公開鍵を追加する。 + </p> + + <pre language="shell-session" linenumbering="unnumbered"> + <code>$ vi ~/.ssh/config</code> + </pre> + + <p> + 設定はこう。 + </p> + + <pre language="ssh_config" linenumbering="unnumbered"> + <code>Host github.com +HostName github.com +User git +IdentityFile ~/.ssh/github.key</code> + </pre> + + <p> + 最後に接続できるか確認しておく。 + </p> + + <pre language="shell-session" linenumbering="unnumbered"> + <code>ssh -T github.com</code> + </pre> + </section> + + <section id="section--_パッケージの更新"> + <h3><a href="#section--_パッケージの更新">パッケージの更新</a></h3> + <pre language="shell-session" linenumbering="unnumbered"> + <code>$ sudo apt update + $ sudo apt upgrade + $ sudo apt update + $ sudo apt upgrade + $ sudo apt autoremove</code> + </pre> + </section> + </section> + + <section id="section--_サイトホスティング用のセットアップ"> + <h2><a href="#section--_サイトホスティング用のセットアップ">サイトホスティング用のセットアップ</a></h2> + <section id="section--_dns_に_ip_アドレスを登録する"> + <h3><a href="#section--_dns_に_ip_アドレスを登録する">DNS に IP アドレスを登録する</a></h3> + <p> + このサーバは固定の IP アドレスがあるので、<code>A</code>レコードに直接入れるだけで済んだ。 + </p> + </section> + + <section id="section--_使うソフトウェアのインストール"> + <h3><a href="#section--_使うソフトウェアのインストール">使うソフトウェアのインストール</a></h3> + <pre language="shell-session" linenumbering="unnumbered"> + <code>$ sudo apt install docker docker-compose git make</code> + </pre> + </section> + + <section id="section--_メインユーザが_docker_を使えるように"> + <h3><a href="#section--_メインユーザが_docker_を使えるように">メインユーザが Docker を使えるように</a></h3> + <pre language="shell-session" linenumbering="unnumbered"> + <code>sudo adduser ********** docker</code> + </pre> + </section> + + <section id="section--_httphttps_を通す"> + <h3><a href="#section--_httphttps_を通す">HTTP/HTTPS を通す</a></h3> + <p> + 80 番と 443 番を空ける。 + </p> + + <pre language="shell-session" linenumbering="unnumbered"> + <code>$ sudo ufw allow 80/tcp + $ sudo ufw allow 443/tcp + $ sudo ufw reload + $ sudo ufw status</code> + </pre> + </section> + + <section id="section--_リポジトリのクローン"> + <h3><a href="#section--_リポジトリのクローン">リポジトリのクローン</a></h3> + <pre language="shell-session" linenumbering="unnumbered"> + <code>$ cd + $ git clone git@github.com:nsfisis/nsfisis.dev.git + $ cd nsfisis.dev + $ git submodule update --init</code> + </pre> + </section> + + <section id="section--_certbot_で証明書取得"> + <h3><a href="#section--_certbot_で証明書取得">certbot で証明書取得</a></h3> + <pre language="shell-session" linenumbering="unnumbered"> + <code>$ docker-compose up -d acme-challenge + $ make setup</code> + </pre> + </section> + + <section id="section--_サーバを稼動させる"> + <h3><a href="#section--_サーバを稼動させる">サーバを稼動させる</a></h3> + <pre language="shell-session" linenumbering="unnumbered"> + <code>$ make serve</code> + </pre> + </section> + </section> + + <section id="section--_感想"> + <h2><a href="#section--_感想">感想</a></h2> + <p> + (業務でなく) 個人だと数年ぶりのサーバセットアップで、これだけでも割と時間を食ってしまった。とはいえ式年遷宮は楽しいので、これからも定期的にやっていきたい。コンテナデプロイにしたい気持ちもあるのだが、色々実験したい関係上、本物のサーバも欲しくはある。次の式年遷宮では、手順の一部だけでも自動化したいところ。 + </p> </section> - <section class="section-1"> - <h2 id="" class="section-header"> - - はじめに - - </h2> - <div class="section-body"> - <div class="paragraph"> -<p>これまでこの blog は GitHub Pages でホストしていたのだが、先日 VPS -に移行した。そのときにおこなったサーバのセットアップ作業を書き残しておく。99 -% 自分用の備忘録。別のベンダに移したりしたくなったら見に来る。</p> -</div> -<div class="paragraph"> -<p>未来の自分へ: 特に自動化してないので、せいぜい苦しんでくれ。</p> -</div> - </div> -</section> -<section class="section-1"> - <h2 id="" class="section-header"> - - VPS - - </h2> - <div class="section-body"> - <div class="paragraph"> -<p><a href="https://vps.sakura.ad.jp/">さくらの VPS</a> の 2 GB -プラン。そこまで真面目に選定していないので、困ったら移動するかも。</p> -</div> - </div> -</section> -<section class="section-1"> - <h2 id="" class="section-header"> - - 事前準備 - - </h2> - <div class="section-body"> - <section class="section-2"> - <h3 id="" class="section-header"> - - サーバのホスト名を決める - - </h3> - <div class="section-body"> - <div class="paragraph"> -<p>モチベーションが上がるという効能がある。今回は藤原定家から取って -``teika'' -にした。たいていいつも源氏物語の帖か小倉百人一首の歌人から選んでいる。</p> -</div> - </div> -</section> -<section class="section-2"> - <h3 id="" class="section-header"> - - SSH の鍵生成 - - </h3> - <div class="section-body"> - <div class="paragraph"> -<p>ローカルマシンで鍵を生成する。</p> -</div> -<div id="source." class="listingblock"> -<div class="content"> -<pre class="rouge highlight"><code data-lang="shell-session"><span class="gp">$</span><span class="w"> </span>ssh-keygen <span class="nt">-t</span> ed25519 <span class="nt">-b</span> 521 <span class="nt">-f</span> ~/.ssh/teika.key -<span class="gp">$</span><span class="w"> </span>ssh-keygen <span class="nt">-t</span> ed25519 <span class="nt">-b</span> 521 <span class="nt">-f</span> ~/.ssh/github2teika.key</code></pre> -</div> -</div> -<div class="paragraph"> -<p><code>teika.key</code> はローカルからサーバへの接続用、<code>github2teika.key</code> -は、GitHub Actions からサーバへのデプロイ用。</p> -</div> - </div> -</section> -<section class="section-2"> - <h3 id="" class="section-header"> - - SSH の設定 - - </h3> - <div class="section-body"> - <div class="paragraph"> -<p><code>.ssh/config</code> に設定しておく。</p> -</div> -<div id="source." class="listingblock"> -<div class="content"> -<pre class="rouge highlight"><code data-lang="ssh_config">Host teika - HostName ********** - User ********** - Port ********** - IdentityFile ~/.ssh/teika.key</code></pre> -</div> -</div> - </div> -</section> - </div> -</section> -<section class="section-1"> - <h2 id="" class="section-header"> - - 基本のセットアップ - - </h2> - <div class="section-body"> - <section class="section-2"> - <h3 id="" class="section-header"> - - SSH 接続 - - </h3> - <div class="section-body"> - <div class="paragraph"> -<p>VPS 契約時に設定した管理者ユーザとパスワードを使ってログインする。</p> -</div> - </div> -</section> -<section class="section-2"> - <h3 id="" class="section-header"> - - ユーザを作成する - - </h3> - <div class="section-body"> - <div class="paragraph"> -<p>管理者ユーザで作業すると危ないので、メインで使うユーザを作成する。<code>sudo</code> -グループに追加して <code>sudo</code> できるようにし、<code>su</code> で切り替え。</p> -</div> -<div id="source." class="listingblock"> -<div class="content"> -<pre class="rouge highlight"><code data-lang="shell-session"><span class="gp">$</span><span class="w"> </span><span class="nb">sudo </span>adduser <span class="k">**********</span> -<span class="gp">$</span><span class="w"> </span><span class="nb">sudo </span>adduser <span class="k">**********</span> <span class="nb">sudo</span> -<span class="gp">$</span><span class="w"> </span>su <span class="k">**********</span> -<span class="gp">$</span><span class="w"> </span><span class="nb">cd</span></code></pre> -</div> -</div> - </div> -</section> -<section class="section-2"> - <h3 id="" class="section-header"> - - ホスト名を変える - - </h3> - <div class="section-body"> - <div id="source." class="listingblock"> -<div class="content"> -<pre class="rouge highlight"><code data-lang="shell-session"><span class="gp">$</span><span class="w"> </span><span class="nb">sudo hostname </span>teika</code></pre> -</div> -</div> - </div> -</section> -<section class="section-2"> - <h3 id="" class="section-header"> - - 公開鍵を置く - - </h3> - <div class="section-body"> - <div id="source." class="listingblock"> -<div class="content"> -<pre class="rouge highlight"><code data-lang="shell-session"><span class="gp">$</span><span class="w"> </span><span class="nb">mkdir</span> ~/.ssh -<span class="gp">$</span><span class="w"> </span><span class="nb">chmod </span>700 ~/.ssh -<span class="gp">$</span><span class="w"> </span>vi ~/.ssh/authorized_keys</code></pre> -</div> -</div> -<div class="paragraph"> -<p><code>authorized_keys</code> には、ローカルで生成した <code>~/.ssh/teika.key.pub</code> と -<code>~/.ssh/github2teika.key.pub</code> の内容をコピーする。</p> -</div> - </div> -</section> -<section class="section-2"> - <h3 id="" class="section-header"> - - SSH の設定 - - </h3> - <div class="section-body"> - <div class="paragraph"> -<p>SSH の設定を変更し、少しでも安全にしておく。</p> -</div> -<div id="source." class="listingblock"> -<div class="content"> -<pre class="rouge highlight"><code data-lang="shell-session"><span class="gp">$</span><span class="w"> </span><span class="nb">sudo cp</span> /etc/ssh/sshd_config /etc/ssh/sshd_config.bak -<span class="gp">$</span><span class="w"> </span><span class="nb">sudo </span>vi /etc/ssh/sshd_config</code></pre> -</div> -</div> -<div class="ulist"> -<ul> -<li> -<p><code>Port</code> を変更</p> -</li> -<li> -<p><code>PermitRootLogin</code> を <code>no</code> に</p> -</li> -<li> -<p><code>PasswordAuthentication</code> を <code>no</code> に</p> -</li> -</ul> -</div> -<div class="paragraph"> -<p>そして設定を反映。</p> -</div> -<div id="source." class="listingblock"> -<div class="content"> -<pre class="rouge highlight"><code data-lang="shell-session"><span class="gp">$</span><span class="w"> </span><span class="nb">sudo </span>systemctl restart sshd -<span class="gp">$</span><span class="w"> </span><span class="nb">sudo </span>systemctl status sshd</code></pre> -</div> -</div> - </div> -</section> -<section class="section-2"> - <h3 id="" class="section-header"> - - SSH で接続確認 - - </h3> - <div class="section-body"> - <div class="paragraph"> -<p>今の SSH -セッションは閉じずに、ターミナルを別途開いて疎通確認する。セッションを閉じてしまうと、SSH -の設定に不備があった場合に締め出しをくらう。</p> -</div> -<div id="source." class="listingblock"> -<div class="content"> -<pre class="rouge highlight"><code data-lang="shell-session"><span class="gp">$</span><span class="w"> </span>ssh teika</code></pre> -</div> -</div> - </div> -</section> -<section class="section-2"> - <h3 id="" class="section-header"> - - ポートの遮断 - - </h3> - <div class="section-body"> - <div class="paragraph"> -<p>デフォルトの 22 番を閉じ、設定したポートだけ空ける。</p> -</div> -<div id="source." class="listingblock"> -<div class="content"> -<pre class="rouge highlight"><code data-lang="shell-session"><span class="gp">$</span><span class="w"> </span><span class="nb">sudo </span>ufw deny ssh -<span class="gp">$</span><span class="w"> </span><span class="nb">sudo </span>ufw allow <span class="k">*******</span> -<span class="gp">$</span><span class="w"> </span><span class="nb">sudo </span>ufw <span class="nb">enable</span> -<span class="gp">$</span><span class="w"> </span><span class="nb">sudo </span>ufw reload -<span class="gp">$</span><span class="w"> </span><span class="nb">sudo </span>ufw status</code></pre> -</div> -</div> -<div class="paragraph"> -<p>ここでもう一度 SSH の接続確認を挟む。</p> -</div> - </div> -</section> -<section class="section-2"> - <h3 id="" class="section-header"> - - GitHub 用の SSH 鍵 - - </h3> - <div class="section-body"> - <div class="paragraph"> -<p>GitHub に置いてある private リポジトリをサーバから clone したいので、SSH -鍵を生成して置いておく。</p> -</div> -<div id="source." class="listingblock"> -<div class="content"> -<pre class="rouge highlight"><code data-lang="shell-session"><span class="gp">$</span><span class="w"> </span>ssh-keygen <span class="nt">-t</span> ed25519 <span class="nt">-b</span> 521 <span class="nt">-f</span> ~/.ssh/github.key -<span class="gp">$</span><span class="w"> </span><span class="nb">cat</span> ~/.ssh/github.key.pub</code></pre> -</div> -</div> -<div class="paragraph"> -<p><a href="https://github.com/settings/ssh">GitHub の設定画面</a> -から、この公開鍵を追加する。</p> -</div> -<div id="source." class="listingblock"> -<div class="content"> -<pre class="rouge highlight"><code data-lang="shell-session"><span class="gp">$</span><span class="w"> </span>vi ~/.ssh/config</code></pre> -</div> -</div> -<div class="paragraph"> -<p>設定はこう。</p> -</div> -<div id="source." class="listingblock"> -<div class="content"> -<pre class="rouge highlight"><code data-lang="ssh_config">Host github.com - HostName github.com - User git - IdentityFile ~/.ssh/github.key</code></pre> -</div> -</div> -<div class="paragraph"> -<p>最後に接続できるか確認しておく。</p> -</div> -<div id="source." class="listingblock"> -<div class="content"> -<pre class="rouge highlight"><code data-lang="shell-session"><span class="go">ssh -T github.com</span></code></pre> -</div> -</div> - </div> -</section> -<section class="section-2"> - <h3 id="" class="section-header"> - - パッケージの更新 - - </h3> - <div class="section-body"> - <div id="source." class="listingblock"> -<div class="content"> -<pre class="rouge highlight"><code data-lang="shell-session"><span class="gp">$</span><span class="w"> </span><span class="nb">sudo </span>apt update -<span class="gp">$</span><span class="w"> </span><span class="nb">sudo </span>apt upgrade -<span class="gp">$</span><span class="w"> </span><span class="nb">sudo </span>apt update -<span class="gp">$</span><span class="w"> </span><span class="nb">sudo </span>apt upgrade -<span class="gp">$</span><span class="w"> </span><span class="nb">sudo </span>apt autoremove</code></pre> -</div> -</div> - </div> -</section> - </div> -</section> -<section class="section-1"> - <h2 id="" class="section-header"> - - サイトホスティング用のセットアップ - - </h2> - <div class="section-body"> - <section class="section-2"> - <h3 id="" class="section-header"> - - DNS に IP アドレスを登録する - - </h3> - <div class="section-body"> - <div class="paragraph"> -<p>このサーバは固定の IP アドレスがあるので、<code>A</code> -レコードに直接入れるだけで済んだ。</p> -</div> - </div> -</section> -<section class="section-2"> - <h3 id="" class="section-header"> - - 使うソフトウェアのインストール - - </h3> - <div class="section-body"> - <div id="source." class="listingblock"> -<div class="content"> -<pre class="rouge highlight"><code data-lang="shell-session"><span class="gp">$</span><span class="w"> </span><span class="nb">sudo </span>apt <span class="nb">install </span>docker docker-compose git make</code></pre> -</div> -</div> - </div> -</section> -<section class="section-2"> - <h3 id="" class="section-header"> - - メインユーザが Docker を使えるように - - </h3> - <div class="section-body"> - <div id="source." class="listingblock"> -<div class="content"> -<pre class="rouge highlight"><code data-lang="shell-session"><span class="go">sudo adduser ********** docker</span></code></pre> -</div> -</div> - </div> -</section> -<section class="section-2"> - <h3 id="" class="section-header"> - - HTTP/HTTPS を通す - - </h3> - <div class="section-body"> - <div class="paragraph"> -<p>80 番と 443 番を空ける。</p> -</div> -<div id="source." class="listingblock"> -<div class="content"> -<pre class="rouge highlight"><code data-lang="shell-session"><span class="gp">$</span><span class="w"> </span><span class="nb">sudo </span>ufw allow 80/tcp -<span class="gp">$</span><span class="w"> </span><span class="nb">sudo </span>ufw allow 443/tcp -<span class="gp">$</span><span class="w"> </span><span class="nb">sudo </span>ufw reload -<span class="gp">$</span><span class="w"> </span><span class="nb">sudo </span>ufw status</code></pre> -</div> -</div> - </div> -</section> -<section class="section-2"> - <h3 id="" class="section-header"> - - リポジトリのクローン - - </h3> - <div class="section-body"> - <div id="source." class="listingblock"> -<div class="content"> -<pre class="rouge highlight"><code data-lang="shell-session"><span class="gp">$</span><span class="w"> </span><span class="nb">cd</span> -<span class="gp">$</span><span class="w"> </span>git clone git@github.com:nsfisis/nsfisis.dev.git -<span class="gp">$</span><span class="w"> </span><span class="nb">cd </span>nsfisis.dev -<span class="gp">$</span><span class="w"> </span>git submodule update <span class="nt">--init</span></code></pre> -</div> -</div> - </div> -</section> -<section class="section-2"> - <h3 id="" class="section-header"> - - certbot で証明書取得 - - </h3> - <div class="section-body"> - <div id="source." class="listingblock"> -<div class="content"> -<pre class="rouge highlight"><code data-lang="shell-session"><span class="gp">$</span><span class="w"> </span>docker-compose up <span class="nt">-d</span> acme-challenge -<span class="gp">$</span><span class="w"> </span>make setup</code></pre> -</div> -</div> - </div> -</section> -<section class="section-2"> - <h3 id="" class="section-header"> - - サーバを稼動させる - - </h3> - <div class="section-body"> - <div id="source." class="listingblock"> -<div class="content"> -<pre class="rouge highlight"><code data-lang="shell-session"><span class="gp">$</span><span class="w"> </span>make serve</code></pre> -</div> -</div> - </div> -</section> - </div> -</section> -<section class="section-1"> - <h2 id="" class="section-header"> - - 感想 - - </h2> - <div class="section-body"> - <div class="paragraph"> -<p>(業務でなく) -個人だと数年ぶりのサーバセットアップで、これだけでも割と時間を食ってしまった。とはいえ式年遷宮は楽しいので、これからも定期的にやっていきたい。コンテナデプロイにしたい気持ちもあるのだが、色々実験したい関係上、本物のサーバも欲しくはある。次の式年遷宮では、手順の一部だけでも自動化したいところ。</p> -</div> - </div> -</section> </div> - </article> </main> <footer class="footer"> diff --git a/public/posts/2022-11-19/phperkaigi-2023-unused-token-quiz-2/index.html b/public/posts/2022-11-19/phperkaigi-2023-unused-token-quiz-2/index.html index a40a5e6..e785f4f 100644 --- a/public/posts/2022-11-19/phperkaigi-2023-unused-token-quiz-2/index.html +++ b/public/posts/2022-11-19/phperkaigi-2023-unused-token-quiz-2/index.html @@ -4,18 +4,14 @@ <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="author" content="nsfisis"> - <meta name="copyright" content="© nsfisis"> - <meta name="description" content="来年の PHPerKaigi 2023 でデジタルサーカス株式会社から出題予定のトークン問題のうち、 ボツになった問題を公開する (その 2)。"> + <meta name="copyright" content="© 2022 nsfisis"> + <meta name="description" content="来年の PHPerKaigi 2023 でデジタルサーカス株式会社から出題予定のトークン問題のうち、ボツになった問題を公開する (その 2)。"> <meta name="keywords" content="PHP,PHPerKaigi"> <link rel="icon" type="image/svg+xml" href="/favicon.svg"> <title>PHPerKaigi 2023: ボツになったトークン問題 その 2 | REPL: Rest-Eat-Program Loop</title> - - <link rel="stylesheet" href="/hl.css?208c52e3b7c9db1cad782c5d30b4698f"> - - <link rel="stylesheet" href="/style.css?779b1a3debcaeba619f6fe500e93d525"> - - <link rel="stylesheet" href="/custom.css?a649ea3528d4b626fb636505d94c1144"> - + <link rel="stylesheet" href="/hl.css?h=208c52e3b7c9db1cad782c5d30b4698f"> + <link rel="stylesheet" href="/style.css?h=779b1a3debcaeba619f6fe500e93d525"> + <link rel="stylesheet" href="/custom.css?h=a649ea3528d4b626fb636505d94c1144"> </head> <body class="single"> <header class="header"> @@ -29,225 +25,198 @@ <article class="post-single"> <header class="post-header"> <h1 class="post-title">PHPerKaigi 2023: ボツになったトークン問題 その 2</h1> - - <ul class="post-tags"> - - <li class="tag"> - <a href="/tags/php/">PHP</a> - </li> - - <li class="tag"> - <a href="/tags/phperkaigi/">PHPerKaigi</a> - </li> - - </ul> - + <ul class="post-tags"> + <li class="tag"> + <a href="/tags/php">PHP</a> + </li> + <li class="tag"> + <a href="/tags/phperkaigi">PHPerKaigi</a> + </li> + </ul> </header> <div class="post-content"> <section> <h2 id="changelog">更新履歴</h2> <ol> - - <li class="revision"> - <time datetime="2022-11-19">2022-11-19</time>: 公開 - </li> - + <li class="revision"> + <time datetime="2022-11-19">2022-11-19</time>: 公開 + </li> </ol> </section> - <section class="section-1"> - <h2 id="" class="section-header"> - - はじめに - - </h2> - <div class="section-body"> - <div class="paragraph"> -<p>2023 年 3 月 23 日から 25 日にかけて開催予定 (記事執筆時点) の <a href="https://phperkaigi.jp/2023/">PHPerKaigi 2023</a> において、 -昨年と同様に、弊社 <a href="https://www.dgcircus.com/">デジタルサーカス株式会社</a> からトークン問題を出題予定である。</p> -</div> -<div class="paragraph"> -<p>昨年のトークン問題の記事はこちら: <a href="/posts/2022-04-09/phperkaigi-2022-tokens/">PHPerKaigi 2022 トークン問題の解説</a></p> -</div> -<div class="paragraph"> -<p>すでに 2023 年用の問題は作成済みであるが、その制作過程の中でいくつかボツ問ができた。せっかくなので、PHPerKaigi 開催を待つ間に紹介しようと思う。</p> -</div> -<div class="paragraph"> -<p>10 月から 2 月まで、毎月 1 記事ずつ公開していく予定 (忘れていなければ)。</p> -</div> -<div class="paragraph"> -<p>その 1 はこちら: <a href="/posts/2022-10-23/phperkaigi-2023-unused-token-quiz-1/">PHPerKaigi 2023: ボツになったトークン問題 その 1</a></p> -</div> - </div> -</section> -<section class="section-1"> - <h2 id="" class="section-header"> - - 問題 - - </h2> - <div class="section-body"> - <div class="paragraph"> -<p>注意: これはボツ問なので、得られたトークンを PHPerKaigi で入力してもポイントにはならない。</p> -</div> -<div id="source." class="listingblock"> -<div class="content"> -<pre class="rouge highlight"><code data-lang="php"><span class="o"><?</span><span class="n">php</span> <span class="nb">printf</span><span class="p">((</span><span class="k">isset</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">?</span><span class="k">fn</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">=></span><span class="nb">trim</span><span class="p">(</span><span class="nv">$s</span><span class="p">,</span><span class="s2">""</span><span class="p">)</span><span class="o">:</span><span class="k">fn</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">=></span><span class="nb">chr</span><span class="p">(</span><span class="nb">strlen</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">/</span><span class="mi">3</span><span class="p">))(</span><span class="nv">$s</span><span class="o">=</span><span class="s1">'<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>'</span><span class="p">)</span><span class="mf">.</span><span class="s2">"</span><span class="se">\n</span><span class="s2">"</span><span class="p">,</span><span class="s2">"</span><span class="se">\x27</span><span class="nv">$s</span><span class="se">\x27</span><span class="s2">"</span><span class="p">);</span><span class="cp">?></span> -<span class="cp"><?php</span> <span class="nb">printf</span><span class="p">((</span><span class="k">isset</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">?</span><span class="k">fn</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">=></span><span class="nb">trim</span><span class="p">(</span><span class="nv">$s</span><span class="p">,</span><span class="s2">""</span><span class="p">)</span><span class="o">:</span><span class="k">fn</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">=></span><span class="nb">chr</span><span class="p">(</span><span class="nb">strlen</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">/</span><span class="mi">3</span><span class="p">))(</span><span class="nv">$s</span><span class="o">=</span><span class="s1">'<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>'</span><span class="p">)</span><span class="mf">.</span><span class="s2">"</span><span class="se">\n</span><span class="s2">"</span><span class="p">,</span><span class="s2">"</span><span class="se">\x27</span><span class="nv">$s</span><span class="se">\x27</span><span class="s2">"</span><span class="p">);</span><span class="cp">?></span> -<span class="cp"><?php</span> <span class="nb">printf</span><span class="p">((</span><span class="k">isset</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">?</span><span class="k">fn</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">=></span><span class="nb">trim</span><span class="p">(</span><span class="nv">$s</span><span class="p">,</span><span class="s2">""</span><span class="p">)</span><span class="o">:</span><span class="k">fn</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">=></span><span class="nb">chr</span><span class="p">(</span><span class="nb">strlen</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">/</span><span class="mi">3</span><span class="p">))(</span><span class="nv">$s</span><span class="o">=</span><span class="s1">'<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>'</span><span class="p">)</span><span class="mf">.</span><span class="s2">"</span><span class="se">\n</span><span class="s2">"</span><span class="p">,</span><span class="s2">"</span><span class="se">\x27</span><span class="nv">$s</span><span class="se">\x27</span><span class="s2">"</span><span class="p">);</span><span class="cp">?></span> -<span class="cp"><?php</span> <span class="nb">printf</span><span class="p">((</span><span class="k">isset</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">?</span><span class="k">fn</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">=></span><span class="nb">trim</span><span class="p">(</span><span class="nv">$s</span><span class="p">,</span><span class="s2">""</span><span class="p">)</span><span class="o">:</span><span class="k">fn</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">=></span><span class="nb">chr</span><span class="p">(</span><span class="nb">strlen</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">/</span><span class="mi">3</span><span class="p">))(</span><span class="nv">$s</span><span class="o">=</span><span class="s1">'<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>'</span><span class="p">)</span><span class="mf">.</span><span class="s2">"</span><span class="se">\n</span><span class="s2">"</span><span class="p">,</span><span class="s2">"</span><span class="se">\x27</span><span class="nv">$s</span><span class="se">\x27</span><span class="s2">"</span><span class="p">);</span><span class="cp">?></span> -<span class="cp"><?php</span> <span class="nb">printf</span><span class="p">((</span><span class="k">isset</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">?</span><span class="k">fn</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">=></span><span class="nb">trim</span><span class="p">(</span><span class="nv">$s</span><span class="p">,</span><span class="s2">""</span><span class="p">)</span><span class="o">:</span><span class="k">fn</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">=></span><span class="nb">chr</span><span class="p">(</span><span class="nb">strlen</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">/</span><span class="mi">3</span><span class="p">))(</span><span class="nv">$s</span><span class="o">=</span><span class="s1">'<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>'</span><span class="p">)</span><span class="mf">.</span><span class="s2">"</span><span class="se">\n</span><span class="s2">"</span><span class="p">,</span><span class="s2">"</span><span class="se">\x27</span><span class="nv">$s</span><span class="se">\x27</span><span class="s2">"</span><span class="p">);</span><span class="cp">?></span> -<span class="cp"><?php</span> <span class="nb">printf</span><span class="p">((</span><span class="k">isset</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">?</span><span class="k">fn</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">=></span><span class="nb">trim</span><span class="p">(</span><span class="nv">$s</span><span class="p">,</span><span class="s2">""</span><span class="p">)</span><span class="o">:</span><span class="k">fn</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">=></span><span class="nb">chr</span><span class="p">(</span><span class="nb">strlen</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">/</span><span class="mi">3</span><span class="p">))(</span><span class="nv">$s</span><span class="o">=</span><span class="s1">'<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>'</span><span class="p">)</span><span class="mf">.</span><span class="s2">"</span><span class="se">\n</span><span class="s2">"</span><span class="p">,</span><span class="s2">"</span><span class="se">\x27</span><span class="nv">$s</span><span class="se">\x27</span><span class="s2">"</span><span class="p">);</span><span class="cp">?></span> -<span class="cp"><?php</span> <span class="nb">printf</span><span class="p">((</span><span class="k">isset</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">?</span><span class="k">fn</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">=></span><span class="nb">trim</span><span class="p">(</span><span class="nv">$s</span><span class="p">,</span><span class="s2">""</span><span class="p">)</span><span class="o">:</span><span class="k">fn</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">=></span><span class="nb">chr</span><span class="p">(</span><span class="nb">strlen</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">/</span><span class="mi">3</span><span class="p">))(</span><span class="nv">$s</span><span class="o">=</span><span class="s1">'<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>'</span><span class="p">)</span><span class="mf">.</span><span class="s2">"</span><span class="se">\n</span><span class="s2">"</span><span class="p">,</span><span class="s2">"</span><span class="se">\x27</span><span class="nv">$s</span><span class="se">\x27</span><span class="s2">"</span><span class="p">);</span><span class="cp">?></span> -<span class="cp"><?php</span> <span class="nb">printf</span><span class="p">((</span><span class="k">isset</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">?</span><span class="k">fn</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">=></span><span class="nb">trim</span><span class="p">(</span><span class="nv">$s</span><span class="p">,</span><span class="s2">""</span><span class="p">)</span><span class="o">:</span><span class="k">fn</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">=></span><span class="nb">chr</span><span class="p">(</span><span class="nb">strlen</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">/</span><span class="mi">3</span><span class="p">))(</span><span class="nv">$s</span><span class="o">=</span><span class="s1">'<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>'</span><span class="p">)</span><span class="mf">.</span><span class="s2">"</span><span class="se">\n</span><span class="s2">"</span><span class="p">,</span><span class="s2">"</span><span class="se">\x27</span><span class="nv">$s</span><span class="se">\x27</span><span class="s2">"</span><span class="p">);</span><span class="cp">?></span> -<span class="cp"><?php</span> <span class="nb">printf</span><span class="p">((</span><span class="k">isset</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">?</span><span class="k">fn</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">=></span><span class="nb">trim</span><span class="p">(</span><span class="nv">$s</span><span class="p">,</span><span class="s2">""</span><span class="p">)</span><span class="o">:</span><span class="k">fn</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">=></span><span class="nb">chr</span><span class="p">(</span><span class="nb">strlen</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">/</span><span class="mi">3</span><span class="p">))(</span><span class="nv">$s</span><span class="o">=</span><span class="s1">'<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>'</span><span class="p">)</span><span class="mf">.</span><span class="s2">"</span><span class="se">\n</span><span class="s2">"</span><span class="p">,</span><span class="s2">"</span><span class="se">\x27</span><span class="nv">$s</span><span class="se">\x27</span><span class="s2">"</span><span class="p">);</span><span class="cp">?></span> -<span class="cp"><?php</span> <span class="nb">printf</span><span class="p">((</span><span class="k">isset</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">?</span><span class="k">fn</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">=></span><span class="nb">trim</span><span class="p">(</span><span class="nv">$s</span><span class="p">,</span><span class="s2">""</span><span class="p">)</span><span class="o">:</span><span class="k">fn</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">=></span><span class="nb">chr</span><span class="p">(</span><span class="nb">strlen</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">/</span><span class="mi">3</span><span class="p">))(</span><span class="nv">$s</span><span class="o">=</span><span class="s1">'<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>'</span><span class="p">)</span><span class="mf">.</span><span class="s2">"</span><span class="se">\n</span><span class="s2">"</span><span class="p">,</span><span class="s2">"</span><span class="se">\x27</span><span class="nv">$s</span><span class="se">\x27</span><span class="s2">"</span><span class="p">);</span><span class="cp">?></span></code></pre> -</div> -</div> -<div class="paragraph"> -<p>"And Then There Were None" (そして誰もいなくなった) と名付けた作品。変則 quine (自分自身と同じソースコードを出力するプログラム) になっている。</p> -</div> - </div> -</section> -<section class="section-1"> - <h2 id="" class="section-header"> - - トークン入手方法 - - </h2> - <div class="section-body"> - <div class="paragraph"> -<p>実行してみると、次のような出力が得られる。</p> -</div> -<div id="source." class="listingblock"> -<div class="content"> -<pre class="rouge highlight"><code data-lang="php"><span class="c1">#</span> -<span class="o"><?</span><span class="n">php</span> <span class="nb">printf</span><span class="p">((</span><span class="k">isset</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">?</span><span class="k">fn</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">=></span><span class="nb">trim</span><span class="p">(</span><span class="nv">$s</span><span class="p">,</span><span class="s2">""</span><span class="p">)</span><span class="o">:</span><span class="k">fn</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">=></span><span class="nb">chr</span><span class="p">(</span><span class="nb">strlen</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">/</span><span class="mi">3</span><span class="p">))(</span><span class="nv">$s</span><span class="o">=</span><span class="s1">'<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>'</span><span class="p">)</span><span class="mf">.</span><span class="s2">"</span><span class="se">\n</span><span class="s2">"</span><span class="p">,</span><span class="s2">"</span><span class="se">\x27</span><span class="nv">$s</span><span class="se">\x27</span><span class="s2">"</span><span class="p">);</span><span class="cp">?></span> -<span class="cp"><?php</span> <span class="nb">printf</span><span class="p">((</span><span class="k">isset</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">?</span><span class="k">fn</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">=></span><span class="nb">trim</span><span class="p">(</span><span class="nv">$s</span><span class="p">,</span><span class="s2">""</span><span class="p">)</span><span class="o">:</span><span class="k">fn</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">=></span><span class="nb">chr</span><span class="p">(</span><span class="nb">strlen</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">/</span><span class="mi">3</span><span class="p">))(</span><span class="nv">$s</span><span class="o">=</span><span class="s1">'<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>'</span><span class="p">)</span><span class="mf">.</span><span class="s2">"</span><span class="se">\n</span><span class="s2">"</span><span class="p">,</span><span class="s2">"</span><span class="se">\x27</span><span class="nv">$s</span><span class="se">\x27</span><span class="s2">"</span><span class="p">);</span><span class="cp">?></span> -<span class="cp"><?php</span> <span class="nb">printf</span><span class="p">((</span><span class="k">isset</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">?</span><span class="k">fn</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">=></span><span class="nb">trim</span><span class="p">(</span><span class="nv">$s</span><span class="p">,</span><span class="s2">""</span><span class="p">)</span><span class="o">:</span><span class="k">fn</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">=></span><span class="nb">chr</span><span class="p">(</span><span class="nb">strlen</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">/</span><span class="mi">3</span><span class="p">))(</span><span class="nv">$s</span><span class="o">=</span><span class="s1">'<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>'</span><span class="p">)</span><span class="mf">.</span><span class="s2">"</span><span class="se">\n</span><span class="s2">"</span><span class="p">,</span><span class="s2">"</span><span class="se">\x27</span><span class="nv">$s</span><span class="se">\x27</span><span class="s2">"</span><span class="p">);</span><span class="cp">?></span> -<span class="cp"><?php</span> <span class="nb">printf</span><span class="p">((</span><span class="k">isset</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">?</span><span class="k">fn</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">=></span><span class="nb">trim</span><span class="p">(</span><span class="nv">$s</span><span class="p">,</span><span class="s2">""</span><span class="p">)</span><span class="o">:</span><span class="k">fn</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">=></span><span class="nb">chr</span><span class="p">(</span><span class="nb">strlen</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">/</span><span class="mi">3</span><span class="p">))(</span><span class="nv">$s</span><span class="o">=</span><span class="s1">'<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>'</span><span class="p">)</span><span class="mf">.</span><span class="s2">"</span><span class="se">\n</span><span class="s2">"</span><span class="p">,</span><span class="s2">"</span><span class="se">\x27</span><span class="nv">$s</span><span class="se">\x27</span><span class="s2">"</span><span class="p">);</span><span class="cp">?></span> -<span class="cp"><?php</span> <span class="nb">printf</span><span class="p">((</span><span class="k">isset</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">?</span><span class="k">fn</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">=></span><span class="nb">trim</span><span class="p">(</span><span class="nv">$s</span><span class="p">,</span><span class="s2">""</span><span class="p">)</span><span class="o">:</span><span class="k">fn</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">=></span><span class="nb">chr</span><span class="p">(</span><span class="nb">strlen</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">/</span><span class="mi">3</span><span class="p">))(</span><span class="nv">$s</span><span class="o">=</span><span class="s1">'<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>'</span><span class="p">)</span><span class="mf">.</span><span class="s2">"</span><span class="se">\n</span><span class="s2">"</span><span class="p">,</span><span class="s2">"</span><span class="se">\x27</span><span class="nv">$s</span><span class="se">\x27</span><span class="s2">"</span><span class="p">);</span><span class="cp">?></span> -<span class="cp"><?php</span> <span class="nb">printf</span><span class="p">((</span><span class="k">isset</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">?</span><span class="k">fn</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">=></span><span class="nb">trim</span><span class="p">(</span><span class="nv">$s</span><span class="p">,</span><span class="s2">""</span><span class="p">)</span><span class="o">:</span><span class="k">fn</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">=></span><span class="nb">chr</span><span class="p">(</span><span class="nb">strlen</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">/</span><span class="mi">3</span><span class="p">))(</span><span class="nv">$s</span><span class="o">=</span><span class="s1">'<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>'</span><span class="p">)</span><span class="mf">.</span><span class="s2">"</span><span class="se">\n</span><span class="s2">"</span><span class="p">,</span><span class="s2">"</span><span class="se">\x27</span><span class="nv">$s</span><span class="se">\x27</span><span class="s2">"</span><span class="p">);</span><span class="cp">?></span> -<span class="cp"><?php</span> <span class="nb">printf</span><span class="p">((</span><span class="k">isset</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">?</span><span class="k">fn</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">=></span><span class="nb">trim</span><span class="p">(</span><span class="nv">$s</span><span class="p">,</span><span class="s2">""</span><span class="p">)</span><span class="o">:</span><span class="k">fn</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">=></span><span class="nb">chr</span><span class="p">(</span><span class="nb">strlen</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">/</span><span class="mi">3</span><span class="p">))(</span><span class="nv">$s</span><span class="o">=</span><span class="s1">'<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>'</span><span class="p">)</span><span class="mf">.</span><span class="s2">"</span><span class="se">\n</span><span class="s2">"</span><span class="p">,</span><span class="s2">"</span><span class="se">\x27</span><span class="nv">$s</span><span class="se">\x27</span><span class="s2">"</span><span class="p">);</span><span class="cp">?></span> -<span class="cp"><?php</span> <span class="nb">printf</span><span class="p">((</span><span class="k">isset</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">?</span><span class="k">fn</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">=></span><span class="nb">trim</span><span class="p">(</span><span class="nv">$s</span><span class="p">,</span><span class="s2">""</span><span class="p">)</span><span class="o">:</span><span class="k">fn</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">=></span><span class="nb">chr</span><span class="p">(</span><span class="nb">strlen</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">/</span><span class="mi">3</span><span class="p">))(</span><span class="nv">$s</span><span class="o">=</span><span class="s1">'<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>'</span><span class="p">)</span><span class="mf">.</span><span class="s2">"</span><span class="se">\n</span><span class="s2">"</span><span class="p">,</span><span class="s2">"</span><span class="se">\x27</span><span class="nv">$s</span><span class="se">\x27</span><span class="s2">"</span><span class="p">);</span><span class="cp">?></span> -<span class="cp"><?php</span> <span class="nb">printf</span><span class="p">((</span><span class="k">isset</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">?</span><span class="k">fn</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">=></span><span class="nb">trim</span><span class="p">(</span><span class="nv">$s</span><span class="p">,</span><span class="s2">""</span><span class="p">)</span><span class="o">:</span><span class="k">fn</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">=></span><span class="nb">chr</span><span class="p">(</span><span class="nb">strlen</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">/</span><span class="mi">3</span><span class="p">))(</span><span class="nv">$s</span><span class="o">=</span><span class="s1">'<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>'</span><span class="p">)</span><span class="mf">.</span><span class="s2">"</span><span class="se">\n</span><span class="s2">"</span><span class="p">,</span><span class="s2">"</span><span class="se">\x27</span><span class="nv">$s</span><span class="se">\x27</span><span class="s2">"</span><span class="p">);</span><span class="cp">?></span></code></pre> -</div> -</div> -<div class="paragraph"> -<p>1 行目を除き、先ほどのコードとほぼ同じものが出てきた。もう一度実行してみる。</p> -</div> -<div id="source." class="listingblock"> -<div class="content"> -<pre class="rouge highlight"><code data-lang="php"><span class="c1">#</span> -<span class="nc">W</span> -<span class="o"><?</span><span class="n">php</span> <span class="nb">printf</span><span class="p">((</span><span class="k">isset</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">?</span><span class="k">fn</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">=></span><span class="nb">trim</span><span class="p">(</span><span class="nv">$s</span><span class="p">,</span><span class="s2">""</span><span class="p">)</span><span class="o">:</span><span class="k">fn</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">=></span><span class="nb">chr</span><span class="p">(</span><span class="nb">strlen</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">/</span><span class="mi">3</span><span class="p">))(</span><span class="nv">$s</span><span class="o">=</span><span class="s1">'<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>'</span><span class="p">)</span><span class="mf">.</span><span class="s2">"</span><span class="se">\n</span><span class="s2">"</span><span class="p">,</span><span class="s2">"</span><span class="se">\x27</span><span class="nv">$s</span><span class="se">\x27</span><span class="s2">"</span><span class="p">);</span><span class="cp">?></span> -<span class="cp"><?php</span> <span class="nb">printf</span><span class="p">((</span><span class="k">isset</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">?</span><span class="k">fn</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">=></span><span class="nb">trim</span><span class="p">(</span><span class="nv">$s</span><span class="p">,</span><span class="s2">""</span><span class="p">)</span><span class="o">:</span><span class="k">fn</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">=></span><span class="nb">chr</span><span class="p">(</span><span class="nb">strlen</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">/</span><span class="mi">3</span><span class="p">))(</span><span class="nv">$s</span><span class="o">=</span><span class="s1">'<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>'</span><span class="p">)</span><span class="mf">.</span><span class="s2">"</span><span class="se">\n</span><span class="s2">"</span><span class="p">,</span><span class="s2">"</span><span class="se">\x27</span><span class="nv">$s</span><span class="se">\x27</span><span class="s2">"</span><span class="p">);</span><span class="cp">?></span> -<span class="cp"><?php</span> <span class="nb">printf</span><span class="p">((</span><span class="k">isset</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">?</span><span class="k">fn</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">=></span><span class="nb">trim</span><span class="p">(</span><span class="nv">$s</span><span class="p">,</span><span class="s2">""</span><span class="p">)</span><span class="o">:</span><span class="k">fn</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">=></span><span class="nb">chr</span><span class="p">(</span><span class="nb">strlen</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">/</span><span class="mi">3</span><span class="p">))(</span><span class="nv">$s</span><span class="o">=</span><span class="s1">'<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>'</span><span class="p">)</span><span class="mf">.</span><span class="s2">"</span><span class="se">\n</span><span class="s2">"</span><span class="p">,</span><span class="s2">"</span><span class="se">\x27</span><span class="nv">$s</span><span class="se">\x27</span><span class="s2">"</span><span class="p">);</span><span class="cp">?></span> -<span class="cp"><?php</span> <span class="nb">printf</span><span class="p">((</span><span class="k">isset</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">?</span><span class="k">fn</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">=></span><span class="nb">trim</span><span class="p">(</span><span class="nv">$s</span><span class="p">,</span><span class="s2">""</span><span class="p">)</span><span class="o">:</span><span class="k">fn</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">=></span><span class="nb">chr</span><span class="p">(</span><span class="nb">strlen</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">/</span><span class="mi">3</span><span class="p">))(</span><span class="nv">$s</span><span class="o">=</span><span class="s1">'<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>'</span><span class="p">)</span><span class="mf">.</span><span class="s2">"</span><span class="se">\n</span><span class="s2">"</span><span class="p">,</span><span class="s2">"</span><span class="se">\x27</span><span class="nv">$s</span><span class="se">\x27</span><span class="s2">"</span><span class="p">);</span><span class="cp">?></span> -<span class="cp"><?php</span> <span class="nb">printf</span><span class="p">((</span><span class="k">isset</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">?</span><span class="k">fn</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">=></span><span class="nb">trim</span><span class="p">(</span><span class="nv">$s</span><span class="p">,</span><span class="s2">""</span><span class="p">)</span><span class="o">:</span><span class="k">fn</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">=></span><span class="nb">chr</span><span class="p">(</span><span class="nb">strlen</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">/</span><span class="mi">3</span><span class="p">))(</span><span class="nv">$s</span><span class="o">=</span><span class="s1">'<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>'</span><span class="p">)</span><span class="mf">.</span><span class="s2">"</span><span class="se">\n</span><span class="s2">"</span><span class="p">,</span><span class="s2">"</span><span class="se">\x27</span><span class="nv">$s</span><span class="se">\x27</span><span class="s2">"</span><span class="p">);</span><span class="cp">?></span> -<span class="cp"><?php</span> <span class="nb">printf</span><span class="p">((</span><span class="k">isset</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">?</span><span class="k">fn</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">=></span><span class="nb">trim</span><span class="p">(</span><span class="nv">$s</span><span class="p">,</span><span class="s2">""</span><span class="p">)</span><span class="o">:</span><span class="k">fn</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">=></span><span class="nb">chr</span><span class="p">(</span><span class="nb">strlen</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">/</span><span class="mi">3</span><span class="p">))(</span><span class="nv">$s</span><span class="o">=</span><span class="s1">'<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>'</span><span class="p">)</span><span class="mf">.</span><span class="s2">"</span><span class="se">\n</span><span class="s2">"</span><span class="p">,</span><span class="s2">"</span><span class="se">\x27</span><span class="nv">$s</span><span class="se">\x27</span><span class="s2">"</span><span class="p">);</span><span class="cp">?></span> -<span class="cp"><?php</span> <span class="nb">printf</span><span class="p">((</span><span class="k">isset</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">?</span><span class="k">fn</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">=></span><span class="nb">trim</span><span class="p">(</span><span class="nv">$s</span><span class="p">,</span><span class="s2">""</span><span class="p">)</span><span class="o">:</span><span class="k">fn</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">=></span><span class="nb">chr</span><span class="p">(</span><span class="nb">strlen</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">/</span><span class="mi">3</span><span class="p">))(</span><span class="nv">$s</span><span class="o">=</span><span class="s1">'<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>'</span><span class="p">)</span><span class="mf">.</span><span class="s2">"</span><span class="se">\n</span><span class="s2">"</span><span class="p">,</span><span class="s2">"</span><span class="se">\x27</span><span class="nv">$s</span><span class="se">\x27</span><span class="s2">"</span><span class="p">);</span><span class="cp">?></span> -<span class="cp"><?php</span> <span class="nb">printf</span><span class="p">((</span><span class="k">isset</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">?</span><span class="k">fn</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">=></span><span class="nb">trim</span><span class="p">(</span><span class="nv">$s</span><span class="p">,</span><span class="s2">""</span><span class="p">)</span><span class="o">:</span><span class="k">fn</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">=></span><span class="nb">chr</span><span class="p">(</span><span class="nb">strlen</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">/</span><span class="mi">3</span><span class="p">))(</span><span class="nv">$s</span><span class="o">=</span><span class="s1">'<?php printf((isset($s)?fn($s)=>trim($s,""):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>'</span><span class="p">)</span><span class="mf">.</span><span class="s2">"</span><span class="se">\n</span><span class="s2">"</span><span class="p">,</span><span class="s2">"</span><span class="se">\x27</span><span class="nv">$s</span><span class="se">\x27</span><span class="s2">"</span><span class="p">);</span><span class="cp">?></span></code></pre> -</div> -</div> -<div class="paragraph"> -<p>今度は 2 行目が書き換えられた。すべての行が変化するまで繰り返すと次のようになる。</p> -</div> -<div id="source." class="listingblock"> -<div class="content"> -<pre class="rouge highlight"><code data-lang="php"><span class="c1">#</span> -<span class="nc">W</span> -<span class="nc">E</span> -<span class="nc">L</span> -<span class="nc">O</span> -<span class="nc">V</span> -<span class="nc">E</span> -<span class="nc">P</span> -<span class="nc">H</span> -<span class="nc">P</span></code></pre> -</div> -</div> -<div class="paragraph"> -<p>トークン「#WELOVEPHP」が手に入った。</p> -</div> - </div> -</section> -<section class="section-1"> - <h2 id="" class="section-header"> - - 解説 - - </h2> - <div class="section-body"> - <div class="paragraph"> -<p>一見すると同じ行が 10 行並んでいるだけなのにも関わらず、なぜそれぞれの行で出力が変わるのか。ソースコードをコピーして、適当なエディタに貼り付けるとわかりやすい。</p> -</div> -<div class="paragraph"> -<p>Vim で開くと次のようになる (1 行目を抜粋)。</p> -</div> -<div id="source." class="listingblock"> -<div class="content"> -<pre class="rouge highlight"><code data-lang="php"><span class="o"><?</span><span class="n">php</span> <span class="nb">printf</span><span class="p">((</span><span class="k">isset</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">?</span><span class="k">fn</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">=></span><span class="nb">trim</span><span class="p">(</span><span class="nv">$s</span><span class="p">,</span><span class="s2">"<200b>"</span><span class="p">)</span><span class="o">:</span><span class="k">fn</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">=></span><span class="nb">chr</span><span class="p">(</span><span class="nb">strlen</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">/</span><span class="mi">3</span><span class="p">))(</span><span class="nv">$s</span><span class="o">=</span><span class="s1">'<200b><?php printf((isset($s)?fn($s)=>trim($s,"<200b>"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>'</span><span class="p">)</span><span class="mf">.</span><span class="s2">"</span><span class="se">\n</span><span class="s2">"</span><span class="p">,</span><span class="s2">"</span><span class="se">\x27</span><span class="nv">$s</span><span class="se">\x27</span><span class="s2">"</span><span class="p">);</span><span class="cp">?></span></code></pre> -</div> -</div> -<div class="paragraph"> -<p><code><200b></code> と表示されているのは、Unicode の U+200b で、ゼロ幅スペースである。</p> -</div> -<div class="admonitionblock note"> -<table> -<tr> -<td class="icon"> -<div class="title">Note</div> -</td> -<td class="content"> -エディタによっては、ゼロ幅スペースが見えないことがある。VSCode ではブラウザと同様に不可視だった。 -</td> -</tr> -</table> -</div> -<div class="paragraph"> -<p>文字列リテラルの中にゼロ幅スペースを仕込むことで、見た目を変えずに情報をエンコードすることが可能となる。</p> -</div> -<div class="paragraph"> -<p>続いて、トークンへの変換ロジックを解析する。注目すべきはこの部分だ。以下、ゼロ幅スペースは Vim での表示に合わせて <code><200b></code> と記載する。</p> -</div> -<div id="source." class="listingblock"> -<div class="content"> -<pre class="rouge highlight"><code data-lang="php"><span class="k">fn</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">=></span><span class="nb">chr</span><span class="p">(</span><span class="nb">strlen</span><span class="p">(</span><span class="nv">$s</span><span class="p">)</span><span class="o">/</span><span class="mi">3</span><span class="p">)</span></code></pre> -</div> -</div> -<div class="paragraph"> -<p>PHP の <code>strlen()</code> は文字列のバイト数を返す。1 行目の <code>$s</code> は以下の内容となっており、</p> -</div> -<div id="source." class="listingblock"> -<div class="content"> -<pre class="rouge highlight"><code data-lang="php"><span class="nv">$s</span><span class="o">=</span><span class="s1">'<200b><?php printf((isset($s)?fn($s)=>trim($s,"<200b>"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>'</span></code></pre> -</div> -</div> -<div class="paragraph"> -<p>このソースコードは UTF-8 で書かれているので、105 バイトになる。それを 3 で割ると 35 となり、これは <code>#</code> の ASCII コードと一致する。他の行も、同様にしてゼロ幅スペースを詰めることで文字列長を調整し、トークンをエンコードしている。</p> -</div> -<div class="paragraph"> -<p>デコード部以外の部分は、quine のための記述である。</p> -</div> - </div> -</section> -<section class="section-1"> - <h2 id="" class="section-header"> - - おわりに - - </h2> - <div class="section-body"> - <div class="paragraph"> -<p><a href="https://blog.rust-lang.org/2021/11/01/cve-2021-42574.html">CVE-2021-42574</a> に着想を得た作品。この脆弱性は、Unicode の制御文字である left-to-right mark と right-to-left mark を利用し、ソースコードの実際の内容を欺く、というもの。簡単のためゼロ幅スペースを用いることとし、ついでに quine にもするとこうなった。</p> -</div> -<div class="paragraph"> -<p>ボツになった理由は、ゼロ幅スペースを表示してくるエディタが想像以上に多かったため。「同じ行が並んでいるだけなのに出力が異なる」というアイデアの根幹を崩されてしまうので、この問題は不採用となった。</p> -</div> - </div> -</section> + <section id="section--_はじめに"> + <h2><a href="#section--_はじめに">はじめに</a></h2> + <p> + 2023 年 3 月 23 日から 25 日にかけて開催予定 (記事執筆時点) の<a xl:href="https://phperkaigi.jp/2023/">PHPerKaigi 2023</a>において、 昨年と同様に、弊社<a xl:href="https://www.dgcircus.com/">デジタルサーカス株式会社</a>からトークン問題を出題予定である。 + </p> + + <p> + 昨年のトークン問題の記事はこちら:<a xl:href="/posts/2022-04-09/phperkaigi-2022-tokens/">PHPerKaigi 2022 トークン問題の解説</a> + </p> + + <p> + すでに 2023 年用の問題は作成済みであるが、その制作過程の中でいくつかボツ問ができた。せっかくなので、PHPerKaigi 開催を待つ間に紹介しようと思う。 + </p> + + <p> + 10 月から 2 月まで、毎月 1 記事ずつ公開していく予定 (忘れていなければ)。 + </p> + + <p> + その 1 はこちら:<a xl:href="/posts/2022-10-23/phperkaigi-2023-unused-token-quiz-1/">PHPerKaigi 2023: ボツになったトークン問題 その 1</a> + </p> + </section> + + <section id="section--_問題"> + <h2><a href="#section--_問題">問題</a></h2> + <p> + 注意: これはボツ問なので、得られたトークンを PHPerKaigi で入力してもポイントにはならない。 + </p> + + <pre language="php" linenumbering="unnumbered"> + <code><?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");?></code> + </pre> + + <p> + "And Then There Were None" (そして誰もいなくなった) と名付けた作品。変則 quine (自分自身と同じソースコードを出力するプログラム) になっている。 + </p> + </section> + + <section id="section--_トークン入手方法"> + <h2><a href="#section--_トークン入手方法">トークン入手方法</a></h2> + <p> + 実行してみると、次のような出力が得られる。 + </p> + + <pre language="php" linenumbering="unnumbered"> + <code># + <?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");?></code> + </pre> + + <p> + 1 行目を除き、先ほどのコードとほぼ同じものが出てきた。もう一度実行してみる。 + </p> + + <pre language="php" linenumbering="unnumbered"> + <code># +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");?></code> + </pre> + + <p> + 今度は 2 行目が書き換えられた。すべての行が変化するまで繰り返すと次のようになる。 + </p> + + <pre language="php" linenumbering="unnumbered"> + <code># +W +E +L +O +V +E +P +H +P</code> + </pre> + + <p> + トークン「#WELOVEPHP」が手に入った。 + </p> + </section> + + <section id="section--_解説"> + <h2><a href="#section--_解説">解説</a></h2> + <p> + 一見すると同じ行が 10 行並んでいるだけなのにも関わらず、なぜそれぞれの行で出力が変わるのか。ソースコードをコピーして、適当なエディタに貼り付けるとわかりやすい。 + </p> + + <p> + Vim で開くと次のようになる (1 行目を抜粋)。 + </p> + + <pre language="php" linenumbering="unnumbered"> + <code><?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");?></code> + </pre> + + <p> + <code><200b></code>と表示されているのは、Unicode の U+200b で、ゼロ幅スペースである。 + </p> + + <div> + <div> + Note + </div> + <div> + <p> + エディタによっては、ゼロ幅スペースが見えないことがある。VSCode ではブラウザと同様に不可視だった。 + </p> + </div> + </div> + + <p> + 文字列リテラルの中にゼロ幅スペースを仕込むことで、見た目を変えずに情報をエンコードすることが可能となる。 + </p> + + <p> + 続いて、トークンへの変換ロジックを解析する。注目すべきはこの部分だ。以下、ゼロ幅スペースは Vim での表示に合わせて<code><200b></code>と記載する。 + </p> + + <pre language="php" linenumbering="unnumbered"> + <code>fn($s)=>chr(strlen($s)/3)</code> + </pre> + + <p> + PHP の<code>strlen()</code>は文字列のバイト数を返す。1 行目の<code>$s</code>は以下の内容となっており、 + </p> + + <pre language="php" linenumbering="unnumbered"> + <code>$s='<200b><?php printf((isset($s)?fn($s)=>trim($s,"<200b>"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>'</code> + </pre> + + <p> + このソースコードは UTF-8 で書かれているので、105 バイトになる。それを 3 で割ると 35 となり、これは<code>#</code>の ASCII コードと一致する。他の行も、同様にしてゼロ幅スペースを詰めることで文字列長を調整し、トークンをエンコードしている。 + </p> + + <p> + デコード部以外の部分は、quine のための記述である。 + </p> + </section> + + <section id="section--_おわりに"> + <h2><a href="#section--_おわりに">おわりに</a></h2> + <p> + <a xl:href="https://blog.rust-lang.org/2021/11/01/cve-2021-42574.html">CVE-2021-42574</a>に着想を得た作品。この脆弱性は、Unicode の制御文字である left-to-right mark と right-to-left mark を利用し、ソースコードの実際の内容を欺く、というもの。簡単のためゼロ幅スペースを用いることとし、ついでに quine にもするとこうなった。 + </p> + + <p> + ボツになった理由は、ゼロ幅スペースを表示してくるエディタが想像以上に多かったため。「同じ行が並んでいるだけなのに出力が異なる」というアイデアの根幹を崩されてしまうので、この問題は不採用となった。 + </p> + </section> </div> - </article> </main> <footer class="footer"> diff --git a/public/posts/2023-01-10/phperkaigi-2023-unused-token-quiz-3/index.html b/public/posts/2023-01-10/phperkaigi-2023-unused-token-quiz-3/index.html index 974b33c..a20444e 100644 --- a/public/posts/2023-01-10/phperkaigi-2023-unused-token-quiz-3/index.html +++ b/public/posts/2023-01-10/phperkaigi-2023-unused-token-quiz-3/index.html @@ -4,18 +4,14 @@ <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="author" content="nsfisis"> - <meta name="copyright" content="© nsfisis"> - <meta name="description" content="来年の PHPerKaigi 2023 でデジタルサーカス株式会社から出題予定のトークン問題のうち、 ボツになった問題を公開する (その 3)。"> + <meta name="copyright" content="© 2023 nsfisis"> + <meta name="description" content="来年の PHPerKaigi 2023 でデジタルサーカス株式会社から出題予定のトークン問題のうち、ボツになった問題を公開する (その 3)。"> <meta name="keywords" content="PHP,PHPerKaigi"> <link rel="icon" type="image/svg+xml" href="/favicon.svg"> <title>PHPerKaigi 2023: ボツになったトークン問題 その 3 | REPL: Rest-Eat-Program Loop</title> - - <link rel="stylesheet" href="/hl.css?208c52e3b7c9db1cad782c5d30b4698f"> - - <link rel="stylesheet" href="/style.css?779b1a3debcaeba619f6fe500e93d525"> - - <link rel="stylesheet" href="/custom.css?a649ea3528d4b626fb636505d94c1144"> - + <link rel="stylesheet" href="/hl.css?h=208c52e3b7c9db1cad782c5d30b4698f"> + <link rel="stylesheet" href="/style.css?h=779b1a3debcaeba619f6fe500e93d525"> + <link rel="stylesheet" href="/custom.css?h=a649ea3528d4b626fb636505d94c1144"> </head> <body class="single"> <header class="header"> @@ -29,100 +25,82 @@ <article class="post-single"> <header class="post-header"> <h1 class="post-title">PHPerKaigi 2023: ボツになったトークン問題 その 3</h1> - - <ul class="post-tags"> - - <li class="tag"> - <a href="/tags/php/">PHP</a> - </li> - - <li class="tag"> - <a href="/tags/phperkaigi/">PHPerKaigi</a> - </li> - - </ul> - + <ul class="post-tags"> + <li class="tag"> + <a href="/tags/php">PHP</a> + </li> + <li class="tag"> + <a href="/tags/phperkaigi">PHPerKaigi</a> + </li> + </ul> </header> <div class="post-content"> <section> <h2 id="changelog">更新履歴</h2> <ol> - - <li class="revision"> - <time datetime="2023-01-10">2023-01-10</time>: 公開 - </li> - - <li class="revision"> - <time datetime="2023-01-10">2023-01-10</time>: 本シリーズの今後について追記 - </li> - + <li class="revision"> + <time datetime="2023-01-10">2023-01-10</time>: 公開 + </li> </ol> </section> - <section class="section-1"> - <h2 id="" class="section-header"> - - はじめに - - </h2> - <div class="section-body"> - <div class="paragraph"> -<p>2023 年 3 月 23 日から 25 日にかけて開催予定 (記事執筆時点) の <a href="https://phperkaigi.jp/2023/">PHPerKaigi 2023</a> において、 -昨年と同様に、弊社 <a href="https://www.dgcircus.com/">デジタルサーカス株式会社</a> からトークン問題を出題予定である。</p> -</div> -<div class="paragraph"> -<p>昨年のトークン問題の記事はこちら: <a href="/posts/2022-04-09/phperkaigi-2022-tokens/">PHPerKaigi 2022 トークン問題の解説</a></p> -</div> -<div class="paragraph"> -<p>すでに 2023 年用の問題は作成済みであるが、その制作過程の中でいくつかボツ問ができた。せっかくなので、PHPerKaigi 開催を待つ間に紹介しようと思う。</p> -</div> -<div class="paragraph"> -<p>10 月から 2 月まで、毎月 1 記事ずつ公開していく予定 (忘れていなければ → 忘れていたので 12 月公開予定だった記事を今書いている)。</p> -</div> -<div class="ulist"> -<ul> -<li> -<p>その 1 はこちら: <a href="/posts/2022-10-23/phperkaigi-2023-unused-token-quiz-1/">PHPerKaigi 2023: ボツになったトークン問題 その 1</a></p> -</li> -<li> -<p>その 2 はこちら: <a href="/posts/2022-11-19/phperkaigi-2023-unused-token-quiz-2/">PHPerKaigi 2023: ボツになったトークン問題 その 2</a></p> -</li> -</ul> -</div> -<div class="paragraph"> -<p>追記: 元々は 10月から 2月にかけて 5つのボツ問を公開予定だったのですが、光栄なことに PHPerKaigi 2023 での登壇が決まったので、1、2月の分は書かない/書けないと思います。</p> -</div> - </div> -</section> -<section class="section-1"> - <h2 id="" class="section-header"> - - 問題 - - </h2> - <div class="section-body"> - <div class="paragraph"> -<p>注意: これはボツ問なので、得られたトークンを PHPerKaigi で入力してもポイントにはならない。</p> -</div> -<div id="source." class="listingblock"> -<div class="content"> -<pre class="rouge highlight"><code data-lang="php"><span class="o"><?</span><span class="n">php</span> -<span class="k">try</span> <span class="p">{</span> - <span class="nf">f</span><span class="p">(</span><span class="nf">g</span><span class="p">()</span> <span class="o">/</span> <span class="k">__LINE__</span><span class="p">);</span> -<span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="nc">Throwable</span> <span class="nv">$e</span><span class="p">)</span> <span class="p">{</span> - <span class="k">while</span> <span class="p">(</span><span class="nv">$e</span> <span class="o">=</span> <span class="nv">$e</span><span class="o">-></span><span class="nf">getPrevious</span><span class="p">())</span> <span class="nb">printf</span><span class="p">(</span><span class="s1">'%c'</span><span class="p">,</span> <span class="nv">$e</span><span class="o">-></span><span class="nf">getLine</span><span class="p">()</span> <span class="o">+</span> <span class="mi">23</span><span class="p">);</span> - <span class="k">echo</span> <span class="s2">"</span><span class="se">\n</span><span class="s2">"</span><span class="p">;</span> -<span class="p">}</span> -<span class="k">function</span> <span class="n">f</span><span class="p">(</span><span class="kt">int</span> <span class="nv">$i</span><span class="p">)</span> <span class="p">{</span> - <span class="k">if</span> <span class="p">(</span><span class="nv">$i</span> <span class="o"><</span> <span class="mi">0</span><span class="p">)</span> <span class="nf">f</span><span class="p">();</span> - <span class="k">try</span> <span class="p">{</span> - <span class="k">match</span> <span class="p">(</span><span class="nv">$i</span><span class="p">)</span> <span class="p">{</span> - <span class="mi">0</span> <span class="o">=></span> <span class="mi">0</span> <span class="o">/</span> <span class="mi">0</span><span class="p">,</span> + <section id="section--_はじめに"> + <h2><a href="#section--_はじめに">はじめに</a></h2> + <p> + 2023 年 3 月 23 日から 25 日にかけて開催予定 (記事執筆時点) の<a xl:href="https://phperkaigi.jp/2023/">PHPerKaigi 2023</a>において、 昨年と同様に、弊社<a xl:href="https://www.dgcircus.com/">デジタルサーカス株式会社</a>からトークン問題を出題予定である。 + </p> + + <p> + 昨年のトークン問題の記事はこちら:<a xl:href="/posts/2022-04-09/phperkaigi-2022-tokens/">PHPerKaigi 2022 トークン問題の解説</a> + </p> + + <p> + すでに 2023 年用の問題は作成済みであるが、その制作過程の中でいくつかボツ問ができた。せっかくなので、PHPerKaigi 開催を待つ間に紹介しようと思う。 + </p> + + <p> + 10 月から 2 月まで、毎月 1 記事ずつ公開していく予定 (忘れていなければ → 忘れていたので 12 月公開予定だった記事を今書いている)。 + </p> + + <ul> + <li> + <p> + その 1 はこちら:<a xl:href="/posts/2022-10-23/phperkaigi-2023-unused-token-quiz-1/">PHPerKaigi 2023: ボツになったトークン問題 その 1</a> + </p> + </li> + + <li> + <p> + その 2 はこちら:<a xl:href="/posts/2022-11-19/phperkaigi-2023-unused-token-quiz-2/">PHPerKaigi 2023: ボツになったトークン問題 その 2</a> + </p> + </li> + </ul> + </section> + + <section id="section--_問題"> + <h2><a href="#section--_問題">問題</a></h2> + <p> + 注意: これはボツ問なので、得られたトークンを PHPerKaigi で入力してもポイントにはならない。 + </p> + + <pre language="php" linenumbering="unnumbered"> + <code><?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, - <span class="mi">15</span><span class="p">,</span> <span class="mi">36</span> <span class="o">=></span> <span class="mi">0</span> <span class="o">/</span> <span class="mi">0</span><span class="p">,</span> - <span class="mi">14</span> <span class="o">=></span> <span class="mi">0</span> <span class="o">/</span> <span class="mi">0</span><span class="p">,</span> - <span class="mi">37</span> <span class="o">=></span> <span class="mi">0</span> <span class="o">/</span> <span class="mi">0</span><span class="p">,</span> + 15, 36 => 0 / 0, + 14 => 0 / 0, + 37 => 0 / 0, @@ -133,16 +111,16 @@ - <span class="mi">6</span> <span class="o">=></span> <span class="mi">0</span> <span class="o">/</span> <span class="mi">0</span><span class="p">,</span> + 6 => 0 / 0, - <span class="mi">5</span> <span class="o">=></span> <span class="mi">0</span> <span class="o">/</span> <span class="mi">0</span><span class="p">,</span> + 5 => 0 / 0, - <span class="mi">22</span> <span class="o">=></span> <span class="mi">0</span> <span class="o">/</span> <span class="mi">0</span><span class="p">,</span> + 22 => 0 / 0, - <span class="mi">34</span><span class="p">,</span> <span class="mi">35</span> <span class="o">=></span> <span class="mi">0</span> <span class="o">/</span> <span class="mi">0</span><span class="p">,</span> + 34, 35 => 0 / 0, @@ -151,10 +129,10 @@ - <span class="mi">25</span> <span class="o">=></span> <span class="mi">0</span> <span class="o">/</span> <span class="mi">0</span><span class="p">,</span> - <span class="mi">17</span><span class="p">,</span> <span class="mi">21</span> <span class="o">=></span> <span class="mi">0</span> <span class="o">/</span> <span class="mi">0</span><span class="p">,</span> + 25 => 0 / 0, + 17, 21 => 0 / 0, - <span class="mi">24</span><span class="p">,</span> <span class="mi">32</span> <span class="o">=></span> <span class="mi">0</span> <span class="o">/</span> <span class="mi">0</span><span class="p">,</span> + 24, 32 => 0 / 0, @@ -162,12 +140,12 @@ - <span class="mi">33</span> <span class="o">=></span> <span class="mi">0</span> <span class="o">/</span> <span class="mi">0</span><span class="p">,</span> + 33 => 0 / 0, - <span class="mi">16</span> <span class="o">=></span> <span class="mi">0</span> <span class="o">/</span> <span class="mi">0</span><span class="p">,</span> + 16 => 0 / 0, - <span class="mi">18</span> <span class="o">=></span> <span class="mi">0</span> <span class="o">/</span> <span class="mi">0</span><span class="p">,</span> + 18 => 0 / 0, @@ -176,37 +154,37 @@ - <span class="mi">7</span> <span class="o">=></span> <span class="mi">0</span> <span class="o">/</span> <span class="mi">0</span><span class="p">,</span> + 7 => 0 / 0, - <span class="mi">2</span> <span class="o">=></span> <span class="mi">0</span> <span class="o">/</span> <span class="mi">0</span><span class="p">,</span> - <span class="mi">1</span><span class="p">,</span> <span class="mi">20</span> <span class="o">=></span> <span class="mi">0</span> <span class="o">/</span> <span class="mi">0</span><span class="p">,</span> - <span class="mi">10</span><span class="p">,</span> <span class="mi">28</span> <span class="o">=></span> <span class="mi">0</span> <span class="o">/</span> <span class="mi">0</span><span class="p">,</span> - <span class="mi">8</span><span class="p">,</span> <span class="mi">12</span><span class="p">,</span> <span class="mi">26</span> <span class="o">=></span> <span class="mi">0</span> <span class="o">/</span> <span class="mi">0</span><span class="p">,</span> - <span class="mi">4</span><span class="p">,</span> <span class="mi">9</span><span class="p">,</span> <span class="mi">13</span> <span class="o">=></span> <span class="mi">0</span> <span class="o">/</span> <span class="mi">0</span><span class="p">,</span> + 2 => 0 / 0, + 1, 20 => 0 / 0, + 10, 28 => 0 / 0, + 8, 12, 26 => 0 / 0, + 4, 9, 13 => 0 / 0, - <span class="mi">31</span> <span class="o">=></span> <span class="mi">0</span> <span class="o">/</span> <span class="mi">0</span><span class="p">,</span> + 31 => 0 / 0, - <span class="mi">29</span> <span class="o">=></span> <span class="mi">0</span> <span class="o">/</span> <span class="mi">0</span><span class="p">,</span> + 29 => 0 / 0, - <span class="mi">11</span> <span class="o">=></span> <span class="mi">0</span> <span class="o">/</span> <span class="mi">0</span><span class="p">,</span> + 11 => 0 / 0, - <span class="mi">3</span><span class="p">,</span> <span class="mi">19</span><span class="p">,</span> <span class="mi">23</span> <span class="o">=></span> <span class="mi">0</span> <span class="o">/</span> <span class="mi">0</span><span class="p">,</span> + 3, 19, 23 => 0 / 0, - <span class="mi">27</span> <span class="o">=></span> <span class="mi">0</span> <span class="o">/</span> <span class="mi">0</span><span class="p">,</span> + 27 => 0 / 0, - <span class="mi">30</span> <span class="o">=></span> <span class="mi">0</span> <span class="o">/</span> <span class="mi">0</span><span class="p">,</span> - <span class="p">};</span> - <span class="p">}</span> <span class="k">finally</span> <span class="p">{</span> - <span class="nf">f</span><span class="p">(</span><span class="nv">$i</span> <span class="o">-</span> <span class="mi">1</span><span class="p">);</span> - <span class="p">}</span> -<span class="p">}</span> + 30 => 0 / 0, + }; + } finally { + f($i - 1); + } + } @@ -214,250 +192,238 @@ -<span class="k">function</span> <span class="n">g</span><span class="p">()</span> <span class="p">{</span> - <span class="k">return</span> <span class="k">__LINE__</span><span class="p">;</span> -<span class="p">}</span></code></pre> -</div> -</div> -<div class="paragraph"> -<p>"Catchline" と名付けた作品。実行するとトークン <code>#base64_decode('SGVsbG8sIFdvcmxkIQ==')</code> が得られる。</p> -</div> -<div class="paragraph"> -<p>トークンは PHP の式になっていて、評価すると <code>Hello, World!</code> という文字列になる。PHPer チャレンジのトークンには空白を含められないという制約があるが、こういった形でトークンにすれば回避できる。</p> -</div> - </div> -</section> -<section class="section-1"> - <h2 id="" class="section-header"> - - 解説 - - </h2> - <div class="section-body"> - <section class="section-2"> - <h3 id="" class="section-header"> - - 概要 - - </h3> - <div class="section-body"> - <div class="paragraph"> -<p>例外が発生した行数にデータをエンコードし、それを <code>catch</code> で捕まえて表示している。</p> -</div> - </div> -</section> -<section class="section-2"> - <h3 id="" class="section-header"> - - 例外オブジェクトの連鎖 - - </h3> - <div class="section-body"> - <div class="paragraph"> -<p><a href="https://www.php.net/class.Exception"><code>Exception</code></a> や <a href="https://www.php.net/class.Error"><code>Error</code></a> には <code>$previous</code> というプロパティがあり、コンストラクタの第3引数から渡すことができる。主に 2つの用法がある:</p> -</div> -<div class="ulist"> -<ul> -<li> -<p>エラーを処理している途中に起こった別のエラーに、元のエラー情報を含める</p> -</li> -<li> -<p>内部エラーをラップして作られたエラーに、内部エラーの情報を含める</p> -</li> -</ul> -</div> -<div class="paragraph"> -<p>このうち 1つ目のケースは、 <code>finally</code> 節の中でエラーを投げると PHP 処理系が勝手に <code>$previous</code> を設定してくれる。</p> -</div> -<div id="source." class="listingblock"> -<div class="content"> -<pre class="rouge highlight"><code data-lang="php"><span class="o"><?</span><span class="n">php</span> - -<span class="k">try</span> <span class="p">{</span> - <span class="k">try</span> <span class="p">{</span> - <span class="k">throw</span> <span class="k">new</span> <span class="nc">Exception</span><span class="p">(</span><span class="s2">"Error 1"</span><span class="p">);</span> - <span class="p">}</span> <span class="k">finally</span> <span class="p">{</span> - <span class="k">throw</span> <span class="k">new</span> <span class="nc">Exception</span><span class="p">(</span><span class="s2">"Error 2"</span><span class="p">);</span> - <span class="p">}</span> -<span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="nc">Exception</span> <span class="nv">$e</span><span class="p">)</span> <span class="p">{</span> - <span class="k">echo</span> <span class="nv">$e</span><span class="o">-></span><span class="nf">getMessage</span><span class="p">()</span> <span class="mf">.</span> <span class="kc">PHP_EOL</span><span class="p">;</span> - <span class="c1">// => Error 2</span> - <span class="k">echo</span> <span class="nv">$e</span><span class="o">-></span><span class="nf">getPrevious</span><span class="p">()</span><span class="o">-></span><span class="nf">getMessage</span><span class="p">()</span> <span class="mf">.</span> <span class="kc">PHP_EOL</span><span class="p">;</span> - <span class="c1">// => Error 1</span> -<span class="p">}</span></code></pre> -</div> -</div> -<div class="paragraph"> -<p>この知識を元に、トークンの出力部を解析してみる。</p> -</div> - </div> -</section> -<section class="section-2"> - <h3 id="" class="section-header"> - - 出力部の解析 - - </h3> - <div class="section-body"> - <div class="paragraph"> -<p>出力部をコメントや改行を追加して再掲する:</p> -</div> -<div id="source." class="listingblock"> -<div class="content"> -<pre class="rouge highlight"><code data-lang="php"><span class="o"><?</span><span class="n">php</span> -<span class="k">try</span> <span class="p">{</span> - <span class="nf">f</span><span class="p">(</span><span class="nf">g</span><span class="p">()</span> <span class="o">/</span> <span class="k">__LINE__</span><span class="p">);</span> -<span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="nc">Throwable</span> <span class="nv">$e</span><span class="p">)</span> <span class="p">{</span> - <span class="k">while</span> <span class="p">(</span><span class="nv">$e</span> <span class="o">=</span> <span class="nv">$e</span><span class="o">-></span><span class="nf">getPrevious</span><span class="p">())</span> <span class="p">{</span> - <span class="nb">printf</span><span class="p">(</span><span class="s1">'%c'</span><span class="p">,</span> <span class="nv">$e</span><span class="o">-></span><span class="nf">getLine</span><span class="p">()</span> <span class="o">+</span> <span class="mi">23</span><span class="p">);</span> - <span class="p">}</span> - <span class="k">echo</span> <span class="s2">"</span><span class="se">\n</span><span class="s2">"</span><span class="p">;</span> -<span class="p">}</span></code></pre> -</div> -</div> -<div class="paragraph"> -<p>出力をおこなう <code>catch</code> 節を見てみると、 <code>Throwable::getPrevious()</code> を呼び出してエラーチェインを辿り、 <code>Throwable::getLine()</code> でエラーが発生した行数を取得している。その行数に <code>23</code> なるマジックナンバーを足し、フォーマット指定子 <code>%c</code> で出力している。</p> -</div> -<div class="paragraph"> -<p>フォーマット指定子 <code>%c</code> は、整数を ASCII コード<sup class="footnote">[<a id="_footnoteref_1" class="footnote" href="#_footnotedef_1" title="View footnote.">1</a>]</sup> と見做して印字する。トークン <code>#base64_decode('SGVsbG8sIFdvcmxkIQ==')</code> の <code>b</code> であれば、ASCII コード <code>98</code> なので、75 行目で発生したエラー、</p> -</div> -<div id="source." class="listingblock"> -<div class="content"> -<pre class="rouge highlight"><code> 1, 20 => 0 / 0,</code></pre> -</div> -</div> -<div class="paragraph"> -<p>によって表現されている。エラーを起こす方法はいろいろと考えられるが、今回はゼロ除算を使った。</p> -</div> -<div class="paragraph"> -<p>それでは、エラーチェインを作る箇所、関数 <code>f()</code> を見ていく。</p> -</div> - </div> -</section> -<section class="section-2"> - <h3 id="" class="section-header"> - - データ構成部の解析 - - </h3> - <div class="section-body"> - <div class="paragraph"> -<p><code>f()</code> の定義を再掲する (エラーオブジェクトの行数を利用しているので、一部分だけ抜き出すと値が変わることに注意):</p> -</div> -<div id="source." class="listingblock"> -<div class="content"> -<pre class="rouge highlight"><code data-lang="php"><span class="k">function</span> <span class="n">f</span><span class="p">(</span><span class="kt">int</span> <span class="nv">$i</span><span class="p">)</span> <span class="p">{</span> - <span class="k">if</span> <span class="p">(</span><span class="nv">$i</span> <span class="o"><</span> <span class="mi">0</span><span class="p">)</span> <span class="nf">f</span><span class="p">();</span> - <span class="k">try</span> <span class="p">{</span> - <span class="k">match</span> <span class="p">(</span><span class="nv">$i</span><span class="p">)</span> <span class="p">{</span> - <span class="mi">0</span> <span class="o">=></span> <span class="mi">0</span> <span class="o">/</span> <span class="mi">0</span><span class="p">,</span> <span class="c1">// 12 行目</span> - - - - <span class="mi">15</span><span class="p">,</span> <span class="mi">36</span> <span class="o">=></span> <span class="mi">0</span> <span class="o">/</span> <span class="mi">0</span><span class="p">,</span> - <span class="mi">14</span> <span class="o">=></span> <span class="mi">0</span> <span class="o">/</span> <span class="mi">0</span><span class="p">,</span> - <span class="mi">37</span> <span class="o">=></span> <span class="mi">0</span> <span class="o">/</span> <span class="mi">0</span><span class="p">,</span> - - <span class="c1">// (略)</span> - - <span class="mi">30</span> <span class="o">=></span> <span class="mi">0</span> <span class="o">/</span> <span class="mi">0</span><span class="p">,</span> <span class="c1">// 97 行目</span> - <span class="p">};</span> - <span class="p">}</span> <span class="k">finally</span> <span class="p">{</span> - <span class="nf">f</span><span class="p">(</span><span class="nv">$i</span> <span class="o">-</span> <span class="mi">1</span><span class="p">);</span> - <span class="p">}</span> -<span class="p">}</span></code></pre> -</div> -</div> -<div class="paragraph"> -<p>前述のように、 <code>finally</code> 節でエラーを投げると PHP 処理系が <code>$previous</code> を設定する。ここでは、エラーを繋げるために <code>f()</code> を再帰呼び出ししている。最初に <code>f()</code> を呼び出している箇所を確認すると、</p> -</div> -<div id="source." class="listingblock"> -<div class="content"> -<pre class="rouge highlight"><code data-lang="php"><span class="o"><?</span><span class="n">php</span> -<span class="k">try</span> <span class="p">{</span> - <span class="nf">f</span><span class="p">(</span><span class="nf">g</span><span class="p">()</span> <span class="o">/</span> <span class="k">__LINE__</span><span class="p">);</span> <span class="c1">// 3 行目</span></code></pre> -</div> -</div> -<div id="source." class="listingblock"> -<div class="content"> -<pre class="rouge highlight"><code data-lang="php"><span class="k">function</span> <span class="n">g</span><span class="p">()</span> <span class="p">{</span> - <span class="k">return</span> <span class="k">__LINE__</span><span class="p">;</span> <span class="c1">// 111 行目</span> -<span class="p">}</span></code></pre> -</div> -</div> -<div class="paragraph"> -<p><code>f()</code> には <code>111 / 3</code> で <code>37</code> が渡されることがわかる。そこから 1 ずつ減らして再帰呼び出ししていき、0 より小さくなったら <code>f()</code> を引数なしで呼び出す。引数の数が足りないと呼び出しに失敗するので、再帰はここで止まる。</p> -</div> -<div class="paragraph"> -<p>エラーチェインは、最後に発生したエラーを先頭とした単方向連結リストになっているので、順に</p> -</div> -<div class="olist arabic"> -<ol class="arabic"> -<li> -<p><code>f()</code> の引数が足りないことによる呼び出し失敗</p> -</li> -<li> -<p><code>f(0)</code> の呼び出しで発生したゼロ除算</p> -</li> -<li> -<p><code>f(1)</code> の呼び出しで発生したゼロ除算</p> -</li> -<li> -<p>…​</p> -</li> -<li> -<p><code>f(37)</code> の呼び出しで発生したゼロ除算</p> -</li> -</ol> -</div> -<div class="paragraph"> -<p>となっている。出力の際は <code>catch</code> したエラーの <code>getPrevious()</code> から処理を始めるので、1 番目の <code>f()</code> によるエラーは無視され、 <code>f(0)</code> によるエラー、 <code>f(1)</code> によるエラー、 <code>f(2)</code> によるエラー、と出力が進む。</p> -</div> -<div class="paragraph"> -<p><code>f()</code> に <code>0</code> を渡したときは 12 行目にある <code>match</code> の <code>0</code> でゼロ除算が起こるので、行数が 12 となったエラーが投げられる。出力部ではこれに 23 を足した数を ASCII コードとして表示しているのだった。 <code>12 + 23</code> は <code>35</code>、ASCII コードでは <code>#</code> である。これがトークンの 1文字目にあたる。</p> -</div> - </div> -</section> - </div> -</section> -<section class="section-1"> - <h2 id="" class="section-header"> - - おわりに - - </h2> - <div class="section-body"> - <div class="paragraph"> -<p>「行数」というのはトークン文字列をデコードする対象として優れている。</p> -</div> -<div class="ulist"> -<ul> -<li> -<p>トークンの一部や全部が陽に現れない</p> -</li> -<li> -<p><code>__LINE__</code> で容易に取得できる</p> -</li> -</ul> -</div> -<div class="paragraph"> -<p>しかし、こういった「変な」プログラムを何度も読んだり書いたりしていると、 <code>__LINE__</code> を使うのはあまりにありきたりで退屈になる。では、他に行数を取得する手段はないか。こうして <code>Throwable</code> を思いつき、続けてエラーオブジェクトには <code>$previous</code> があることを思い出した。</p> -</div> -<div class="paragraph"> -<p>今回エラーを投げるのにゼロ除算を用いたのは、それがエラーを投げる最も短いコードだと考えたからである。もし 3バイト未満で <code>Throwable</code> なオブジェクトを投げる手段をご存じのかたがいらっしゃれば、ぜひご教示いただきたい。……と締める予定だったのだが、<code>0/0</code> のところを存在しない定数にすれば、簡単に 1バイトを達成できた。ゼロ除算している箇所はちょうど 26 箇所あるので、アルファベットにでもしておけば意味ありげで良かったかもしれない。</p> -</div> - </div> -</section> - </div> - - <div id="footnotes"> + function g() { + return __LINE__; + }</code> + </pre> + + <p> + "Catchline" と名付けた作品。実行するとトークン<code>#base64_decode('SGVsbG8sIFdvcmxkIQ==')</code>が得られる。 + </p> + + <p> + トークンは PHP の式になっていて、評価すると<code>Hello, World!</code>という文字列になる。PHPer チャレンジのトークンには空白を含められないという制約があるが、こういった形でトークンにすれば回避できる。 + </p> + </section> + + <section id="section--_解説"> + <h2><a href="#section--_解説">解説</a></h2> + <section id="section--_概要"> + <h3><a href="#section--_概要">概要</a></h3> + <p> + 例外が発生した行数にデータをエンコードし、それを<code>catch</code>で捕まえて表示している。 + </p> + </section> + + <section id="section--_例外オブジェクトの連鎖"> + <h3><a href="#section--_例外オブジェクトの連鎖">例外オブジェクトの連鎖</a></h3> + <p> + <a xl:href="https://www.php.net/class.Exception"><code>Exception</code></a>や<a xl:href="https://www.php.net/class.Error"><code>Error</code></a>には<code>$previous</code>というプロパティがあり、コンストラクタの第3引数から渡すことができる。主に 2つの用法がある: + </p> + + <ul> + <li> + <p> + エラーを処理している途中に起こった別のエラーに、元のエラー情報を含める + </p> + </li> + + <li> + <p> + 内部エラーをラップして作られたエラーに、内部エラーの情報を含める + </p> + </li> + </ul> + + <p> + このうち 1つ目のケースは、<code>finally</code>節の中でエラーを投げると PHP 処理系が勝手に<code>$previous</code>を設定してくれる。 + </p> + + <pre language="php" linenumbering="unnumbered"> + <code><?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 + }</code> + </pre> + + <p> + この知識を元に、トークンの出力部を解析してみる。 + </p> + </section> - <div class="footnote" id="_footnotedef_1"> - <a href="#_footnoteref_1">1</a>. RAS syndrome - </div> + <section id="section--_出力部の解析"> + <h3><a href="#section--_出力部の解析">出力部の解析</a></h3> + <p> + 出力部をコメントや改行を追加して再掲する: + </p> + + <pre language="php" linenumbering="unnumbered"> + <code><?php + try { + f(g() / __LINE__); + } catch (Throwable $e) { + while ($e = $e->getPrevious()) { + printf('%c', $e->getLine() + 23); + } + echo "\n"; + }</code> + </pre> + + <p> + 出力をおこなう<code>catch</code>節を見てみると、<code>Throwable::getPrevious()</code>を呼び出してエラーチェインを辿り、<code>Throwable::getLine()</code>でエラーが発生した行数を取得している。その行数に<code>23</code>なるマジックナンバーを足し、フォーマット指定子<code>%c</code>で出力している。 + </p> + + <p> + フォーマット指定子<code>%c</code>は、整数を ASCII コード<span></span>と見做して印字する。トークン<code>#base64_decode('SGVsbG8sIFdvcmxkIQ==')</code>の<code>b</code>であれば、ASCII コード<code>98</code>なので、75 行目で発生したエラー、 + </p> + + <pre language="php" linenumbering="unnumbered"> + <code> 1, 20 => 0 / 0,</code> + </pre> + + <p> + によって表現されている。エラーを起こす方法はいろいろと考えられるが、今回はゼロ除算を使った。 + </p> + + <p> + それでは、エラーチェインを作る箇所、関数<code>f()</code>を見ていく。 + </p> + </section> + + <section id="section--_データ構成部の解析"> + <h3><a href="#section--_データ構成部の解析">データ構成部の解析</a></h3> + <p> + <code>f()</code>の定義を再掲する (エラーオブジェクトの行数を利用しているので、一部分だけ抜き出すと値が変わることに注意): + </p> + + <pre language="php" linenumbering="unnumbered"> + <code>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); + } + }</code> + </pre> + + <p> + 前述のように、<code>finally</code>節でエラーを投げると PHP 処理系が<code>$previous</code>を設定する。ここでは、エラーを繋げるために<code>f()</code>を再帰呼び出ししている。最初に<code>f()</code>を呼び出している箇所を確認すると、 + </p> + + <pre language="php" linenumbering="unnumbered"> + <code><?php + try { + f(g() / __LINE__); // 3 行目</code> + </pre> + + <pre language="php" linenumbering="unnumbered"> + <code>function g() { + return __LINE__; // 111 行目 + }</code> + </pre> + + <p> + <code>f()</code>には<code>111 / 3</code>で<code>37</code>が渡されることがわかる。そこから 1 ずつ減らして再帰呼び出ししていき、0 より小さくなったら<code>f()</code>を引数なしで呼び出す。引数の数が足りないと呼び出しに失敗するので、再帰はここで止まる。 + </p> + + <p> + エラーチェインは、最後に発生したエラーを先頭とした単方向連結リストになっているので、順に + </p> + + <ol numeration="arabic"> + <li> + <p> + <code>f()</code>の引数が足りないことによる呼び出し失敗 + </p> + </li> + + <li> + <p> + <code>f(0)</code>の呼び出しで発生したゼロ除算 + </p> + </li> + + <li> + <p> + <code>f(1)</code>の呼び出しで発生したゼロ除算 + </p> + </li> + + <li> + <p> + &#8230;&#8203; + </p> + </li> + + <li> + <p> + <code>f(37)</code>の呼び出しで発生したゼロ除算 + </p> + </li> + </ol> + + <p> + となっている。出力の際は<code>catch</code>したエラーの<code>getPrevious()</code>から処理を始めるので、1 番目の<code>f()</code>によるエラーは無視され、<code>f(0)</code>によるエラー、<code>f(1)</code>によるエラー、<code>f(2)</code>によるエラー、と出力が進む。 + </p> + + <p> + <code>f()</code>に<code>0</code>を渡したときは 12 行目にある<code>match</code>の<code>0</code>でゼロ除算が起こるので、行数が 12 となったエラーが投げられる。出力部ではこれに 23 を足した数を ASCII コードとして表示しているのだった。<code>12 + 23</code>は<code>35</code>、ASCII コードでは<code>#</code>である。これがトークンの 1文字目にあたる。 + </p> + </section> + </section> + + <section id="section--_おわりに"> + <h2><a href="#section--_おわりに">おわりに</a></h2> + <p> + 「行数」というのはトークン文字列をデコードする対象として優れている。 + </p> + + <ul> + <li> + <p> + トークンの一部や全部が陽に現れない + </p> + </li> + + <li> + <p> + <code>__LINE__</code>で容易に取得できる + </p> + </li> + </ul> + + <p> + しかし、こういった「変な」プログラムを何度も読んだり書いたりしていると、<code>__LINE__</code>を使うのはあまりにありきたりで退屈になる。では、他に行数を取得する手段はないか。こうして<code>Throwable</code>を思いつき、続けてエラーオブジェクトには<code>$previous</code>があることを思い出した。 + </p> - </div> - + <p> + 今回エラーを投げるのにゼロ除算を用いたのは、それがエラーを投げる最も短いコードだと考えたからである。もし 3バイト未満で<code>Throwable</code>なオブジェクトを投げる手段をご存じのかたがいらっしゃれば、ぜひご教示いただきたい。……と締める予定だったのだが、<code>0/0</code>のところを存在しない定数にすれば、簡単に 1バイトを達成できた。ゼロ除算している箇所はちょうど 26 箇所あるので、アルファベットにでもしておけば意味ありげで良かったかもしれない。 + </p> + </section> + </div> </article> </main> <footer class="footer"> diff --git a/public/posts/index.html b/public/posts/index.html index a559e9b..28f36e5 100644 --- a/public/posts/index.html +++ b/public/posts/index.html @@ -8,13 +8,9 @@ <meta name="description" content="投稿した記事の一覧"> <link rel="icon" type="image/svg+xml" href="/favicon.svg"> <title>投稿一覧 | REPL: Rest-Eat-Program Loop</title> - - <link rel="stylesheet" href="/hl.css?208c52e3b7c9db1cad782c5d30b4698f"> - - <link rel="stylesheet" href="/style.css?779b1a3debcaeba619f6fe500e93d525"> - - <link rel="stylesheet" href="/custom.css?a649ea3528d4b626fb636505d94c1144"> - + <link rel="stylesheet" href="/hl.css?h=208c52e3b7c9db1cad782c5d30b4698f"> + <link rel="stylesheet" href="/style.css?h=779b1a3debcaeba619f6fe500e93d525"> + <link rel="stylesheet" href="/custom.css?h=a649ea3528d4b626fb636505d94c1144"> </head> <body class="list"> <header class="header"> @@ -28,311 +24,272 @@ <header class="page-header"> <h1>投稿一覧</h1> </header> - - <article class="post-entry"> - <a href="/posts/2023-01-10/phperkaigi-2023-unused-token-quiz-3/"> - <header class="entry-header"> - <h2>PHPerKaigi 2023: ボツになったトークン問題 その 3</h2> - </header> - <section class="entry-content"> - <p> - 来年の PHPerKaigi 2023 でデジタルサーカス株式会社から出題予定のトークン問題のうち、 ボツになった問題を公開する (その 3)。 - </p> - </section> - <footer class="entry-footer"> - Posted on <time datetime="2023-01-10">2023-01-10</time>, updated on <time datetime="2023-01-10">2023-01-10</time> - </footer> - </a> - </article> - - <article class="post-entry"> - <a href="/posts/2022-11-19/phperkaigi-2023-unused-token-quiz-2/"> - <header class="entry-header"> - <h2>PHPerKaigi 2023: ボツになったトークン問題 その 2</h2> - </header> - <section class="entry-content"> - <p> - 来年の PHPerKaigi 2023 でデジタルサーカス株式会社から出題予定のトークン問題のうち、 ボツになった問題を公開する (その 2)。 - </p> - </section> - <footer class="entry-footer"> - Posted on <time datetime="2022-11-19">2022-11-19</time> - </footer> - </a> - </article> - - <article class="post-entry"> - <a href="/posts/2022-10-28/setup-server-for-this-site/"> - <header class="entry-header"> - <h2>【備忘録】このサイト用の VPS をセットアップしたときのメモ</h2> - </header> - <section class="entry-content"> - <p> - GitHub Pages でホストしていたこのサイトを VPS へ移行したので、 そのときにやったことのメモ。99 % 自分用。 - </p> - </section> - <footer class="entry-footer"> - Posted on <time datetime="2022-10-28">2022-10-28</time> - </footer> - </a> - </article> - - <article class="post-entry"> - <a href="/posts/2022-10-23/phperkaigi-2023-unused-token-quiz-1/"> - <header class="entry-header"> - <h2>PHPerKaigi 2023: ボツになったトークン問題 その 1</h2> - </header> - <section class="entry-content"> - <p> - 来年の PHPerKaigi 2023 でデジタルサーカス株式会社から出題予定のトークン問題のうち、 ボツになった問題を公開する (その 1)。 - </p> - </section> - <footer class="entry-footer"> - Posted on <time datetime="2022-10-23">2022-10-23</time> - </footer> - </a> - </article> - - <article class="post-entry"> - <a href="/posts/2022-09-29/write-fizzbuzz-in-php-2-letters-per-line/"> - <header class="entry-header"> - <h2>【PHP】fizzbuzz を書く。1行あたり2文字で。</h2> - </header> - <section class="entry-content"> - <p> - PHP で、fizzbuzz を書いた。ただし、1行あたりに使える文字数は2文字まで。 - </p> - </section> - <footer class="entry-footer"> - Posted on <time datetime="2022-09-28">2022-09-28</time>, updated on <time datetime="2022-09-29">2022-09-29</time> - </footer> - </a> - </article> - - <article class="post-entry"> - <a href="/posts/2022-08-31/support-for-communty-is-employee-benefits/"> - <header class="entry-header"> - <h2>弊社の PHP Foundation への寄付に寄せて</h2> - </header> - <section class="entry-content"> - <p> - 先日、私の勤めるデジタルサーカス株式会社が、PHP Foundation へ寄付をおこないました。 本件を社内でしつこく推進した1人として、推進の理由等を書き残しておきます。 - </p> - </section> - <footer class="entry-footer"> - Posted on <time datetime="2022-08-31">2022-08-31</time> - </footer> - </a> - </article> - - <article class="post-entry"> - <a href="/posts/2022-08-27/php-conference-okinawa-code-golf/"> - <header class="entry-header"> - <h2>PHP カンファレンス沖縄で出題されたコードゴルフの問題を解いてみた</h2> - </header> - <section class="entry-content"> - <p> - PHP カンファレンス沖縄の懇親会 LT で出題されたコードゴルフの問題を解いてみた。 - </p> - </section> - <footer class="entry-footer"> - Posted on <time datetime="2022-08-27">2022-08-27</time> - </footer> - </a> - </article> - - <article class="post-entry"> - <a href="/posts/2022-05-01/phperkaigi-2022/"> - <header class="entry-header"> - <h2>PHPerKaigi 2022</h2> - </header> - <section class="entry-content"> - <p> - 2022-04-09 から 2022-04-11 にかけて開催された、PHPerKaigi 2022 に参加した。 - </p> - </section> - <footer class="entry-footer"> - Posted on <time datetime="2022-05-01">2022-05-01</time> - </footer> - </a> - </article> - - <article class="post-entry"> - <a href="/posts/2022-04-24/term-banner-write-tool-showing-banner-in-terminal/"> - <header class="entry-header"> - <h2>term-banner: ターミナルにバナーを表示するツールを書いた</h2> - </header> - <section class="entry-content"> - <p> - ターミナルに任意の文字のバナーを表示するためのツールを Go で書いた。 - </p> - </section> - <footer class="entry-footer"> - Posted on <time datetime="2022-04-24">2022-04-24</time>, updated on <time datetime="2022-04-27">2022-04-27</time> - </footer> - </a> - </article> - - <article class="post-entry"> - <a href="/posts/2022-04-09/phperkaigi-2022-tokens/"> - <header class="entry-header"> - <h2>PHPerKaigi 2022 トークン問題の解説</h2> - </header> - <section class="entry-content"> - <p> - PHPerKaigi 2022 で私が作成した PHPer チャレンジ問題を解説する。 - </p> - </section> - <footer class="entry-footer"> - Posted on <time datetime="2022-04-09">2022-04-09</time>, updated on <time datetime="2022-04-16">2022-04-16</time> - </footer> - </a> - </article> - - <article class="post-entry"> - <a href="/posts/2021-10-02/vim-swap-order-of-selected-lines/"> - <header class="entry-header"> - <h2>Vimで選択した行の順番を入れ替える</h2> - </header> - <section class="entry-content"> - <p> - Vim で選択した行の順番を入れ替える方法。 - </p> - </section> - <footer class="entry-footer"> - Posted on <time datetime="2021-10-02">2021-10-02</time> - </footer> - </a> - </article> - - <article class="post-entry"> - <a href="/posts/2021-10-02/vim-difference-between-autocmd-bufwrite-and-bufwritepre/"> - <header class="entry-header"> - <h2>【Vim】autocmd events の BufWrite/BufWritePre の違い</h2> - </header> - <section class="entry-content"> - <p> - Vim の autocmd events における BufWrite/BufWritePre がどう違うのかを調べた結果、 違いはないことがわかった。 - </p> - </section> - <footer class="entry-footer"> - Posted on <time datetime="2021-10-02">2021-10-02</time> - </footer> - </a> - </article> - - <article class="post-entry"> - <a href="/posts/2021-10-02/rust-where-are-primitive-types-from/"> - <header class="entry-header"> - <h2>Rust のプリミティブ型はどこからやって来るか</h2> - </header> - <section class="entry-content"> - <p> - Rust のプリミティブ型は予約語ではなく普通の識別子である。 どのようにこれが名前解決されるのかを調べた。 - </p> - </section> - <footer class="entry-footer"> - Posted on <time datetime="2021-10-02">2021-10-02</time> - </footer> - </a> - </article> - - <article class="post-entry"> - <a href="/posts/2021-10-02/ruby-then-keyword-and-case-in/"> - <header class="entry-header"> - <h2>【Ruby】then キーワードと case in</h2> - </header> - <section class="entry-content"> - <p> - Ruby 3.0 で追加される case in 構文と、then キーワードについて。 - </p> - </section> - <footer class="entry-footer"> - Posted on <time datetime="2021-10-02">2021-10-02</time> - </footer> - </a> - </article> - - <article class="post-entry"> - <a href="/posts/2021-10-02/ruby-detect-running-implementation/"> - <header class="entry-header"> - <h2>【Ruby】自身を実行している処理系の種類を判定する</h2> - </header> - <section class="entry-content"> - <p> - Ruby には複数の実装があるが、自身を実行している処理系の種類を スクリプト上からどのように判定すればよいだろうか。 - </p> - </section> - <footer class="entry-footer"> - Posted on <time datetime="2021-10-02">2021-10-02</time> - </footer> - </a> - </article> - - <article class="post-entry"> - <a href="/posts/2021-10-02/python-unbound-local-error/"> - <header class="entry-header"> - <h2>【Python】クロージャとUnboundLocalError: local variable 'x' referenced before assignment</h2> - </header> - <section class="entry-content"> - <p> - Python における UnboundLocalError の理由と対処法。 - </p> - </section> - <footer class="entry-footer"> - Posted on <time datetime="2021-10-02">2021-10-02</time> - </footer> - </a> - </article> - - <article class="post-entry"> - <a href="/posts/2021-10-02/cpp-you-can-use-keywords-in-attributes/"> - <header class="entry-header"> - <h2>【C++】属性構文の属性名にはキーワードが使える</h2> - </header> - <section class="entry-content"> - <p> - C++ の属性構文の属性名には、キーワードが使える。ネタ記事。 - </p> - </section> - <footer class="entry-footer"> - Posted on <time datetime="2021-10-02">2021-10-02</time> - </footer> - </a> - </article> - - <article class="post-entry"> - <a href="/posts/2021-03-30/phperkaigi-2021/"> - <header class="entry-header"> - <h2>PHPerKaigi 2021</h2> - </header> - <section class="entry-content"> - <p> - 2021-03-26 から 2021-03-28 にかけて開催された、PHPerKaigi 2021 に参加した。 - </p> - </section> - <footer class="entry-footer"> - Posted on <time datetime="2021-03-30">2021-03-30</time> - </footer> - </a> - </article> - - <article class="post-entry"> - <a href="/posts/2021-03-05/my-first-post/"> - <header class="entry-header"> - <h2>My First Post</h2> - </header> - <section class="entry-content"> - <p> - これはテスト投稿です。これはテスト投稿です。これはテスト投稿です。 - </p> - </section> - <footer class="entry-footer"> - Posted on <time datetime="2021-03-05">2021-03-05</time> - </footer> - </a> - </article> - + <article class="post-entry"> + <a href="/posts/2023-01-10/phperkaigi-2023-unused-token-quiz-3/"> <header class="entry-header"> + <h2>PHPerKaigi 2023: ボツになったトークン問題 その 3</h2> + </header> + <section class="entry-content"> + <p> + 来年の PHPerKaigi 2023 でデジタルサーカス株式会社から出題予定のトークン問題のうち、ボツになった問題を公開する (その 3)。 + </p> + </section> + <footer class="entry-footer"> + Posted on<time datetime="2023-01-10">2023-01-10</time> + </footer> +</a> + </article> + <article class="post-entry"> + <a href="/posts/2022-11-19/phperkaigi-2023-unused-token-quiz-2/"> <header class="entry-header"> + <h2>PHPerKaigi 2023: ボツになったトークン問題 その 2</h2> + </header> + <section class="entry-content"> + <p> + 来年の PHPerKaigi 2023 でデジタルサーカス株式会社から出題予定のトークン問題のうち、ボツになった問題を公開する (その 2)。 + </p> + </section> + <footer class="entry-footer"> + Posted on<time datetime="2022-11-19">2022-11-19</time> + </footer> +</a> + </article> + <article class="post-entry"> + <a href="/posts/2022-10-28/setup-server-for-this-site/"> <header class="entry-header"> + <h2>【備忘録】 このサイト用の VPS をセットアップしたときのメモ</h2> + </header> + <section class="entry-content"> + <p> + GitHub Pages でホストしていたこのサイトを VPS へ移行したので、そのときにやったことのメモ。99 % 自分用。 + </p> + </section> + <footer class="entry-footer"> + Posted on<time datetime="2022-10-28">2022-10-28</time> + </footer> +</a> + </article> + <article class="post-entry"> + <a href="/posts/2022-10-23/phperkaigi-2023-unused-token-quiz-1/"> <header class="entry-header"> + <h2>PHPerKaigi 2023: ボツになったトークン問題 その 1</h2> + </header> + <section class="entry-content"> + <p> + 来年の PHPerKaigi 2023 でデジタルサーカス株式会社から出題予定のトークン問題のうち、ボツになった問題を公開する (その 1)。 + </p> + </section> + <footer class="entry-footer"> + Posted on<time datetime="2022-10-23">2022-10-23</time> + </footer> +</a> + </article> + <article class="post-entry"> + <a href="/posts/2022-09-29/write-fizzbuzz-in-php-2-letters-per-line/"> <header class="entry-header"> + <h2>【PHP】 fizzbuzz を書く。1行あたり2文字で。</h2> + </header> + <section class="entry-content"> + <p> + PHP で、fizzbuzz を書いた。ただし、1行あたりに使える文字数は2文字まで。 + </p> + </section> + <footer class="entry-footer"> + Posted on<time datetime="2022-09-28">2022-09-28</time>, updated on<time datetime="2022-09-29">2022-09-29</time> + </footer> +</a> + </article> + <article class="post-entry"> + <a href="/posts/2022-08-31/support-for-communty-is-employee-benefits/"> <header class="entry-header"> + <h2>弊社の PHP Foundation への寄付に寄せて</h2> + </header> + <section class="entry-content"> + <p> + 先日、私の勤めるデジタルサーカス株式会社が、PHP Foundation へ寄付をおこないました。本件を社内でしつこく推進した1人として、推進の理由等を書き残しておきます。 + </p> + </section> + <footer class="entry-footer"> + Posted on<time datetime="2022-08-31">2022-08-31</time> + </footer> +</a> + </article> + <article class="post-entry"> + <a href="/posts/2022-08-27/php-conference-okinawa-code-golf/"> <header class="entry-header"> + <h2>PHP カンファレンス沖縄で出題されたコードゴルフの問題を解いてみた</h2> + </header> + <section class="entry-content"> + <p> + PHP カンファレンス沖縄の懇親会 LT で出題されたコードゴルフの問題を解いてみた。 + </p> + </section> + <footer class="entry-footer"> + Posted on<time datetime="2022-08-27">2022-08-27</time> + </footer> +</a> + </article> + <article class="post-entry"> + <a href="/posts/2022-05-01/phperkaigi-2022/"> <header class="entry-header"> + <h2>PHPerKaigi 2022</h2> + </header> + <section class="entry-content"> + <p> + 2022-04-09 から 2022-04-11 にかけて開催された、PHPerKaigi 2022 に参加した。 + </p> + </section> + <footer class="entry-footer"> + Posted on<time datetime="2022-05-01">2022-05-01</time> + </footer> +</a> + </article> + <article class="post-entry"> + <a href="/posts/2022-04-24/term-banner-write-tool-showing-banner-in-terminal/"> <header class="entry-header"> + <h2>term-banner: ターミナルにバナーを表示するツールを書いた</h2> + </header> + <section class="entry-content"> + <p> + ターミナルに任意の文字のバナーを表示するためのツールを Go で書いた。 + </p> + </section> + <footer class="entry-footer"> + Posted on<time datetime="2022-04-24">2022-04-24</time>, updated on<time datetime="2022-04-27">2022-04-27</time> + </footer> +</a> + </article> + <article class="post-entry"> + <a href="/posts/2022-04-09/phperkaigi-2022-tokens/"> <header class="entry-header"> + <h2>PHPerKaigi 2022 トークン問題の解説</h2> + </header> + <section class="entry-content"> + <p> + PHPerKaigi 2022 で私が作成した PHPer チャレンジ問題を解説する。 + </p> + </section> + <footer class="entry-footer"> + Posted on<time datetime="2022-04-09">2022-04-09</time>, updated on<time datetime="2022-04-16">2022-04-16</time> + </footer> +</a> + </article> + <article class="post-entry"> + <a href="/posts/2021-10-02/cpp-you-can-use-keywords-in-attributes/"> <header class="entry-header"> + <h2>【C++】 属性構文の属性名にはキーワードが使える</h2> + </header> + <section class="entry-content"> + <p> + C++ の属性構文の属性名には、キーワードが使える。ネタ記事。 + </p> + </section> + <footer class="entry-footer"> + Posted on<time datetime="2021-10-02">2021-10-02</time> + </footer> +</a> + </article> + <article class="post-entry"> + <a href="/posts/2021-10-02/python-unbound-local-error/"> <header class="entry-header"> + <h2>【Python】 クロージャとUnboundLocalError: local variable 'x' referenced before assignment</h2> + </header> + <section class="entry-content"> + <p> + Python における UnboundLocalError の理由と対処法。 + </p> + </section> + <footer class="entry-footer"> + Posted on<time datetime="2021-10-02">2021-10-02</time> + </footer> +</a> + </article> + <article class="post-entry"> + <a href="/posts/2021-10-02/ruby-detect-running-implementation/"> <header class="entry-header"> + <h2>【Ruby】 自身を実行している処理系の種類を判定する</h2> + </header> + <section class="entry-content"> + <p> + Ruby には複数の実装があるが、自身を実行している処理系の種類をスクリプト上からどのように判定すればよいだろうか。 + </p> + </section> + <footer class="entry-footer"> + Posted on<time datetime="2021-10-02">2021-10-02</time> + </footer> +</a> + </article> + <article class="post-entry"> + <a href="/posts/2021-10-02/ruby-then-keyword-and-case-in/"> <header class="entry-header"> + <h2>【Ruby】 then キーワードと case in</h2> + </header> + <section class="entry-content"> + <p> + Ruby 3.0 で追加される case in 構文と、then キーワードについて。 + </p> + </section> + <footer class="entry-footer"> + Posted on<time datetime="2021-10-02">2021-10-02</time> + </footer> +</a> + </article> + <article class="post-entry"> + <a href="/posts/2021-10-02/rust-where-are-primitive-types-from/"> <header class="entry-header"> + <h2>Rust のプリミティブ型はどこからやって来るか</h2> + </header> + <section class="entry-content"> + <p> + Rust のプリミティブ型は予約語ではなく普通の識別子である。どのようにこれが名前解決されるのかを調べた。 + </p> + </section> + <footer class="entry-footer"> + Posted on<time datetime="2021-10-02">2021-10-02</time> + </footer> +</a> + </article> + <article class="post-entry"> + <a href="/posts/2021-10-02/vim-difference-between-autocmd-bufwrite-and-bufwritepre/"> <header class="entry-header"> + <h2>【Vim】 autocmd events の BufWrite/BufWritePre の違い</h2> + </header> + <section class="entry-content"> + <p> + Vim の autocmd events における BufWrite/BufWritePre がどう違うのかを調べた結果、違いはないことがわかった。 + </p> + </section> + <footer class="entry-footer"> + Posted on<time datetime="2021-10-02">2021-10-02</time> + </footer> +</a> + </article> + <article class="post-entry"> + <a href="/posts/2021-10-02/vim-swap-order-of-selected-lines/"> <header class="entry-header"> + <h2>Vimで選択した行の順番を入れ替える</h2> + </header> + <section class="entry-content"> + <p> + Vim で選択した行の順番を入れ替える方法。 + </p> + </section> + <footer class="entry-footer"> + Posted on<time datetime="2021-10-02">2021-10-02</time> + </footer> +</a> + </article> + <article class="post-entry"> + <a href="/posts/2021-03-30/phperkaigi-2021/"> <header class="entry-header"> + <h2>PHPerKaigi 2021</h2> + </header> + <section class="entry-content"> + <p> + 2021-03-26 から 2021-03-28 にかけて開催された、PHPerKaigi 2021 に参加した。 + </p> + </section> + <footer class="entry-footer"> + Posted on<time datetime="2021-03-30">2021-03-30</time> + </footer> +</a> + </article> + <article class="post-entry"> + <a href="/posts/2021-03-05/my-first-post/"> <header class="entry-header"> + <h2>My First Post</h2> + </header> + <section class="entry-content"> + <p> + これはテスト投稿です。これはテスト投稿です。これはテスト投稿です。 + </p> + </section> + <footer class="entry-footer"> + Posted on<time datetime="2021-03-05">2021-03-05</time> + </footer> +</a> + </article> </main> <footer class="footer"> © 2021 nsfisis diff --git a/public/tags/conference/index.html b/public/tags/conference/index.html index 757ab24..c3e7a26 100644 --- a/public/tags/conference/index.html +++ b/public/tags/conference/index.html @@ -6,15 +6,12 @@ <meta name="author" content="nsfisis"> <meta name="copyright" content="© 2021 nsfisis"> <meta name="description" content="タグ「カンファレンス」のついた記事一覧"> + <meta name="keywords" content="カンファレンス"> <link rel="icon" type="image/svg+xml" href="/favicon.svg"> <title>カンファレンス | REPL: Rest-Eat-Program Loop</title> - - <link rel="stylesheet" href="/hl.css?208c52e3b7c9db1cad782c5d30b4698f"> - - <link rel="stylesheet" href="/style.css?779b1a3debcaeba619f6fe500e93d525"> - - <link rel="stylesheet" href="/custom.css?a649ea3528d4b626fb636505d94c1144"> - + <link rel="stylesheet" href="/hl.css?h=208c52e3b7c9db1cad782c5d30b4698f"> + <link rel="stylesheet" href="/style.css?h=779b1a3debcaeba619f6fe500e93d525"> + <link rel="stylesheet" href="/custom.css?h=a649ea3528d4b626fb636505d94c1144"> </head> <body class="list"> <header class="header"> @@ -28,71 +25,62 @@ <header class="page-header"> <h1>カンファレンス</h1> </header> - - <article class="post-entry"> - <a href="/posts/2022-08-27/php-conference-okinawa-code-golf/"> - <header class="entry-header"> - <h2>PHP カンファレンス沖縄で出題されたコードゴルフの問題を解いてみた</h2> - </header> - <section class="entry-content"> - <p> - PHP カンファレンス沖縄の懇親会 LT で出題されたコードゴルフの問題を解いてみた。 - </p> - </section> - <footer class="entry-footer"> - Posted on <time datetime="2022-08-27">2022-08-27</time> - </footer> - </a> - </article> - - <article class="post-entry"> - <a href="/posts/2022-05-01/phperkaigi-2022/"> - <header class="entry-header"> - <h2>PHPerKaigi 2022</h2> - </header> - <section class="entry-content"> - <p> - 2022-04-09 から 2022-04-11 にかけて開催された、PHPerKaigi 2022 に参加した。 - </p> - </section> - <footer class="entry-footer"> - Posted on <time datetime="2022-05-01">2022-05-01</time> - </footer> - </a> - </article> - - <article class="post-entry"> - <a href="/posts/2022-04-09/phperkaigi-2022-tokens/"> - <header class="entry-header"> - <h2>PHPerKaigi 2022 トークン問題の解説</h2> - </header> - <section class="entry-content"> - <p> - PHPerKaigi 2022 で私が作成した PHPer チャレンジ問題を解説する。 - </p> - </section> - <footer class="entry-footer"> - Posted on <time datetime="2022-04-09">2022-04-09</time>, updated on <time datetime="2022-04-16">2022-04-16</time> - </footer> - </a> - </article> - - <article class="post-entry"> - <a href="/posts/2021-03-30/phperkaigi-2021/"> - <header class="entry-header"> - <h2>PHPerKaigi 2021</h2> - </header> - <section class="entry-content"> - <p> - 2021-03-26 から 2021-03-28 にかけて開催された、PHPerKaigi 2021 に参加した。 - </p> - </section> - <footer class="entry-footer"> - Posted on <time datetime="2021-03-30">2021-03-30</time> - </footer> - </a> - </article> - + <article class="post-entry"> + <a href="/posts/2022-08-27/php-conference-okinawa-code-golf/"> <header class="entry-header"> + <h2>PHP カンファレンス沖縄で出題されたコードゴルフの問題を解いてみた</h2> + </header> + <section class="entry-content"> + <p> + PHP カンファレンス沖縄の懇親会 LT で出題されたコードゴルフの問題を解いてみた。 + </p> + </section> + <footer class="entry-footer"> + Posted on<time datetime="2022-08-27">2022-08-27</time> + </footer> +</a> + </article> + <article class="post-entry"> + <a href="/posts/2022-05-01/phperkaigi-2022/"> <header class="entry-header"> + <h2>PHPerKaigi 2022</h2> + </header> + <section class="entry-content"> + <p> + 2022-04-09 から 2022-04-11 にかけて開催された、PHPerKaigi 2022 に参加した。 + </p> + </section> + <footer class="entry-footer"> + Posted on<time datetime="2022-05-01">2022-05-01</time> + </footer> +</a> + </article> + <article class="post-entry"> + <a href="/posts/2022-04-09/phperkaigi-2022-tokens/"> <header class="entry-header"> + <h2>PHPerKaigi 2022 トークン問題の解説</h2> + </header> + <section class="entry-content"> + <p> + PHPerKaigi 2022 で私が作成した PHPer チャレンジ問題を解説する。 + </p> + </section> + <footer class="entry-footer"> + Posted on<time datetime="2022-04-09">2022-04-09</time>, updated on<time datetime="2022-04-16">2022-04-16</time> + </footer> +</a> + </article> + <article class="post-entry"> + <a href="/posts/2021-03-30/phperkaigi-2021/"> <header class="entry-header"> + <h2>PHPerKaigi 2021</h2> + </header> + <section class="entry-content"> + <p> + 2021-03-26 から 2021-03-28 にかけて開催された、PHPerKaigi 2021 に参加した。 + </p> + </section> + <footer class="entry-footer"> + Posted on<time datetime="2021-03-30">2021-03-30</time> + </footer> +</a> + </article> </main> <footer class="footer"> © 2021 nsfisis diff --git a/public/tags/cpp/index.html b/public/tags/cpp/index.html index 5f831c4..e1b9d2e 100644 --- a/public/tags/cpp/index.html +++ b/public/tags/cpp/index.html @@ -6,15 +6,12 @@ <meta name="author" content="nsfisis"> <meta name="copyright" content="© 2021 nsfisis"> <meta name="description" content="タグ「C++」のついた記事一覧"> + <meta name="keywords" content="C++"> <link rel="icon" type="image/svg+xml" href="/favicon.svg"> <title>C++ | REPL: Rest-Eat-Program Loop</title> - - <link rel="stylesheet" href="/hl.css?208c52e3b7c9db1cad782c5d30b4698f"> - - <link rel="stylesheet" href="/style.css?779b1a3debcaeba619f6fe500e93d525"> - - <link rel="stylesheet" href="/custom.css?a649ea3528d4b626fb636505d94c1144"> - + <link rel="stylesheet" href="/hl.css?h=208c52e3b7c9db1cad782c5d30b4698f"> + <link rel="stylesheet" href="/style.css?h=779b1a3debcaeba619f6fe500e93d525"> + <link rel="stylesheet" href="/custom.css?h=a649ea3528d4b626fb636505d94c1144"> </head> <body class="list"> <header class="header"> @@ -28,23 +25,20 @@ <header class="page-header"> <h1>C++</h1> </header> - - <article class="post-entry"> - <a href="/posts/2021-10-02/cpp-you-can-use-keywords-in-attributes/"> - <header class="entry-header"> - <h2>【C++】属性構文の属性名にはキーワードが使える</h2> - </header> - <section class="entry-content"> - <p> - C++ の属性構文の属性名には、キーワードが使える。ネタ記事。 - </p> - </section> - <footer class="entry-footer"> - Posted on <time datetime="2021-10-02">2021-10-02</time> - </footer> - </a> - </article> - + <article class="post-entry"> + <a href="/posts/2021-10-02/cpp-you-can-use-keywords-in-attributes/"> <header class="entry-header"> + <h2>【C++】 属性構文の属性名にはキーワードが使える</h2> + </header> + <section class="entry-content"> + <p> + C++ の属性構文の属性名には、キーワードが使える。ネタ記事。 + </p> + </section> + <footer class="entry-footer"> + Posted on<time datetime="2021-10-02">2021-10-02</time> + </footer> +</a> + </article> </main> <footer class="footer"> © 2021 nsfisis diff --git a/public/tags/cpp17/index.html b/public/tags/cpp17/index.html index 77e426a..52acbcf 100644 --- a/public/tags/cpp17/index.html +++ b/public/tags/cpp17/index.html @@ -6,15 +6,12 @@ <meta name="author" content="nsfisis"> <meta name="copyright" content="© 2021 nsfisis"> <meta name="description" content="タグ「C++ 17」のついた記事一覧"> + <meta name="keywords" content="C++ 17"> <link rel="icon" type="image/svg+xml" href="/favicon.svg"> <title>C++ 17 | REPL: Rest-Eat-Program Loop</title> - - <link rel="stylesheet" href="/hl.css?208c52e3b7c9db1cad782c5d30b4698f"> - - <link rel="stylesheet" href="/style.css?779b1a3debcaeba619f6fe500e93d525"> - - <link rel="stylesheet" href="/custom.css?a649ea3528d4b626fb636505d94c1144"> - + <link rel="stylesheet" href="/hl.css?h=208c52e3b7c9db1cad782c5d30b4698f"> + <link rel="stylesheet" href="/style.css?h=779b1a3debcaeba619f6fe500e93d525"> + <link rel="stylesheet" href="/custom.css?h=a649ea3528d4b626fb636505d94c1144"> </head> <body class="list"> <header class="header"> @@ -28,23 +25,20 @@ <header class="page-header"> <h1>C++ 17</h1> </header> - - <article class="post-entry"> - <a href="/posts/2021-10-02/cpp-you-can-use-keywords-in-attributes/"> - <header class="entry-header"> - <h2>【C++】属性構文の属性名にはキーワードが使える</h2> - </header> - <section class="entry-content"> - <p> - C++ の属性構文の属性名には、キーワードが使える。ネタ記事。 - </p> - </section> - <footer class="entry-footer"> - Posted on <time datetime="2021-10-02">2021-10-02</time> - </footer> - </a> - </article> - + <article class="post-entry"> + <a href="/posts/2021-10-02/cpp-you-can-use-keywords-in-attributes/"> <header class="entry-header"> + <h2>【C++】 属性構文の属性名にはキーワードが使える</h2> + </header> + <section class="entry-content"> + <p> + C++ の属性構文の属性名には、キーワードが使える。ネタ記事。 + </p> + </section> + <footer class="entry-footer"> + Posted on<time datetime="2021-10-02">2021-10-02</time> + </footer> +</a> + </article> </main> <footer class="footer"> © 2021 nsfisis diff --git a/public/tags/note-to-self/index.html b/public/tags/note-to-self/index.html index d73bf03..162b92c 100644 --- a/public/tags/note-to-self/index.html +++ b/public/tags/note-to-self/index.html @@ -6,15 +6,12 @@ <meta name="author" content="nsfisis"> <meta name="copyright" content="© 2022 nsfisis"> <meta name="description" content="タグ「備忘録」のついた記事一覧"> + <meta name="keywords" content="備忘録"> <link rel="icon" type="image/svg+xml" href="/favicon.svg"> <title>備忘録 | REPL: Rest-Eat-Program Loop</title> - - <link rel="stylesheet" href="/hl.css?208c52e3b7c9db1cad782c5d30b4698f"> - - <link rel="stylesheet" href="/style.css?779b1a3debcaeba619f6fe500e93d525"> - - <link rel="stylesheet" href="/custom.css?a649ea3528d4b626fb636505d94c1144"> - + <link rel="stylesheet" href="/hl.css?h=208c52e3b7c9db1cad782c5d30b4698f"> + <link rel="stylesheet" href="/style.css?h=779b1a3debcaeba619f6fe500e93d525"> + <link rel="stylesheet" href="/custom.css?h=a649ea3528d4b626fb636505d94c1144"> </head> <body class="list"> <header class="header"> @@ -28,23 +25,20 @@ <header class="page-header"> <h1>備忘録</h1> </header> - - <article class="post-entry"> - <a href="/posts/2022-10-28/setup-server-for-this-site/"> - <header class="entry-header"> - <h2>【備忘録】このサイト用の VPS をセットアップしたときのメモ</h2> - </header> - <section class="entry-content"> - <p> - GitHub Pages でホストしていたこのサイトを VPS へ移行したので、 そのときにやったことのメモ。99 % 自分用。 - </p> - </section> - <footer class="entry-footer"> - Posted on <time datetime="2022-10-28">2022-10-28</time> - </footer> - </a> - </article> - + <article class="post-entry"> + <a href="/posts/2022-10-28/setup-server-for-this-site/"> <header class="entry-header"> + <h2>【備忘録】 このサイト用の VPS をセットアップしたときのメモ</h2> + </header> + <section class="entry-content"> + <p> + GitHub Pages でホストしていたこのサイトを VPS へ移行したので、そのときにやったことのメモ。99 % 自分用。 + </p> + </section> + <footer class="entry-footer"> + Posted on<time datetime="2022-10-28">2022-10-28</time> + </footer> +</a> + </article> </main> <footer class="footer"> © 2021 nsfisis diff --git a/public/tags/php/index.html b/public/tags/php/index.html index fed4a3f..719c4a1 100644 --- a/public/tags/php/index.html +++ b/public/tags/php/index.html @@ -6,15 +6,12 @@ <meta name="author" content="nsfisis"> <meta name="copyright" content="© 2021 nsfisis"> <meta name="description" content="タグ「PHP」のついた記事一覧"> + <meta name="keywords" content="PHP"> <link rel="icon" type="image/svg+xml" href="/favicon.svg"> <title>PHP | REPL: Rest-Eat-Program Loop</title> - - <link rel="stylesheet" href="/hl.css?208c52e3b7c9db1cad782c5d30b4698f"> - - <link rel="stylesheet" href="/style.css?779b1a3debcaeba619f6fe500e93d525"> - - <link rel="stylesheet" href="/custom.css?a649ea3528d4b626fb636505d94c1144"> - + <link rel="stylesheet" href="/hl.css?h=208c52e3b7c9db1cad782c5d30b4698f"> + <link rel="stylesheet" href="/style.css?h=779b1a3debcaeba619f6fe500e93d525"> + <link rel="stylesheet" href="/custom.css?h=a649ea3528d4b626fb636505d94c1144"> </head> <body class="list"> <header class="header"> @@ -28,135 +25,118 @@ <header class="page-header"> <h1>PHP</h1> </header> - - <article class="post-entry"> - <a href="/posts/2023-01-10/phperkaigi-2023-unused-token-quiz-3/"> - <header class="entry-header"> - <h2>PHPerKaigi 2023: ボツになったトークン問題 その 3</h2> - </header> - <section class="entry-content"> - <p> - 来年の PHPerKaigi 2023 でデジタルサーカス株式会社から出題予定のトークン問題のうち、 ボツになった問題を公開する (その 3)。 - </p> - </section> - <footer class="entry-footer"> - Posted on <time datetime="2023-01-10">2023-01-10</time>, updated on <time datetime="2023-01-10">2023-01-10</time> - </footer> - </a> - </article> - - <article class="post-entry"> - <a href="/posts/2022-11-19/phperkaigi-2023-unused-token-quiz-2/"> - <header class="entry-header"> - <h2>PHPerKaigi 2023: ボツになったトークン問題 その 2</h2> - </header> - <section class="entry-content"> - <p> - 来年の PHPerKaigi 2023 でデジタルサーカス株式会社から出題予定のトークン問題のうち、 ボツになった問題を公開する (その 2)。 - </p> - </section> - <footer class="entry-footer"> - Posted on <time datetime="2022-11-19">2022-11-19</time> - </footer> - </a> - </article> - - <article class="post-entry"> - <a href="/posts/2022-10-23/phperkaigi-2023-unused-token-quiz-1/"> - <header class="entry-header"> - <h2>PHPerKaigi 2023: ボツになったトークン問題 その 1</h2> - </header> - <section class="entry-content"> - <p> - 来年の PHPerKaigi 2023 でデジタルサーカス株式会社から出題予定のトークン問題のうち、 ボツになった問題を公開する (その 1)。 - </p> - </section> - <footer class="entry-footer"> - Posted on <time datetime="2022-10-23">2022-10-23</time> - </footer> - </a> - </article> - - <article class="post-entry"> - <a href="/posts/2022-09-29/write-fizzbuzz-in-php-2-letters-per-line/"> - <header class="entry-header"> - <h2>【PHP】fizzbuzz を書く。1行あたり2文字で。</h2> - </header> - <section class="entry-content"> - <p> - PHP で、fizzbuzz を書いた。ただし、1行あたりに使える文字数は2文字まで。 - </p> - </section> - <footer class="entry-footer"> - Posted on <time datetime="2022-09-28">2022-09-28</time>, updated on <time datetime="2022-09-29">2022-09-29</time> - </footer> - </a> - </article> - - <article class="post-entry"> - <a href="/posts/2022-08-27/php-conference-okinawa-code-golf/"> - <header class="entry-header"> - <h2>PHP カンファレンス沖縄で出題されたコードゴルフの問題を解いてみた</h2> - </header> - <section class="entry-content"> - <p> - PHP カンファレンス沖縄の懇親会 LT で出題されたコードゴルフの問題を解いてみた。 - </p> - </section> - <footer class="entry-footer"> - Posted on <time datetime="2022-08-27">2022-08-27</time> - </footer> - </a> - </article> - - <article class="post-entry"> - <a href="/posts/2022-05-01/phperkaigi-2022/"> - <header class="entry-header"> - <h2>PHPerKaigi 2022</h2> - </header> - <section class="entry-content"> - <p> - 2022-04-09 から 2022-04-11 にかけて開催された、PHPerKaigi 2022 に参加した。 - </p> - </section> - <footer class="entry-footer"> - Posted on <time datetime="2022-05-01">2022-05-01</time> - </footer> - </a> - </article> - - <article class="post-entry"> - <a href="/posts/2022-04-09/phperkaigi-2022-tokens/"> - <header class="entry-header"> - <h2>PHPerKaigi 2022 トークン問題の解説</h2> - </header> - <section class="entry-content"> - <p> - PHPerKaigi 2022 で私が作成した PHPer チャレンジ問題を解説する。 - </p> - </section> - <footer class="entry-footer"> - Posted on <time datetime="2022-04-09">2022-04-09</time>, updated on <time datetime="2022-04-16">2022-04-16</time> - </footer> - </a> - </article> - - <article class="post-entry"> - <a href="/posts/2021-03-30/phperkaigi-2021/"> - <header class="entry-header"> - <h2>PHPerKaigi 2021</h2> - </header> - <section class="entry-content"> - <p> - 2021-03-26 から 2021-03-28 にかけて開催された、PHPerKaigi 2021 に参加した。 - </p> - </section> - <footer class="entry-footer"> - Posted on <time datetime="2021-03-30">2021-03-30</time> - </footer> - </a> - </article> - + <article class="post-entry"> + <a href="/posts/2023-01-10/phperkaigi-2023-unused-token-quiz-3/"> <header class="entry-header"> + <h2>PHPerKaigi 2023: ボツになったトークン問題 その 3</h2> + </header> + <section class="entry-content"> + <p> + 来年の PHPerKaigi 2023 でデジタルサーカス株式会社から出題予定のトークン問題のうち、ボツになった問題を公開する (その 3)。 + </p> + </section> + <footer class="entry-footer"> + Posted on<time datetime="2023-01-10">2023-01-10</time> + </footer> +</a> + </article> + <article class="post-entry"> + <a href="/posts/2022-11-19/phperkaigi-2023-unused-token-quiz-2/"> <header class="entry-header"> + <h2>PHPerKaigi 2023: ボツになったトークン問題 その 2</h2> + </header> + <section class="entry-content"> + <p> + 来年の PHPerKaigi 2023 でデジタルサーカス株式会社から出題予定のトークン問題のうち、ボツになった問題を公開する (その 2)。 + </p> + </section> + <footer class="entry-footer"> + Posted on<time datetime="2022-11-19">2022-11-19</time> + </footer> +</a> + </article> + <article class="post-entry"> + <a href="/posts/2022-10-23/phperkaigi-2023-unused-token-quiz-1/"> <header class="entry-header"> + <h2>PHPerKaigi 2023: ボツになったトークン問題 その 1</h2> + </header> + <section class="entry-content"> + <p> + 来年の PHPerKaigi 2023 でデジタルサーカス株式会社から出題予定のトークン問題のうち、ボツになった問題を公開する (その 1)。 + </p> + </section> + <footer class="entry-footer"> + Posted on<time datetime="2022-10-23">2022-10-23</time> + </footer> +</a> + </article> + <article class="post-entry"> + <a href="/posts/2022-09-29/write-fizzbuzz-in-php-2-letters-per-line/"> <header class="entry-header"> + <h2>【PHP】 fizzbuzz を書く。1行あたり2文字で。</h2> + </header> + <section class="entry-content"> + <p> + PHP で、fizzbuzz を書いた。ただし、1行あたりに使える文字数は2文字まで。 + </p> + </section> + <footer class="entry-footer"> + Posted on<time datetime="2022-09-28">2022-09-28</time>, updated on<time datetime="2022-09-29">2022-09-29</time> + </footer> +</a> + </article> + <article class="post-entry"> + <a href="/posts/2022-08-27/php-conference-okinawa-code-golf/"> <header class="entry-header"> + <h2>PHP カンファレンス沖縄で出題されたコードゴルフの問題を解いてみた</h2> + </header> + <section class="entry-content"> + <p> + PHP カンファレンス沖縄の懇親会 LT で出題されたコードゴルフの問題を解いてみた。 + </p> + </section> + <footer class="entry-footer"> + Posted on<time datetime="2022-08-27">2022-08-27</time> + </footer> +</a> + </article> + <article class="post-entry"> + <a href="/posts/2022-05-01/phperkaigi-2022/"> <header class="entry-header"> + <h2>PHPerKaigi 2022</h2> + </header> + <section class="entry-content"> + <p> + 2022-04-09 から 2022-04-11 にかけて開催された、PHPerKaigi 2022 に参加した。 + </p> + </section> + <footer class="entry-footer"> + Posted on<time datetime="2022-05-01">2022-05-01</time> + </footer> +</a> + </article> + <article class="post-entry"> + <a href="/posts/2022-04-09/phperkaigi-2022-tokens/"> <header class="entry-header"> + <h2>PHPerKaigi 2022 トークン問題の解説</h2> + </header> + <section class="entry-content"> + <p> + PHPerKaigi 2022 で私が作成した PHPer チャレンジ問題を解説する。 + </p> + </section> + <footer class="entry-footer"> + Posted on<time datetime="2022-04-09">2022-04-09</time>, updated on<time datetime="2022-04-16">2022-04-16</time> + </footer> +</a> + </article> + <article class="post-entry"> + <a href="/posts/2021-03-30/phperkaigi-2021/"> <header class="entry-header"> + <h2>PHPerKaigi 2021</h2> + </header> + <section class="entry-content"> + <p> + 2021-03-26 から 2021-03-28 にかけて開催された、PHPerKaigi 2021 に参加した。 + </p> + </section> + <footer class="entry-footer"> + Posted on<time datetime="2021-03-30">2021-03-30</time> + </footer> +</a> + </article> </main> <footer class="footer"> © 2021 nsfisis diff --git a/public/tags/phpcon/index.html b/public/tags/phpcon/index.html index 266ae5c..5b4e28f 100644 --- a/public/tags/phpcon/index.html +++ b/public/tags/phpcon/index.html @@ -6,15 +6,12 @@ <meta name="author" content="nsfisis"> <meta name="copyright" content="© 2022 nsfisis"> <meta name="description" content="タグ「PHP カンファレンス」のついた記事一覧"> + <meta name="keywords" content="PHP カンファレンス"> <link rel="icon" type="image/svg+xml" href="/favicon.svg"> <title>PHP カンファレンス | REPL: Rest-Eat-Program Loop</title> - - <link rel="stylesheet" href="/hl.css?208c52e3b7c9db1cad782c5d30b4698f"> - - <link rel="stylesheet" href="/style.css?779b1a3debcaeba619f6fe500e93d525"> - - <link rel="stylesheet" href="/custom.css?a649ea3528d4b626fb636505d94c1144"> - + <link rel="stylesheet" href="/hl.css?h=208c52e3b7c9db1cad782c5d30b4698f"> + <link rel="stylesheet" href="/style.css?h=779b1a3debcaeba619f6fe500e93d525"> + <link rel="stylesheet" href="/custom.css?h=a649ea3528d4b626fb636505d94c1144"> </head> <body class="list"> <header class="header"> @@ -28,23 +25,20 @@ <header class="page-header"> <h1>PHP カンファレンス</h1> </header> - - <article class="post-entry"> - <a href="/posts/2022-08-27/php-conference-okinawa-code-golf/"> - <header class="entry-header"> - <h2>PHP カンファレンス沖縄で出題されたコードゴルフの問題を解いてみた</h2> - </header> - <section class="entry-content"> - <p> - PHP カンファレンス沖縄の懇親会 LT で出題されたコードゴルフの問題を解いてみた。 - </p> - </section> - <footer class="entry-footer"> - Posted on <time datetime="2022-08-27">2022-08-27</time> - </footer> - </a> - </article> - + <article class="post-entry"> + <a href="/posts/2022-08-27/php-conference-okinawa-code-golf/"> <header class="entry-header"> + <h2>PHP カンファレンス沖縄で出題されたコードゴルフの問題を解いてみた</h2> + </header> + <section class="entry-content"> + <p> + PHP カンファレンス沖縄の懇親会 LT で出題されたコードゴルフの問題を解いてみた。 + </p> + </section> + <footer class="entry-footer"> + Posted on<time datetime="2022-08-27">2022-08-27</time> + </footer> +</a> + </article> </main> <footer class="footer"> © 2021 nsfisis diff --git a/public/tags/phperkaigi/index.html b/public/tags/phperkaigi/index.html index 7c166a7..0899911 100644 --- a/public/tags/phperkaigi/index.html +++ b/public/tags/phperkaigi/index.html @@ -6,15 +6,12 @@ <meta name="author" content="nsfisis"> <meta name="copyright" content="© 2021 nsfisis"> <meta name="description" content="タグ「PHPerKaigi」のついた記事一覧"> + <meta name="keywords" content="PHPerKaigi"> <link rel="icon" type="image/svg+xml" href="/favicon.svg"> <title>PHPerKaigi | REPL: Rest-Eat-Program Loop</title> - - <link rel="stylesheet" href="/hl.css?208c52e3b7c9db1cad782c5d30b4698f"> - - <link rel="stylesheet" href="/style.css?779b1a3debcaeba619f6fe500e93d525"> - - <link rel="stylesheet" href="/custom.css?a649ea3528d4b626fb636505d94c1144"> - + <link rel="stylesheet" href="/hl.css?h=208c52e3b7c9db1cad782c5d30b4698f"> + <link rel="stylesheet" href="/style.css?h=779b1a3debcaeba619f6fe500e93d525"> + <link rel="stylesheet" href="/custom.css?h=a649ea3528d4b626fb636505d94c1144"> </head> <body class="list"> <header class="header"> @@ -28,103 +25,90 @@ <header class="page-header"> <h1>PHPerKaigi</h1> </header> - - <article class="post-entry"> - <a href="/posts/2023-01-10/phperkaigi-2023-unused-token-quiz-3/"> - <header class="entry-header"> - <h2>PHPerKaigi 2023: ボツになったトークン問題 その 3</h2> - </header> - <section class="entry-content"> - <p> - 来年の PHPerKaigi 2023 でデジタルサーカス株式会社から出題予定のトークン問題のうち、 ボツになった問題を公開する (その 3)。 - </p> - </section> - <footer class="entry-footer"> - Posted on <time datetime="2023-01-10">2023-01-10</time>, updated on <time datetime="2023-01-10">2023-01-10</time> - </footer> - </a> - </article> - - <article class="post-entry"> - <a href="/posts/2022-11-19/phperkaigi-2023-unused-token-quiz-2/"> - <header class="entry-header"> - <h2>PHPerKaigi 2023: ボツになったトークン問題 その 2</h2> - </header> - <section class="entry-content"> - <p> - 来年の PHPerKaigi 2023 でデジタルサーカス株式会社から出題予定のトークン問題のうち、 ボツになった問題を公開する (その 2)。 - </p> - </section> - <footer class="entry-footer"> - Posted on <time datetime="2022-11-19">2022-11-19</time> - </footer> - </a> - </article> - - <article class="post-entry"> - <a href="/posts/2022-10-23/phperkaigi-2023-unused-token-quiz-1/"> - <header class="entry-header"> - <h2>PHPerKaigi 2023: ボツになったトークン問題 その 1</h2> - </header> - <section class="entry-content"> - <p> - 来年の PHPerKaigi 2023 でデジタルサーカス株式会社から出題予定のトークン問題のうち、 ボツになった問題を公開する (その 1)。 - </p> - </section> - <footer class="entry-footer"> - Posted on <time datetime="2022-10-23">2022-10-23</time> - </footer> - </a> - </article> - - <article class="post-entry"> - <a href="/posts/2022-05-01/phperkaigi-2022/"> - <header class="entry-header"> - <h2>PHPerKaigi 2022</h2> - </header> - <section class="entry-content"> - <p> - 2022-04-09 から 2022-04-11 にかけて開催された、PHPerKaigi 2022 に参加した。 - </p> - </section> - <footer class="entry-footer"> - Posted on <time datetime="2022-05-01">2022-05-01</time> - </footer> - </a> - </article> - - <article class="post-entry"> - <a href="/posts/2022-04-09/phperkaigi-2022-tokens/"> - <header class="entry-header"> - <h2>PHPerKaigi 2022 トークン問題の解説</h2> - </header> - <section class="entry-content"> - <p> - PHPerKaigi 2022 で私が作成した PHPer チャレンジ問題を解説する。 - </p> - </section> - <footer class="entry-footer"> - Posted on <time datetime="2022-04-09">2022-04-09</time>, updated on <time datetime="2022-04-16">2022-04-16</time> - </footer> - </a> - </article> - - <article class="post-entry"> - <a href="/posts/2021-03-30/phperkaigi-2021/"> - <header class="entry-header"> - <h2>PHPerKaigi 2021</h2> - </header> - <section class="entry-content"> - <p> - 2021-03-26 から 2021-03-28 にかけて開催された、PHPerKaigi 2021 に参加した。 - </p> - </section> - <footer class="entry-footer"> - Posted on <time datetime="2021-03-30">2021-03-30</time> - </footer> - </a> - </article> - + <article class="post-entry"> + <a href="/posts/2023-01-10/phperkaigi-2023-unused-token-quiz-3/"> <header class="entry-header"> + <h2>PHPerKaigi 2023: ボツになったトークン問題 その 3</h2> + </header> + <section class="entry-content"> + <p> + 来年の PHPerKaigi 2023 でデジタルサーカス株式会社から出題予定のトークン問題のうち、ボツになった問題を公開する (その 3)。 + </p> + </section> + <footer class="entry-footer"> + Posted on<time datetime="2023-01-10">2023-01-10</time> + </footer> +</a> + </article> + <article class="post-entry"> + <a href="/posts/2022-11-19/phperkaigi-2023-unused-token-quiz-2/"> <header class="entry-header"> + <h2>PHPerKaigi 2023: ボツになったトークン問題 その 2</h2> + </header> + <section class="entry-content"> + <p> + 来年の PHPerKaigi 2023 でデジタルサーカス株式会社から出題予定のトークン問題のうち、ボツになった問題を公開する (その 2)。 + </p> + </section> + <footer class="entry-footer"> + Posted on<time datetime="2022-11-19">2022-11-19</time> + </footer> +</a> + </article> + <article class="post-entry"> + <a href="/posts/2022-10-23/phperkaigi-2023-unused-token-quiz-1/"> <header class="entry-header"> + <h2>PHPerKaigi 2023: ボツになったトークン問題 その 1</h2> + </header> + <section class="entry-content"> + <p> + 来年の PHPerKaigi 2023 でデジタルサーカス株式会社から出題予定のトークン問題のうち、ボツになった問題を公開する (その 1)。 + </p> + </section> + <footer class="entry-footer"> + Posted on<time datetime="2022-10-23">2022-10-23</time> + </footer> +</a> + </article> + <article class="post-entry"> + <a href="/posts/2022-05-01/phperkaigi-2022/"> <header class="entry-header"> + <h2>PHPerKaigi 2022</h2> + </header> + <section class="entry-content"> + <p> + 2022-04-09 から 2022-04-11 にかけて開催された、PHPerKaigi 2022 に参加した。 + </p> + </section> + <footer class="entry-footer"> + Posted on<time datetime="2022-05-01">2022-05-01</time> + </footer> +</a> + </article> + <article class="post-entry"> + <a href="/posts/2022-04-09/phperkaigi-2022-tokens/"> <header class="entry-header"> + <h2>PHPerKaigi 2022 トークン問題の解説</h2> + </header> + <section class="entry-content"> + <p> + PHPerKaigi 2022 で私が作成した PHPer チャレンジ問題を解説する。 + </p> + </section> + <footer class="entry-footer"> + Posted on<time datetime="2022-04-09">2022-04-09</time>, updated on<time datetime="2022-04-16">2022-04-16</time> + </footer> +</a> + </article> + <article class="post-entry"> + <a href="/posts/2021-03-30/phperkaigi-2021/"> <header class="entry-header"> + <h2>PHPerKaigi 2021</h2> + </header> + <section class="entry-content"> + <p> + 2021-03-26 から 2021-03-28 にかけて開催された、PHPerKaigi 2021 に参加した。 + </p> + </section> + <footer class="entry-footer"> + Posted on<time datetime="2021-03-30">2021-03-30</time> + </footer> +</a> + </article> </main> <footer class="footer"> © 2021 nsfisis diff --git a/public/tags/python/index.html b/public/tags/python/index.html index f178bae..7b1643f 100644 --- a/public/tags/python/index.html +++ b/public/tags/python/index.html @@ -6,15 +6,12 @@ <meta name="author" content="nsfisis"> <meta name="copyright" content="© 2021 nsfisis"> <meta name="description" content="タグ「Python」のついた記事一覧"> + <meta name="keywords" content="Python"> <link rel="icon" type="image/svg+xml" href="/favicon.svg"> <title>Python | REPL: Rest-Eat-Program Loop</title> - - <link rel="stylesheet" href="/hl.css?208c52e3b7c9db1cad782c5d30b4698f"> - - <link rel="stylesheet" href="/style.css?779b1a3debcaeba619f6fe500e93d525"> - - <link rel="stylesheet" href="/custom.css?a649ea3528d4b626fb636505d94c1144"> - + <link rel="stylesheet" href="/hl.css?h=208c52e3b7c9db1cad782c5d30b4698f"> + <link rel="stylesheet" href="/style.css?h=779b1a3debcaeba619f6fe500e93d525"> + <link rel="stylesheet" href="/custom.css?h=a649ea3528d4b626fb636505d94c1144"> </head> <body class="list"> <header class="header"> @@ -28,23 +25,20 @@ <header class="page-header"> <h1>Python</h1> </header> - - <article class="post-entry"> - <a href="/posts/2021-10-02/python-unbound-local-error/"> - <header class="entry-header"> - <h2>【Python】クロージャとUnboundLocalError: local variable 'x' referenced before assignment</h2> - </header> - <section class="entry-content"> - <p> - Python における UnboundLocalError の理由と対処法。 - </p> - </section> - <footer class="entry-footer"> - Posted on <time datetime="2021-10-02">2021-10-02</time> - </footer> - </a> - </article> - + <article class="post-entry"> + <a href="/posts/2021-10-02/python-unbound-local-error/"> <header class="entry-header"> + <h2>【Python】 クロージャとUnboundLocalError: local variable 'x' referenced before assignment</h2> + </header> + <section class="entry-content"> + <p> + Python における UnboundLocalError の理由と対処法。 + </p> + </section> + <footer class="entry-footer"> + Posted on<time datetime="2021-10-02">2021-10-02</time> + </footer> +</a> + </article> </main> <footer class="footer"> © 2021 nsfisis diff --git a/public/tags/python3/index.html b/public/tags/python3/index.html index d2401bc..17470ce 100644 --- a/public/tags/python3/index.html +++ b/public/tags/python3/index.html @@ -6,15 +6,12 @@ <meta name="author" content="nsfisis"> <meta name="copyright" content="© 2021 nsfisis"> <meta name="description" content="タグ「Python 3」のついた記事一覧"> + <meta name="keywords" content="Python 3"> <link rel="icon" type="image/svg+xml" href="/favicon.svg"> <title>Python 3 | REPL: Rest-Eat-Program Loop</title> - - <link rel="stylesheet" href="/hl.css?208c52e3b7c9db1cad782c5d30b4698f"> - - <link rel="stylesheet" href="/style.css?779b1a3debcaeba619f6fe500e93d525"> - - <link rel="stylesheet" href="/custom.css?a649ea3528d4b626fb636505d94c1144"> - + <link rel="stylesheet" href="/hl.css?h=208c52e3b7c9db1cad782c5d30b4698f"> + <link rel="stylesheet" href="/style.css?h=779b1a3debcaeba619f6fe500e93d525"> + <link rel="stylesheet" href="/custom.css?h=a649ea3528d4b626fb636505d94c1144"> </head> <body class="list"> <header class="header"> @@ -28,23 +25,20 @@ <header class="page-header"> <h1>Python 3</h1> </header> - - <article class="post-entry"> - <a href="/posts/2021-10-02/python-unbound-local-error/"> - <header class="entry-header"> - <h2>【Python】クロージャとUnboundLocalError: local variable 'x' referenced before assignment</h2> - </header> - <section class="entry-content"> - <p> - Python における UnboundLocalError の理由と対処法。 - </p> - </section> - <footer class="entry-footer"> - Posted on <time datetime="2021-10-02">2021-10-02</time> - </footer> - </a> - </article> - + <article class="post-entry"> + <a href="/posts/2021-10-02/python-unbound-local-error/"> <header class="entry-header"> + <h2>【Python】 クロージャとUnboundLocalError: local variable 'x' referenced before assignment</h2> + </header> + <section class="entry-content"> + <p> + Python における UnboundLocalError の理由と対処法。 + </p> + </section> + <footer class="entry-footer"> + Posted on<time datetime="2021-10-02">2021-10-02</time> + </footer> +</a> + </article> </main> <footer class="footer"> © 2021 nsfisis diff --git a/public/tags/ruby/index.html b/public/tags/ruby/index.html index ebfbfd4..06a79d4 100644 --- a/public/tags/ruby/index.html +++ b/public/tags/ruby/index.html @@ -6,15 +6,12 @@ <meta name="author" content="nsfisis"> <meta name="copyright" content="© 2021 nsfisis"> <meta name="description" content="タグ「Ruby」のついた記事一覧"> + <meta name="keywords" content="Ruby"> <link rel="icon" type="image/svg+xml" href="/favicon.svg"> <title>Ruby | REPL: Rest-Eat-Program Loop</title> - - <link rel="stylesheet" href="/hl.css?208c52e3b7c9db1cad782c5d30b4698f"> - - <link rel="stylesheet" href="/style.css?779b1a3debcaeba619f6fe500e93d525"> - - <link rel="stylesheet" href="/custom.css?a649ea3528d4b626fb636505d94c1144"> - + <link rel="stylesheet" href="/hl.css?h=208c52e3b7c9db1cad782c5d30b4698f"> + <link rel="stylesheet" href="/style.css?h=779b1a3debcaeba619f6fe500e93d525"> + <link rel="stylesheet" href="/custom.css?h=a649ea3528d4b626fb636505d94c1144"> </head> <body class="list"> <header class="header"> @@ -28,39 +25,34 @@ <header class="page-header"> <h1>Ruby</h1> </header> - - <article class="post-entry"> - <a href="/posts/2021-10-02/ruby-then-keyword-and-case-in/"> - <header class="entry-header"> - <h2>【Ruby】then キーワードと case in</h2> - </header> - <section class="entry-content"> - <p> - Ruby 3.0 で追加される case in 構文と、then キーワードについて。 - </p> - </section> - <footer class="entry-footer"> - Posted on <time datetime="2021-10-02">2021-10-02</time> - </footer> - </a> - </article> - - <article class="post-entry"> - <a href="/posts/2021-10-02/ruby-detect-running-implementation/"> - <header class="entry-header"> - <h2>【Ruby】自身を実行している処理系の種類を判定する</h2> - </header> - <section class="entry-content"> - <p> - Ruby には複数の実装があるが、自身を実行している処理系の種類を スクリプト上からどのように判定すればよいだろうか。 - </p> - </section> - <footer class="entry-footer"> - Posted on <time datetime="2021-10-02">2021-10-02</time> - </footer> - </a> - </article> - + <article class="post-entry"> + <a href="/posts/2021-10-02/ruby-detect-running-implementation/"> <header class="entry-header"> + <h2>【Ruby】 自身を実行している処理系の種類を判定する</h2> + </header> + <section class="entry-content"> + <p> + Ruby には複数の実装があるが、自身を実行している処理系の種類をスクリプト上からどのように判定すればよいだろうか。 + </p> + </section> + <footer class="entry-footer"> + Posted on<time datetime="2021-10-02">2021-10-02</time> + </footer> +</a> + </article> + <article class="post-entry"> + <a href="/posts/2021-10-02/ruby-then-keyword-and-case-in/"> <header class="entry-header"> + <h2>【Ruby】 then キーワードと case in</h2> + </header> + <section class="entry-content"> + <p> + Ruby 3.0 で追加される case in 構文と、then キーワードについて。 + </p> + </section> + <footer class="entry-footer"> + Posted on<time datetime="2021-10-02">2021-10-02</time> + </footer> +</a> + </article> </main> <footer class="footer"> © 2021 nsfisis diff --git a/public/tags/ruby3/index.html b/public/tags/ruby3/index.html index 029cd8a..f127cdf 100644 --- a/public/tags/ruby3/index.html +++ b/public/tags/ruby3/index.html @@ -6,15 +6,12 @@ <meta name="author" content="nsfisis"> <meta name="copyright" content="© 2021 nsfisis"> <meta name="description" content="タグ「Ruby 3」のついた記事一覧"> + <meta name="keywords" content="Ruby 3"> <link rel="icon" type="image/svg+xml" href="/favicon.svg"> <title>Ruby 3 | REPL: Rest-Eat-Program Loop</title> - - <link rel="stylesheet" href="/hl.css?208c52e3b7c9db1cad782c5d30b4698f"> - - <link rel="stylesheet" href="/style.css?779b1a3debcaeba619f6fe500e93d525"> - - <link rel="stylesheet" href="/custom.css?a649ea3528d4b626fb636505d94c1144"> - + <link rel="stylesheet" href="/hl.css?h=208c52e3b7c9db1cad782c5d30b4698f"> + <link rel="stylesheet" href="/style.css?h=779b1a3debcaeba619f6fe500e93d525"> + <link rel="stylesheet" href="/custom.css?h=a649ea3528d4b626fb636505d94c1144"> </head> <body class="list"> <header class="header"> @@ -28,23 +25,20 @@ <header class="page-header"> <h1>Ruby 3</h1> </header> - - <article class="post-entry"> - <a href="/posts/2021-10-02/ruby-then-keyword-and-case-in/"> - <header class="entry-header"> - <h2>【Ruby】then キーワードと case in</h2> - </header> - <section class="entry-content"> - <p> - Ruby 3.0 で追加される case in 構文と、then キーワードについて。 - </p> - </section> - <footer class="entry-footer"> - Posted on <time datetime="2021-10-02">2021-10-02</time> - </footer> - </a> - </article> - + <article class="post-entry"> + <a href="/posts/2021-10-02/ruby-then-keyword-and-case-in/"> <header class="entry-header"> + <h2>【Ruby】 then キーワードと case in</h2> + </header> + <section class="entry-content"> + <p> + Ruby 3.0 で追加される case in 構文と、then キーワードについて。 + </p> + </section> + <footer class="entry-footer"> + Posted on<time datetime="2021-10-02">2021-10-02</time> + </footer> +</a> + </article> </main> <footer class="footer"> © 2021 nsfisis diff --git a/public/tags/rust/index.html b/public/tags/rust/index.html index cdf3ae9..683a105 100644 --- a/public/tags/rust/index.html +++ b/public/tags/rust/index.html @@ -6,15 +6,12 @@ <meta name="author" content="nsfisis"> <meta name="copyright" content="© 2021 nsfisis"> <meta name="description" content="タグ「Rust」のついた記事一覧"> + <meta name="keywords" content="Rust"> <link rel="icon" type="image/svg+xml" href="/favicon.svg"> <title>Rust | REPL: Rest-Eat-Program Loop</title> - - <link rel="stylesheet" href="/hl.css?208c52e3b7c9db1cad782c5d30b4698f"> - - <link rel="stylesheet" href="/style.css?779b1a3debcaeba619f6fe500e93d525"> - - <link rel="stylesheet" href="/custom.css?a649ea3528d4b626fb636505d94c1144"> - + <link rel="stylesheet" href="/hl.css?h=208c52e3b7c9db1cad782c5d30b4698f"> + <link rel="stylesheet" href="/style.css?h=779b1a3debcaeba619f6fe500e93d525"> + <link rel="stylesheet" href="/custom.css?h=a649ea3528d4b626fb636505d94c1144"> </head> <body class="list"> <header class="header"> @@ -28,23 +25,20 @@ <header class="page-header"> <h1>Rust</h1> </header> - - <article class="post-entry"> - <a href="/posts/2021-10-02/rust-where-are-primitive-types-from/"> - <header class="entry-header"> - <h2>Rust のプリミティブ型はどこからやって来るか</h2> - </header> - <section class="entry-content"> - <p> - Rust のプリミティブ型は予約語ではなく普通の識別子である。 どのようにこれが名前解決されるのかを調べた。 - </p> - </section> - <footer class="entry-footer"> - Posted on <time datetime="2021-10-02">2021-10-02</time> - </footer> - </a> - </article> - + <article class="post-entry"> + <a href="/posts/2021-10-02/rust-where-are-primitive-types-from/"> <header class="entry-header"> + <h2>Rust のプリミティブ型はどこからやって来るか</h2> + </header> + <section class="entry-content"> + <p> + Rust のプリミティブ型は予約語ではなく普通の識別子である。どのようにこれが名前解決されるのかを調べた。 + </p> + </section> + <footer class="entry-footer"> + Posted on<time datetime="2021-10-02">2021-10-02</time> + </footer> +</a> + </article> </main> <footer class="footer"> © 2021 nsfisis diff --git a/public/tags/vim/index.html b/public/tags/vim/index.html index 6046bd5..addba5c 100644 --- a/public/tags/vim/index.html +++ b/public/tags/vim/index.html @@ -6,15 +6,12 @@ <meta name="author" content="nsfisis"> <meta name="copyright" content="© 2021 nsfisis"> <meta name="description" content="タグ「Vim」のついた記事一覧"> + <meta name="keywords" content="Vim"> <link rel="icon" type="image/svg+xml" href="/favicon.svg"> <title>Vim | REPL: Rest-Eat-Program Loop</title> - - <link rel="stylesheet" href="/hl.css?208c52e3b7c9db1cad782c5d30b4698f"> - - <link rel="stylesheet" href="/style.css?779b1a3debcaeba619f6fe500e93d525"> - - <link rel="stylesheet" href="/custom.css?a649ea3528d4b626fb636505d94c1144"> - + <link rel="stylesheet" href="/hl.css?h=208c52e3b7c9db1cad782c5d30b4698f"> + <link rel="stylesheet" href="/style.css?h=779b1a3debcaeba619f6fe500e93d525"> + <link rel="stylesheet" href="/custom.css?h=a649ea3528d4b626fb636505d94c1144"> </head> <body class="list"> <header class="header"> @@ -28,39 +25,34 @@ <header class="page-header"> <h1>Vim</h1> </header> - - <article class="post-entry"> - <a href="/posts/2021-10-02/vim-swap-order-of-selected-lines/"> - <header class="entry-header"> - <h2>Vimで選択した行の順番を入れ替える</h2> - </header> - <section class="entry-content"> - <p> - Vim で選択した行の順番を入れ替える方法。 - </p> - </section> - <footer class="entry-footer"> - Posted on <time datetime="2021-10-02">2021-10-02</time> - </footer> - </a> - </article> - - <article class="post-entry"> - <a href="/posts/2021-10-02/vim-difference-between-autocmd-bufwrite-and-bufwritepre/"> - <header class="entry-header"> - <h2>【Vim】autocmd events の BufWrite/BufWritePre の違い</h2> - </header> - <section class="entry-content"> - <p> - Vim の autocmd events における BufWrite/BufWritePre がどう違うのかを調べた結果、 違いはないことがわかった。 - </p> - </section> - <footer class="entry-footer"> - Posted on <time datetime="2021-10-02">2021-10-02</time> - </footer> - </a> - </article> - + <article class="post-entry"> + <a href="/posts/2021-10-02/vim-difference-between-autocmd-bufwrite-and-bufwritepre/"> <header class="entry-header"> + <h2>【Vim】 autocmd events の BufWrite/BufWritePre の違い</h2> + </header> + <section class="entry-content"> + <p> + Vim の autocmd events における BufWrite/BufWritePre がどう違うのかを調べた結果、違いはないことがわかった。 + </p> + </section> + <footer class="entry-footer"> + Posted on<time datetime="2021-10-02">2021-10-02</time> + </footer> +</a> + </article> + <article class="post-entry"> + <a href="/posts/2021-10-02/vim-swap-order-of-selected-lines/"> <header class="entry-header"> + <h2>Vimで選択した行の順番を入れ替える</h2> + </header> + <section class="entry-content"> + <p> + Vim で選択した行の順番を入れ替える方法。 + </p> + </section> + <footer class="entry-footer"> + Posted on<time datetime="2021-10-02">2021-10-02</time> + </footer> +</a> + </article> </main> <footer class="footer"> © 2021 nsfisis diff --git a/templates/document__post.html.erb b/templates/document__post.html.erb deleted file mode 100644 index a48a5f4..0000000 --- a/templates/document__post.html.erb +++ /dev/null @@ -1,77 +0,0 @@ -<% _stylesheets = attr 'stylesheets' %> -<% _author = attr 'author' %> -<% _description = attr 'description' %> -<% _lang = attr 'lang' %> -<% _site_copyright_year = attr 'site-copyright-year' %> -<% _copyright_year = attr 'copyright-year' %> -<% _site_name = attr 'site-name' %> -<% _title = title %> -<% _revisions = attr 'revision-history' %> -<% _tags = attr 'tags' %> -<% _content = content %> -<!DOCTYPE html> -<html lang="<%= _lang %>"> - <head> - <meta charset="UTF-8"> - <meta name="viewport" content="width=device-width, initial-scale=1.0"> - <meta name="author" content="<%= _author %>"> - <meta name="copyright" content="© <%= _copyright_year %> <%= _author %>"> - <meta name="description" content="<%= _description %>"> - <meta name="keywords" content="<%= _tags.map(&:label).join(',') %>"> - <link rel="icon" type="image/svg+xml" href="/favicon.svg"> - <title><%= _title %> | <%= _site_name %></title> - <% for stylesheet in _stylesheets %> - <link rel="stylesheet" href="<%= stylesheet %>"> - <% end %> - </head> - <body class="single"> - <header class="header"> - <nav class="nav"> - <p class="logo"> - <a href="/"><%= _site_name %></a> - </p> - </nav> - </header> - <main class="main"> - <article class="post-single"> - <header class="post-header"> - <h1 class="post-title"><%= _title %></h1> - <% if not _tags.empty? %> - <ul class="post-tags"> - <% for tag in _tags %> - <li class="tag"> - <a href="/tags/<%= tag.slug %>/"><%= tag.label %></a> - </li> - <% end %> - </ul> - <% end %> - </header> - <div class="post-content"> - <section> - <h2 id="changelog">更新履歴</h2> - <ol> - <% for revision in _revisions %> - <li class="revision"> - <time datetime="<%= revision.date %>"><%= revision.date %></time>: <%= revision.remark %> - </li> - <% end %> - </ol> - </section> - <%= _content %> - </div> - <% if footnotes? %> - <div id="footnotes"> - <% for footnote in footnotes %> - <div class="footnote" id="_footnotedef_<%= footnote.index %>"> - <a href="#_footnoteref_<%= footnote.index %>"><%= footnote.index %></a>. <%= footnote.text %> - </div> - <% end %> - </div> - <% end %> - </article> - </main> - <footer class="footer"> - © <%= _site_copyright_year %> <%= _author %> - </footer> - </body> -</html> diff --git a/templates/document__post_list.html.erb b/templates/document__post_list.html.erb deleted file mode 100644 index c1429ec..0000000 --- a/templates/document__post_list.html.erb +++ /dev/null @@ -1,58 +0,0 @@ -<% _stylesheets = attr 'stylesheets' %> -<% _author = attr 'author' %> -<% _description = attr 'description' %> -<% _lang = attr 'lang' %> -<% _site_copyright_year = attr 'site-copyright-year' %> -<% _copyright_year = attr 'copyright-year' %> -<% _site_name = attr 'site-name' %> -<% _title = title %> -<% _posts = posts %> -<!DOCTYPE html> -<html lang="<%= _lang %>"> - <head> - <meta charset="UTF-8"> - <meta name="viewport" content="width=device-width, initial-scale=1.0"> - <meta name="author" content="<%= _author %>"> - <meta name="copyright" content="© <%= _copyright_year %> <%= _author %>"> - <meta name="description" content="<%= _description %>"> - <link rel="icon" type="image/svg+xml" href="/favicon.svg"> - <title><%= _title %> | <%= _site_name %></title> - <% for stylesheet in _stylesheets %> - <link rel="stylesheet" href="<%= stylesheet %>"> - <% end %> - </head> - <body class="list"> - <header class="header"> - <nav class="nav"> - <p class="logo"> - <a href="/"><%= _site_name %></a> - </p> - </nav> - </header> - <main class="main"> - <header class="page-header"> - <h1><%= _title %></h1> - </header> - <% for post in _posts %> - <article class="post-entry"> - <a href="<%= post.attributes['href'] %>"> - <header class="entry-header"> - <h2><%= post.doctitle %></h2> - </header> - <section class="entry-content"> - <p> - <%= post.attributes['description'] %> - </p> - </section> - <footer class="entry-footer"> - Posted on <time datetime="<%= post.attributes['revision-history'].first.date %>"><%= post.attributes['revision-history'].first.date %></time><% if post.attributes['revision-history'].length > 1 %>, updated on <time datetime="<%= post.attributes['revision-history'].last.date %>"><%= post.attributes['revision-history'].last.date %></time><% end %> - </footer> - </a> - </article> - <% end %> - </main> - <footer class="footer"> - © <%= _site_copyright_year %> <%= _author %> - </footer> - </body> -</html> diff --git a/templates/document__tag.html.erb b/templates/document__tag.html.erb deleted file mode 100644 index c1429ec..0000000 --- a/templates/document__tag.html.erb +++ /dev/null @@ -1,58 +0,0 @@ -<% _stylesheets = attr 'stylesheets' %> -<% _author = attr 'author' %> -<% _description = attr 'description' %> -<% _lang = attr 'lang' %> -<% _site_copyright_year = attr 'site-copyright-year' %> -<% _copyright_year = attr 'copyright-year' %> -<% _site_name = attr 'site-name' %> -<% _title = title %> -<% _posts = posts %> -<!DOCTYPE html> -<html lang="<%= _lang %>"> - <head> - <meta charset="UTF-8"> - <meta name="viewport" content="width=device-width, initial-scale=1.0"> - <meta name="author" content="<%= _author %>"> - <meta name="copyright" content="© <%= _copyright_year %> <%= _author %>"> - <meta name="description" content="<%= _description %>"> - <link rel="icon" type="image/svg+xml" href="/favicon.svg"> - <title><%= _title %> | <%= _site_name %></title> - <% for stylesheet in _stylesheets %> - <link rel="stylesheet" href="<%= stylesheet %>"> - <% end %> - </head> - <body class="list"> - <header class="header"> - <nav class="nav"> - <p class="logo"> - <a href="/"><%= _site_name %></a> - </p> - </nav> - </header> - <main class="main"> - <header class="page-header"> - <h1><%= _title %></h1> - </header> - <% for post in _posts %> - <article class="post-entry"> - <a href="<%= post.attributes['href'] %>"> - <header class="entry-header"> - <h2><%= post.doctitle %></h2> - </header> - <section class="entry-content"> - <p> - <%= post.attributes['description'] %> - </p> - </section> - <footer class="entry-footer"> - Posted on <time datetime="<%= post.attributes['revision-history'].first.date %>"><%= post.attributes['revision-history'].first.date %></time><% if post.attributes['revision-history'].length > 1 %>, updated on <time datetime="<%= post.attributes['revision-history'].last.date %>"><%= post.attributes['revision-history'].last.date %></time><% end %> - </footer> - </a> - </article> - <% end %> - </main> - <footer class="footer"> - © <%= _site_copyright_year %> <%= _author %> - </footer> - </body> -</html> diff --git a/templates/section.html.erb b/templates/section.html.erb deleted file mode 100644 index 1510595..0000000 --- a/templates/section.html.erb +++ /dev/null @@ -1,19 +0,0 @@ -<% _level = level %> -<% _role = role %> -<% _id = id %> -<% _title = caption ? captioned_title : title %> -<% _content = content %> -<section class="section-<%= _level %><%= _role ? " #{role}" : '' %>"> - <h<%= _level + 1 %> id="<%= _id %>" class="section-header"> - <% if _id %> - <a href="#<%= _id %>"> - <%= _title %> - </a> - <% else %> - <%= _title %> - <% end %> - </h<%= _level + 1 %>> - <div class="section-body"> - <%= _content %> - </div> -</section> |
