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 /content/posts | |
| parent | 8f988a6e899aed678406ddfac1be4ef105439274 (diff) | |
| download | blog.nsfisis.dev-88ba6cfe220216f371f8756921059fac51a21262.tar.gz blog.nsfisis.dev-88ba6cfe220216f371f8756921059fac51a21262.tar.zst blog.nsfisis.dev-88ba6cfe220216f371f8756921059fac51a21262.zip | |
AsciiDoc to DocBook
Diffstat (limited to 'content/posts')
38 files changed, 3577 insertions, 3766 deletions
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> |
