From 6209453817da9922f28bac1bb1522c6d380630ab Mon Sep 17 00:00:00 2001 From: nsfisis Date: Sat, 19 Nov 2022 14:23:32 +0900 Subject: Hugo to Asciidoctor --- public/custom.css | 29 + public/favicon.svg | 62 + public/hl.css | 214 ++++ public/posts/2021-03-05/my-first-post/index.html | 71 ++ public/posts/2021-03-30/phperkaigi-2021/index.html | 1195 ++++++++++++++++++++ .../index.html | 209 ++++ .../python-unbound-local-error/index.html | 131 +++ .../ruby-detect-running-implementation/index.html | 167 +++ .../ruby-then-keyword-and-case-in/index.html | 392 +++++++ .../rust-where-are-primitive-types-from/index.html | 317 ++++++ .../index.html | 299 +++++ .../vim-swap-order-of-selected-lines/index.html | 366 ++++++ .../2022-04-09/phperkaigi-2022-tokens/index.html | 876 ++++++++++++++ .../index.html | 222 ++++ public/posts/2022-05-01/phperkaigi-2022/index.html | 324 ++++++ .../php-conference-okinawa-code-golf/index.html | 276 +++++ .../index.html | 140 +++ .../index.html | 929 +++++++++++++++ .../phperkaigi-2023-unused-token-quiz-1/index.html | 291 +++++ .../setup-server-for-this-site/index.html | 630 +++++++++++ public/posts/index.html | 315 ++++++ public/style.css | 480 ++++++++ public/tags/conference/index.html | 107 ++ public/tags/cpp/index.html | 59 + public/tags/cpp17/index.html | 59 + public/tags/note-to-self/index.html | 59 + public/tags/php/index.html | 139 +++ public/tags/phpcon/index.html | 59 + public/tags/phperkaigi/index.html | 107 ++ public/tags/python/index.html | 59 + public/tags/python3/index.html | 59 + public/tags/ruby/index.html | 75 ++ public/tags/ruby3/index.html | 59 + public/tags/rust/index.html | 59 + public/tags/vim/index.html | 75 ++ 35 files changed, 8910 insertions(+) create mode 100644 public/custom.css create mode 100644 public/favicon.svg create mode 100644 public/hl.css create mode 100644 public/posts/2021-03-05/my-first-post/index.html create mode 100644 public/posts/2021-03-30/phperkaigi-2021/index.html create mode 100644 public/posts/2021-10-02/cpp-you-can-use-keywords-in-attributes/index.html create mode 100644 public/posts/2021-10-02/python-unbound-local-error/index.html create mode 100644 public/posts/2021-10-02/ruby-detect-running-implementation/index.html create mode 100644 public/posts/2021-10-02/ruby-then-keyword-and-case-in/index.html create mode 100644 public/posts/2021-10-02/rust-where-are-primitive-types-from/index.html create mode 100644 public/posts/2021-10-02/vim-difference-between-autocmd-bufwrite-and-bufwritepre/index.html create mode 100644 public/posts/2021-10-02/vim-swap-order-of-selected-lines/index.html create mode 100644 public/posts/2022-04-09/phperkaigi-2022-tokens/index.html create mode 100644 public/posts/2022-04-24/term-banner-write-tool-showing-banner-in-terminal/index.html create mode 100644 public/posts/2022-05-01/phperkaigi-2022/index.html create mode 100644 public/posts/2022-08-27/php-conference-okinawa-code-golf/index.html create mode 100644 public/posts/2022-08-31/support-for-communty-is-employee-benefits/index.html create mode 100644 public/posts/2022-09-29/write-fizzbuzz-in-php-2-letters-per-line/index.html create mode 100644 public/posts/2022-10-23/phperkaigi-2023-unused-token-quiz-1/index.html create mode 100644 public/posts/2022-10-28/setup-server-for-this-site/index.html create mode 100644 public/posts/index.html create mode 100644 public/style.css create mode 100644 public/tags/conference/index.html create mode 100644 public/tags/cpp/index.html create mode 100644 public/tags/cpp17/index.html create mode 100644 public/tags/note-to-self/index.html create mode 100644 public/tags/php/index.html create mode 100644 public/tags/phpcon/index.html create mode 100644 public/tags/phperkaigi/index.html create mode 100644 public/tags/python/index.html create mode 100644 public/tags/python3/index.html create mode 100644 public/tags/ruby/index.html create mode 100644 public/tags/ruby3/index.html create mode 100644 public/tags/rust/index.html create mode 100644 public/tags/vim/index.html (limited to 'public') diff --git a/public/custom.css b/public/custom.css new file mode 100644 index 0000000..d3b1672 --- /dev/null +++ b/public/custom.css @@ -0,0 +1,29 @@ +/* Place a custom css file in your own project to use this. */ +.post-content h1, +.post-content h2, +.post-content h3, +.post-content h4, +.post-content h5, +.post-content h6 { + border-bottom: var(--primary) solid 1px; +} + +.post-content h1::before, +.post-content h2::before, +.post-content h3::before, +.post-content h4::before, +.post-content h5::before, +.post-content h6::before { + color: var(--secondary); + padding-right: 0.3rem; +} + +.post-content h2::before { content: '#'; } +.post-content h3::before { content: '##'; } +.post-content h4::before { content: '###'; } +.post-content h5::before { content: '####'; } +.post-content h6::before { content: '#####'; } + +li.revision { + list-style: inside; +} diff --git a/public/favicon.svg b/public/favicon.svg new file mode 100644 index 0000000..d122ea1 --- /dev/null +++ b/public/favicon.svg @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/hl.css b/public/hl.css new file mode 100644 index 0000000..fabe9de --- /dev/null +++ b/public/hl.css @@ -0,0 +1,214 @@ +/* ruby -rrouge -e 'puts Rouge::Themes::Github.render(scope: ".highlight")' */ +.highlight table td { padding: 5px; } +.highlight table pre { margin: 0; } +.highlight .cm { + color: #999988; + font-style: italic; +} +.highlight .cp { + color: #999999; + font-weight: bold; +} +.highlight .c1 { + color: #999988; + font-style: italic; +} +.highlight .cs { + color: #999999; + font-weight: bold; + font-style: italic; +} +.highlight .c, .highlight .ch, .highlight .cd, .highlight .cpf { + color: #999988; + font-style: italic; +} +.highlight .err { + color: #a61717; + background-color: #e3d2d2; +} +.highlight .gd { + color: #000000; + background-color: #ffdddd; +} +.highlight .ge { + color: #000000; + font-style: italic; +} +.highlight .gr { + color: #aa0000; +} +.highlight .gh { + color: #999999; +} +.highlight .gi { + color: #000000; + background-color: #ddffdd; +} +.highlight .go { + color: #888888; +} +.highlight .gp { + color: #555555; +} +.highlight .gs { + font-weight: bold; +} +.highlight .gu { + color: #aaaaaa; +} +.highlight .gt { + color: #aa0000; +} +.highlight .kc { + color: #000000; + font-weight: bold; +} +.highlight .kd { + color: #000000; + font-weight: bold; +} +.highlight .kn { + color: #000000; + font-weight: bold; +} +.highlight .kp { + color: #000000; + font-weight: bold; +} +.highlight .kr { + color: #000000; + font-weight: bold; +} +.highlight .kt { + color: #445588; + font-weight: bold; +} +.highlight .k, .highlight .kv { + color: #000000; + font-weight: bold; +} +.highlight .mf { + color: #009999; +} +.highlight .mh { + color: #009999; +} +.highlight .il { + color: #009999; +} +.highlight .mi { + color: #009999; +} +.highlight .mo { + color: #009999; +} +.highlight .m, .highlight .mb, .highlight .mx { + color: #009999; +} +.highlight .sa { + color: #000000; + font-weight: bold; +} +.highlight .sb { + color: #d14; +} +.highlight .sc { + color: #d14; +} +.highlight .sd { + color: #d14; +} +.highlight .s2 { + color: #d14; +} +.highlight .se { + color: #d14; +} +.highlight .sh { + color: #d14; +} +.highlight .si { + color: #d14; +} +.highlight .sx { + color: #d14; +} +.highlight .sr { + color: #009926; +} +.highlight .s1 { + color: #d14; +} +.highlight .ss { + color: #990073; +} +.highlight .s, .highlight .dl { + color: #d14; +} +.highlight .na { + color: #008080; +} +.highlight .bp { + color: #999999; +} +.highlight .nb { + color: #0086B3; +} +.highlight .nc { + color: #445588; + font-weight: bold; +} +.highlight .no { + color: #008080; +} +.highlight .nd { + color: #3c5d5d; + font-weight: bold; +} +.highlight .ni { + color: #800080; +} +.highlight .ne { + color: #990000; + font-weight: bold; +} +.highlight .nf, .highlight .fm { + color: #990000; + font-weight: bold; +} +.highlight .nl { + color: #990000; + font-weight: bold; +} +.highlight .nn { + color: #555555; +} +.highlight .nt { + color: #000080; +} +.highlight .vc { + color: #008080; +} +.highlight .vg { + color: #008080; +} +.highlight .vi { + color: #008080; +} +.highlight .nv, .highlight .vm { + color: #008080; +} +.highlight .ow { + color: #000000; + font-weight: bold; +} +.highlight .o { + color: #000000; + font-weight: bold; +} +.highlight .w { + color: #bbbbbb; +} +.highlight { + background-color: #f8f8f8; +} \ No newline at end of file diff --git a/public/posts/2021-03-05/my-first-post/index.html b/public/posts/2021-03-05/my-first-post/index.html new file mode 100644 index 0000000..fbf7237 --- /dev/null +++ b/public/posts/2021-03-05/my-first-post/index.html @@ -0,0 +1,71 @@ + + + + + + + + + + + My First Post | REPL: Rest-Eat-Program Loop + + + + + +
+ +
+
+
+
+

My First Post

+ +
+
+
+

更新履歴

+
    + +
  1. + : 公開 +
  2. + +
+
+ + + + + +
+

+ + 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/public/posts/2021-03-30/phperkaigi-2021/index.html b/public/posts/2021-03-30/phperkaigi-2021/index.html new file mode 100644 index 0000000..46fce9b --- /dev/null +++ b/public/posts/2021-03-30/phperkaigi-2021/index.html @@ -0,0 +1,1195 @@ + + + + + + + + + + + PHPerKaigi 2021 | REPL: Rest-Eat-Program Loop + + + + + +
+ +
+
+
+
+

PHPerKaigi 2021

+ + + +
+
+
+

更新履歴

+
    + +
  1. + : 公開 +
  2. + +
+
+ + + + + +
+

+ + PHPerKaigi 2021 参加レポ + +

+
+
+

2021-03-26 から 2021-03-28 +にかけて開催された、 PHPerKaigi 2021 +に一般参加者として参加した。 +弊社 デジタルサーカス株式会社 +(今年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 捕捉した上で)さらに上に是非を問う

+
+
+

開発段階で例外を把握し、ハンドリングを考えておく

+
+
+

+
+
+

はキャッチすべきでない

+
+
+
    +
  • +

    +
    +
      +
    • +

      本番で起きてはいけない

      +
    • +
    +
    +
  • +
  • +

    +
    +
      +
    • +

      本番で起きてはいけない →生じないのだから捕捉もしない

      +
    • +
    +
    +
  • +
  • +

    +
    +
      +
    • +

      起こるかもしれないので本番環境でも考慮する

      +
    • +
    +
    +
  • +
+
+
+

捕捉して対応するのではなく、未然に防ぐ

+
+
+

独自例外を使う を投げてしまうと、 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/public/posts/2021-10-02/cpp-you-can-use-keywords-in-attributes/index.html b/public/posts/2021-10-02/cpp-you-can-use-keywords-in-attributes/index.html new file mode 100644 index 0000000..16a8188 --- /dev/null +++ b/public/posts/2021-10-02/cpp-you-can-use-keywords-in-attributes/index.html @@ -0,0 +1,209 @@ + + + + + + + + + + + 【C++】属性構文の属性名にはキーワードが使える | REPL: Rest-Eat-Program Loop + + + + + +
+ +
+
+
+
+

【C++】属性構文の属性名にはキーワードが使える

+ + + +
+
+
+

更新履歴

+
    + +
  1. + : Qiita から移植 +
  2. + +
+
+
+

この記事は Qiita から移植してきたものです。 元 URL: +https://qiita.com/nsfisis/items/94090937bcf860cfa93b

+
+
+
+

タイトル落ち。まずはこのコードを見て欲しい。

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

+
+
+

コンパイルコマンド (C17指定) $ clang –std=c++17 hoge.cpp

+
+
+
+
+

この記事から得られるものはこれ以上ないので以下は蛇足になる。

+
+
+

別件で cppreference.com の +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.  is a valid attribute) (since C++11)

      +
    • +
    +
    +
  • +
+
+
+
+
+

キーワードでも属性として指定する場合は非キーワードとして使えるらしい。 +実際にやってみる。

+
+
+

同サイトの keywords のページ +から一覧を拝借し、上のコードが出来上がった (C++17 +においてキーワードでないものなど、一部省いている)。 大量の警告 (unknown +attribute `〇〇' ignored) +がコンパイラから出力されるが、コンパイルできる。

+
+
+

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

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

C++17 の仕様も見てみる (正確には標準化前のドラフト)。

+
+ +
+
+
+

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 +の構文上の要件を満たさないような代替トークンなどあるのか? +疑問に思って調べたところ、代替トークンという語にはダイグラフも含まれるらしい +(参考: +同ドラフト)

+
+
+
    +
  • +

    <%{

    +
  • +
  • +

    %>}

    +
  • +
  • +

    <:[

    +
  • +
  • +

    :>]

    +
  • +
  • +

    %:#

    +
  • +
  • +

    %:%:##

    +
  • +
+
+
+

identifier +の構文上の要件を満たさないような代替トークン」はこれらが当てはまると思われる。

+
+
+

調べた感想: 字句解析器か構文解析器が辛そう

+
+
+
+
+ + + diff --git a/public/posts/2021-10-02/python-unbound-local-error/index.html b/public/posts/2021-10-02/python-unbound-local-error/index.html new file mode 100644 index 0000000..20e1e0e --- /dev/null +++ b/public/posts/2021-10-02/python-unbound-local-error/index.html @@ -0,0 +1,131 @@ + + + + + + + + + + + 【Python】クロージャとUnboundLocalError: local variable 'x' referenced before assignment | REPL: Rest-Eat-Program Loop + + + + + +
+ +
+
+
+
+

【Python】クロージャとUnboundLocalError: local variable 'x' referenced before assignment

+ + + +
+
+
+

更新履歴

+
    + +
  1. + : Qiita から移植 +
  2. + +
+
+
+

この記事は Qiita から移植してきたものです。 元 URL: +https://qiita.com/nsfisis/items/5d733703afcb35bbf399

+
+
+
+

本記事は Python 3.7.6 の動作結果を元にして書かれている。

+
+
+

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 が代入前に参照された、とある。これは、fx +を参照するのではなく、新しく別の変数を g 内に作ってしまっているため。 +前述のコードを宣言と代入を便宜上分けて書き直すと次のようになる。var +を変数宣言のための構文として擬似的に利用している。

+
+
+
+
# 注: var は正しい Python の文法ではない。上記参照のこと
+def f():
+    var x           #  f の local変数 'x' を宣言
+    x = 0           #  x に 0 を代入
+    def g():        #  f の内部関数 g を定義
+        var x       #  g の local変数 'x' を宣言
+                    #  たまたま f にも同じ名前の変数があるが、それとは別の変数
+        x += 1      #  x に 1 を加算 (x = x + 1 の糖衣構文)
+                    #  加算する前の値を参照しようとするが、まだ代入されていないためエラー
+    g()
+
+
+
+

当初の意図を表現するには、次のように書けばよい。

+
+
+
+
def f():
+    x = 0
+    def g():
+        nonlocal x   ## (*)
+        x += 1
+    g()
+
+
+
+

(*) のように、nonlocal を追加する。これにより一つ外側のスコープ (g +の一つ外側 = f) で定義されている x を探しに行くようになる。

+
+
+
+
+ + + diff --git a/public/posts/2021-10-02/ruby-detect-running-implementation/index.html b/public/posts/2021-10-02/ruby-detect-running-implementation/index.html new file mode 100644 index 0000000..6fe62d7 --- /dev/null +++ b/public/posts/2021-10-02/ruby-detect-running-implementation/index.html @@ -0,0 +1,167 @@ + + + + + + + + + + + 【Ruby】自身を実行している処理系の種類を判定する | REPL: Rest-Eat-Program Loop + + + + + +
+ +
+
+
+
+

【Ruby】自身を実行している処理系の種類を判定する

+ + + +
+
+
+

更新履歴

+
    + +
  1. + : Qiita から移植 +
  2. + +
+
+
+

この記事は Qiita から移植してきたものです。 元 URL: +https://qiita.com/nsfisis/items/74d7ffeeebc51b20d791

+
+
+
+

Ruby +という言語には複数の実装があるが、それらをスクリプト上からどのようにして +programmatically に見分ければよいだろうか。

+
+
+

Object クラスに定義されている RUBY_ENGINE +という定数がこの用途に使える。

+
+
+

参考: +Object::RUBY_ENGINE

+
+
+

上記ページの例から引用する:

+
+
+
+
$ 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 +に良い質問と回答があった。

+
+ +
+
+ ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
RUBY_ENGINEImplementation

<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' +を返す。

+
+ +
+
+
/*
+ * Ruby engine.
+ */
+#define MRUBY_RUBY_ENGINE  "mruby"
+
+
+
+
+
+ + + diff --git a/public/posts/2021-10-02/ruby-then-keyword-and-case-in/index.html b/public/posts/2021-10-02/ruby-then-keyword-and-case-in/index.html new file mode 100644 index 0000000..7ddf636 --- /dev/null +++ b/public/posts/2021-10-02/ruby-then-keyword-and-case-in/index.html @@ -0,0 +1,392 @@ + + + + + + + + + + + 【Ruby】then キーワードと case in | REPL: Rest-Eat-Program Loop + + + + + +
+ +
+
+
+
+

【Ruby】then キーワードと case in

+ + + +
+
+
+

更新履歴

+
    + +
  1. + : Qiita から移植 +
  2. + +
+
+
+
+
+

この記事は Qiita から移植してきたものです。 元 URL: +https://qiita.com/nsfisis/items/787a8cf888a304497223

+
+
+
+
+ + + + + +
+

+ + TL; DR + +

+
+
+

case - in によるパターンマッチング構文でも、case - when +と同じように then が使える (場合によっては使う必要がある)。

+
+
+
+ + + + + +
+

+ + then とは + +

+
+
+

使われることは稀だが、Ruby では then +がキーワードになっている。次のように使う:

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

このキーワードが現れうる場所はいくつかあり、ifunlessrescuecase +構文がそれに当たる。 上記のように、何か条件を書いた後 then +を置き、式がそこで終了していることを示すマーカーとして機能する。

+
+
+
+
# Example:
+
+if x then
+  a
+end
+
+unless x then
+  a
+end
+
+begin
+  a
+rescue then
+  b
+end
+
+case x
+when p then
+  a
+end
+
+
+
+
+ + + + + +
+

+ + なぜ普段は書かなくてもよいのか + +

+
+
+

普通 Ruby のコードで then +を書くことはない。なぜか。次のコードを実行してみるとわかる。

+
+
+
+
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 +の後に改行を入れてみる。

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

無事 Hello, World! と出力されるようになった。

+
+
+
+ + + + + +
+

+ + なぜ then; や改行が必要か + +

+
+
+

なぜ then; や改行 (以下 「then 等」) +が必要なのだろうか。次の例を見てほしい:

+
+
+
+
if a b end
+
+
+
+

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

+
+
+
+
# a という変数かメソッドの評価結果が truthy なら b という変数かメソッドを評価
+if a then
+  b
+end
+
+
+
+
+
# 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 では casein +キーワードを使ったパターンマッチングの構文が入る予定である。この構文でもパターン部との区切りとして +then 等が必要になる。 (現在の) Ruby には formal +な形式での文法仕様は存在しないので、yacc の定義ファイルを参照した (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)) %*/
+            }
+        ;
+
+
+
+

簡略版:

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

ここで、keyword_in は文字通り inp_top_expr +はいわゆるパターン、thenthen +キーワードのことではなく、この記事で then 等と呼んでいるもの、つまり +then キーワード、;、改行のいずれかである。

+
+
+

これにより、case - when による従来の構文と同じように、then +等をパターンの後ろに挿入すればよいことがわかった。つまり次の3通りのいずれかになる:

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

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

+
+
+
+
case x
+in 0 then a
+in n if n < 0 then b
+in n then c
+end
+
+
+
+
+ + + + + +
+

+ + まとめ + +

+
+
+
    +
  • +

    ifcase の条件の後ろには then;、改行のいずれかが必要

    +
    +
      +
    • +

      通常は改行しておけばよい

      +
    • +
    +
    +
  • +
  • +

    3.0 で入る予定の case - in でも then 等が必要になる

    +
  • +
  • +

    Ruby の構文を正確に知るには (現状) parse.y を直接読めばよい

    +
  • +
+
+
+
+
+
+
+ + + diff --git a/public/posts/2021-10-02/rust-where-are-primitive-types-from/index.html b/public/posts/2021-10-02/rust-where-are-primitive-types-from/index.html new file mode 100644 index 0000000..27f2632 --- /dev/null +++ b/public/posts/2021-10-02/rust-where-are-primitive-types-from/index.html @@ -0,0 +1,317 @@ + + + + + + + + + + + Rust のプリミティブ型はどこからやって来るか | REPL: Rest-Eat-Program Loop + + + + + +
+ +
+
+
+
+

Rust のプリミティブ型はどこからやって来るか

+ + + +
+
+
+

更新履歴

+
    + +
  1. + : Qiita から移植 +
  2. + +
+
+
+
+
+

この記事は Qiita から移植してきたものです。 元 URL: +https://qiita.com/nsfisis/items/9a429432258bbcd6c565

+
+
+
+
+ + + + + +
+

+ + 前置き + +

+
+
+

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)

+
+ +
+

どのようにして調べるか。rustc +の構造には詳しくないため、すぐに当たりをつけるのは難しい。

+
+
+

大雑把な構造としては、compiler フォルダ以下に rustc_* +という名前のクレートが数十個入っている。これがどうやら rustc +コマンドの実装部のようだ。

+
+
+

rustc はセルフホストされている (= rustc 自身が Rust で書かれている) +ので、boolchar +などで適当に検索をかけてもノイズが多すぎて話にならない。 +しかし、お誂え向きなことに i128/u128 +というコンパイラ自身が使うことがなさそうな型が存在するのでこれを使って +git grep してみる。

+
+
+
+
$ git grep "\bi128\b" | wc      # i128
+     165    1069   15790
+
+$ git grep "\bu128\b" | wc      # u128
+     293    2127   26667
+
+$ git grep "\bbool\b" | wc      # cf. bool の結果
+    3563   23577  294659
+
+
+
+

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

+
+
+
+
$ git grep "\bi128\b"
+...
+rustc_resolve/src/lib.rs:        table.insert(sym::i128, Int(IntTy::I128));
+...
+
+
+
+

rustc_resolve +というのはいかにも名前解決を担いそうなクレート名である。該当箇所を見てみる。

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

これは初めに列挙したプリミティブ型の一覧と一致している。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 +の使用箇所を追う。追うと言っても使われている箇所は次の一箇所しかない。なお説明に不要な箇所は大きく削っている。

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

関数名や doc comment が示している通り、この関数は識別子 (identifier, +ident) を現在のレキシカルスコープ内で解決 (resolve) する。 +if ns == TypeNS のブロック内では、primitive_type_table (上記の +PrimitiveTypeTable::new() で作られた変数) に含まれている識別子 +(booli32 など) +かどうか判定し、そうであればそれに紐づけられたプリミティブ型を返している。

+
+
+

なお、ns は「名前空間」を示す変数である。Rust +における名前空間はC言語におけるそれとほとんど同じで、今探している名前が関数名/変数名なのか型なのかマクロなのかを区別している。この +if +は、プリミティブ型に解決されるのは型を探しているときだけだ、と言っている。

+
+
+

重要なのは、これが resolve_ident_in_lexical_scope() +の最後に書かれている点である。つまり、最初に挙げたプリミティブ型の識別子は、「名前解決の最終段階で」、「他に同名の型が見つかっていなければ」プリミティブ型として解決される。

+
+
+

動作がわかったところで、例として次のコードを考える。

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

ここで main()boolstruct bool +として解決される。なぜなら、プリミティブ型の判定をする前に bool +という名前の別の型が見つかるからだ。

+
+
+
+ + + + + +
+

+ + まとめ + +

+
+
+

Rust +のプリミティブ型は予約語ではない。名前解決の最終段階で特別扱いされ、他に同名の型が見つかっていなければ対応するプリミティブ型に解決される。

+
+
+
+
+
+
+ + + diff --git a/public/posts/2021-10-02/vim-difference-between-autocmd-bufwrite-and-bufwritepre/index.html b/public/posts/2021-10-02/vim-difference-between-autocmd-bufwrite-and-bufwritepre/index.html new file mode 100644 index 0000000..94912ed --- /dev/null +++ b/public/posts/2021-10-02/vim-difference-between-autocmd-bufwrite-and-bufwritepre/index.html @@ -0,0 +1,299 @@ + + + + + + + + + + + 【Vim】autocmd events の BufWrite/BufWritePre の違い | REPL: Rest-Eat-Program Loop + + + + + +
+ +
+
+
+
+

【Vim】autocmd events の BufWrite/BufWritePre の違い

+ + + +
+
+
+

更新履歴

+
    + +
  1. + : Qiita から移植 +
  2. + +
+
+
+
+
+

この記事は 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 のソースコードを調査した。

+
+
+
+
+

ソースコードへのリンク +vim +(調査時点での master branch) +neovim +(上に同じ)

+
+
+
+ + + + + +
+

+ + vim のソースコード + +

+
+
+

以下は、autocmd events +の名前と内部で使われている整数値とのマッピングを定義している箇所である。見ての通り、上でエイリアスではないかと述べた3組には、それぞれ同じ内部値が使われている。

+
+ +
+
+
    {"BufAdd",      EVENT_BUFADD},
+    {"BufCreate",   EVENT_BUFADD},
+
+
+ +
+
+
    {"BufRead",     EVENT_BUFREADPOST},
+    {"BufReadCmd",  EVENT_BUFREADCMD},
+    {"BufReadPost", EVENT_BUFREADPOST},
+
+
+ +
+
+
    {"BufWrite",    EVENT_BUFWRITEPRE},
+    {"BufWritePost",    EVENT_BUFWRITEPOST},
+    {"BufWritePre", EVENT_BUFWRITEPRE},
+
+
+
+
+ + + + + +
+

+ + neovim のソースコード + +

+
+
+

neovim の場合でも同様のマッピングが定義されているが、こちらの場合は Lua +で書かれている。以下にある通り、はっきり aliases と書かれている。

+
+ +
+
+
  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 を使う

      +
    • +
    +
    +
  • +
+
+
+

ところでこの調査で知ったのだが、BufReadBufWrite +は上にある通り発火するタイミングが「後」と「前」で対称性がない。可能なら +Pre/Post 付きのものを使った方が分かりやすいだろう。

+
+
+
+
+
+
+
+
+ + + diff --git a/public/posts/2021-10-02/vim-swap-order-of-selected-lines/index.html b/public/posts/2021-10-02/vim-swap-order-of-selected-lines/index.html new file mode 100644 index 0000000..2b66f2e --- /dev/null +++ b/public/posts/2021-10-02/vim-swap-order-of-selected-lines/index.html @@ -0,0 +1,366 @@ + + + + + + + + + + + Vimで選択した行の順番を入れ替える | REPL: Rest-Eat-Program Loop + + + + + +
+ +
+
+
+
+

Vimで選択した行の順番を入れ替える

+ + + +
+
+
+

更新履歴

+
    + +
  1. + : Qiita から移植 +
  2. + +
+
+
+
+
+

この記事は 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 + +

+
+
+

tactail -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行目を処理範囲とするよう拡張できる。手でこれを入力するわけにはいかないので、次のようなコマンドを用意する。

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

これは望みの動作をするが、実際に実行してみると全行がハイライトされてしまう。次節で詳細を述べる。

+
+
+
+
+
+ + + + + +
+

+ + :g/^/m0 の問題点 + +

+
+
+

:global +コマンドは各行に対してマッチングを行う際、現在の検索パターンを上書きしてしまう。^ +は行の先頭にマッチするため、結果として全ての行がハイライトされてしまう。'hlsearch' +オプションを無効にしている場合その限りではないが、その場合でも直前の検索パターンが失われてしまうと +n コマンドなどの際に不便である。

+
+
+
+
+

:h @/

+
+
+
+
+
+ + + + + +
+

+ + 解決策 + +

+
+
+
+
+

[2020/9/28追記] より簡潔な方法を見つけたので次節に追記した

+
+
+
+
+

前述した :Reverse コマンドの定義を少し変えて、次のようにする:

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

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

+
+
+

この理由は、ユーザー定義関数を実行する際は検索パターンが一度保存され、実行が終了したあと復元されるため。結果として検索パターンが +^ で上書きされることがなくなる。

+
+
+

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追記] より簡潔な方法を見つけたため追記する

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

まさにこのための Exコマンド、:keeppatterns +が存在する。:keeppatterns {command} +のように使い、読んで字の如く、後ろに続く +Exコマンドを「現在の検索パターンを保ったまま」実行する。はるかに分かりやすく意図を表現できる。

+
+
+
+
+

:h :keeppatterns

+
+
+
+
+
+ + + + + +
+

+ + コピペ用再掲 + +

+
+
+
+
" License: Public Domain
+
+command! -bar -range=%
+    \ Reverse
+    \ keeppatterns <line1>,<line2>g/^/m<line1>-1
+
+
+
+
+
+
+
+ + + diff --git a/public/posts/2022-04-09/phperkaigi-2022-tokens/index.html b/public/posts/2022-04-09/phperkaigi-2022-tokens/index.html new file mode 100644 index 0000000..afb6eac --- /dev/null +++ b/public/posts/2022-04-09/phperkaigi-2022-tokens/index.html @@ -0,0 +1,876 @@ + + + + + + + + + + + PHPerKaigi 2022 トークン問題の解説 | REPL: Rest-Eat-Program Loop + + + + + +
+ +
+
+
+
+

PHPerKaigi 2022 トークン問題の解説

+ + + +
+
+
+

更新履歴

+
    + +
  1. + : 公開 +
  2. + +
  3. + : 2問目、3問目の解説を追加、1問目に加筆 +
  4. + +
+
+ + + + + +
+

+ + はじめに + +

+
+
+

本日開始された PHPerKaigi 2022 の PHPer +チャレンジにおいて、弊社 +デジタルサーカス株式会社 の問題を +3問作成した。この記事では、これらの問題の解説をおこなう。

+
+
+

リポジトリはこちら: https://github.com/nsfisis/PHPerKaigi2022-tokens

+
+
+
+ + + + + +
+

+ + 第1問 brainf_ck.php + +

+
+
+

ソースコードはこちら。実行には PHP 8.1 以上が必要なので注意。

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

この問題は、単に適切なバージョンの PHP で動かせばトークンが得られる。

+
+ + + + + +
+

+ + 解説 + +

+
+ + + + + +
+

+ + 絵文字 + +

+
+
+

まず目につくのは大量の絵文字だろう。 PHP +は識別子に使用できる文字の範囲が広く、絵文字も使うことができる。

+
+
+
+ + + + + +
+

+ + プログラム全体 + +

+
+
+

Brainf*ck のインタプリタとプログラムになっている。 Brainf*ck +とは、難解プログラミング言語のひとつであり、ここで説明するよりも +Wikipedia の該当ページを読んだ方がよい。

+
+ +
+

なお、brainf*ck プログラムを普通の書き方で書くと、次のようになる。

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

実行結果はこちら: https://ideone.com/22VWmb

+
+
+

それぞれの絵文字で表された関数が、各命令に対応している。

+
+
+
    +
  • +

    $👉: >

    +
  • +
  • +

    $👈: <

    +
  • +
  • +

    $👍: +

    +
  • +
  • +

    $👎: -

    +
  • +
  • +

    $📝: .

    +
  • +
  • +

    $🤡: [

    +
  • +
  • +

    $🎪: ]

    +
  • +
+
+
+

, (入力) に対応する関数はない +(このプログラムでは使わないので用意していない)。

+
+
+

なお、$🐘 はいわゆる main 関数であり、プログラムの実行部分である。

+
+
+
+ + + + + +
+

+ + 絵文字の選択 + +

+
+
+

おおよそ意味に合致するよう選んでいるが、$🤡$🎪 +は弊社デジタルサーカスにちなんでいる。 また、$🐘 は PHP +のマスコットの象に由来する。

+
+
+
+ + + + + +
+

+ + strict_types + +

+
+
+

declare 文の strict_types に指定できるのは、01 +の数値リテラルだが、 0x00b1 のような値も受け付ける。 今回は、PHP +8.1 から追加された、0O または 0o から始まる八進数リテラルを使った。

+
+
+
+ + + + + +
+

+ + URL + +

+
+
+

ソースコードのライセンスを示したこの部分だが、

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

完全に合法な PHP のコードである。 https: 部分はラベル、// +以降は行コメントになっている。

+
+
+
+ + + + + +
+

+ + リテラルなしで数値を生成する + +

+
+
+

ソースコード中に、ほとんど数値リテラルが書かれていないことにお気づきだろうか。 +PHP では、型変換を利用することで任意の整数を作り出すことができる。

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

[]! を適用すると true が返ってくる。それに + +を適用すると、bool から int ヘの型変換が走り、1 が生成される。10 +はさらにトリッキーだ。まず 10 を作り、. で文字列として結合する +('10')。これに + を適用すると、string から int +への型変換が走り、10 が生まれる (コード量に頓着しないなら、1 を 10 +個足し合わせてももちろん 10 が作れる)。

+
+
+

また、error_reporting に指定しているのは -1 である。 これは、! +によって文字列を false にし、+ によって false0 +にし、さらにビット反転して -1 にしている。

+
+
+
+ + + + + +
+

+ + if 文なしで条件分岐 + +

+
+
+

三項演算子ないし match 式を使うことで、if +を一切書かずに条件分岐ができる。 また、&& / || も使えることがある。 +遅延評価が不要なケースでは、[$t, $f][$cond] +のような形で分岐することもできる。

+
+
+
+ + + + + +
+

+ + whilefor 文なしでループ + +

+
+
+

不動点コンビネータを使って無名再帰する +(詳しい説明は省略する。これらの単語で検索してほしい)。 ここでは、一般に +Z コンビネータとして知られるものを使った ($z)。

+
+
+

実際のところ、$🤡$🎪$🐘 は、一度 Scheme (Lisp の一種) +で書いてから PHP に翻訳する形で記述した。

+
+
+

なお、PHP は末尾再帰の最適化をおこなわない (少なくとも今のところは) +ので、 あまりに長い brainf*ck +プログラムを書くとスタックオーバーフローする。

+
+
+
+
+
+
+
+ + + + + +
+

+ + 第2問 riddle.php + +

+
+
+

ソースコードはこちら。実行には PHP 8.0 以上が必要なので注意。

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

さて、この問題はさきほどのように単純に実行しただけでは、謎のブロックが表示されるだけでトークンは得られない。 +トークンを得るためには、ソースコードを読み、定数 N +を特定する必要がある。

+
+
+

ここでは、私の想定解を解説する。

+
+ + + + + +
+

+ + 読解 + +

+
+
+

まずはソースコードを読んでいく。

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

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

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

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

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

二進数に変換して、

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

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

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

5文字ごとに区切ったあと、改行で結合している。

+
+
+
+ + + + + +
+

+ + ヒント + +

+
+
+

次に、ソースコードに書いてあるヒントを読んでいく。

+
+
+
    +
  • +

    N それ自体は、42 や 8128 +といったような特別な意味を持たず、ランダムに決められている

    +
  • +
  • +

    $token の各要素は、1文字を表す

    +
  • +
  • +

    1文字は 5x5 のセルからなる

    +
  • +
  • +

    出力されるのは、完全な PHPer トークンである

    +
  • +
+
+
+

ここで、PHPer トークンは必ず 記号から始まることを思いだすと、 +$token の最初の数字 0x14B499C は、変換の結果 +になるのではないかと予想される (なお、このことは、リポジトリの README +ファイルに追加ヒントとして書かれている)。

+
+
+
+ + + + + +
+

+ + 解く + +

+
+
+

ここまでわかれば、あと一歩で解ける。すなわち、0x14B499C# +に変換されるような N を見つければよい。

+
+
+

N は高々

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

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

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

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

+
+
+
+
<?php
+
+$x =
+  " # # \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 + +

+
+
+

ソースコードはこちら。

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

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

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

実際にはもう少しパイプで繋げなければならない。

+
+ + + + + +
+

+ + 解説 + +

+
+ + + + + +
+

+ + プログラム全体 + +

+
+
+

コメントにもあるとおり、これは quine (風) のプログラムになっている。 +Quine +とは、自分のソースコードをそっくりそのまま出力するようなプログラムのことである。

+
+
+

このプログラムは、実行すると自身とほとんど同じプログラムを出力する。 +異なるのはトークンになっている部分のみである。

+
+
+
+ + + + + +
+

+ + トークン + +

+
+
+

$xs がトークンに対応している。変換のロジックは riddle.php +とほぼ同じなので省略する。

+
+
+
+ + + + + +
+

+ + 状態保持 + +

+
+
+

トークンの何文字目まで出力したかを、ソースコードを変えずに (quine +なので) 覚えておく必要がある。 +このプログラムでは、トークンが出力されるとソースコードがだんだんと長くなっていくのを利用して、LINE +から情報を取得している。

+
+
+
+ + + + + +
+

+ + ROT 13 + +

+
+
+

Quine は、素朴に書くとプログラムの一部が 2回記述されてしまう。 +これがあまり美しくないので、toquine.php では、ROT 13 +変換を使って難読化した。

+
+
+

それにしてもなぜこんなものが標準ライブラリに……。

+
+
+
+
+
+
+
+ + + + + +
+

+ + おわりに + +

+
+
+

解いていただいたみなさん、また、難易度調整につきあっていただいた社内のみなさん、ありがとうございました。

+
+
+

今回は直前に作りはじめたのもあり、3問だけかつ使い古されたネタばかりになってしまいましたが、 +来年は 5問、より面白い問題を持っていきます。

+
+
+

実はもう作りはじめているので、どうか来年もありますように……。

+
+
+
+
+
+
+ + + diff --git a/public/posts/2022-04-24/term-banner-write-tool-showing-banner-in-terminal/index.html b/public/posts/2022-04-24/term-banner-write-tool-showing-banner-in-terminal/index.html new file mode 100644 index 0000000..628d3f8 --- /dev/null +++ b/public/posts/2022-04-24/term-banner-write-tool-showing-banner-in-terminal/index.html @@ -0,0 +1,222 @@ + + + + + + + + + + + term-banner: ターミナルにバナーを表示するツールを書いた | REPL: Rest-Eat-Program Loop + + + + + +
+ +
+
+
+
+

term-banner: ターミナルにバナーを表示するツールを書いた

+ +
+
+
+

更新履歴

+
    + +
  1. + : 公開 +
  2. + +
  3. + : -f オプションについて追記 +
  4. + +
+
+ + + + + +
+

+ + はじめに + +

+
+
+

こんなものを作った。

+
+
+
+
$ 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 +会議などで画面共有しているときに、休憩を挟んで特定の時刻から再開する、ということがある。 +こういったケースで、画面上に現在の時刻を大きめに表示しておくと、モニタから離れても遠くから時刻がわかるので便利である。

+
+
+

それこそタイマアプリか何かを使えばいいのだが、ターミナルに棲むいきものとしては、住処から離れたくないわけだ。

+
+
+

しばらく便利に使っていたのだが、ひとつ不満点が出てきた。それは、再開する時刻がいつだったかを覚えておかなければならないということだ。 +どこかにメモしておいてもいいが、せっかくなら現在時刻とともに表示させておきたい。

+
+
+

そんなわけで、「任意の文字列をターミナルに表示する」プログラムを書く運びとなった。 +まあ、作らなくても探せばあると思うが、作りたいものは作りたいので知ったことではない。

+
+
+
+ + + + + +
+

+ + プログラム + +

+
+
+

全体の流れは次のようになっている。

+
+
+
    +
  1. +

    フォントファイルを読み込む

    +
  2. +
  3. +

    コマンドライン引数を Shift-JIS に変換する (フォントが Shift-JIS +基準で並んでいるため)

    +
  4. +
  5. +

    1文字ずつレンダリングしていく

    +
  6. +
+
+
+

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/public/posts/2022-05-01/phperkaigi-2022/index.html b/public/posts/2022-05-01/phperkaigi-2022/index.html new file mode 100644 index 0000000..4168136 --- /dev/null +++ b/public/posts/2022-05-01/phperkaigi-2022/index.html @@ -0,0 +1,324 @@ + + + + + + + + + + + PHPerKaigi 2022 | REPL: Rest-Eat-Program Loop + + + + + +
+ +
+
+
+
+

PHPerKaigi 2022

+ + + +
+
+
+

更新履歴

+
    + +
  1. + : 公開 +
  2. + +
+
+ + + + + +
+

+ + はじめに + +

+
+
+

2022-04-09 から 2022-04-11 +にかけて開催された、https://phperkaigi.jp/2022/[PHPerKaigi 2022] +に、一般参加者として参加した。 +弊社https://www.dgcircus.com/[デジタルサーカス株式会社] +はダイヤモンドスポンサーとなっており、スポンサー枠のチケットを使わせていただいた。

+
+
+

昨年のレポートはこちら

+
+
+
+ + + + + +
+

+ + 感想 + +

+
+ + + + + +
+

+ + 厳選おすすめトーク + +

+
+
+

多くの素晴らしいトークの中から、特におすすめのものを +5つ選んだ。是非聞いてほしい。引用部分は、リンク先プロポーザルから引用している。

+
+ +
+
+
+

PHP +はバージョンを追う毎に型宣言、例外、表明、列挙型などの機能が大幅に強化され、堅牢なコードを書くための機能が充実してきました。それらの機能はどう使うと効果的なのでしょうか。

+
+
+

本講演では PHP 8.1 +をベースにして、誤りを想定してチェックするのではなく、そもそも誤りにくい設計とはどのようなものか、つまり「予防」の観点を軸足に、堅牢なコードを導くための様々な設計のヒントをご紹介します。

+
+
+
+ +
+
+
+

PHPを使ってるとよく遭遇する Fatal error / Parse error / Warning / Notice +理解していますか?
+これらのエラー文を理解することで、すぐにエラーの原因に気付き適切に対象できる様になります!
+またそれらを理解した上でのエラーハンドリングを学びましょう。

+
+
+
+ +
+
+
+

毎日流れてくるエラーに皆さんはどう向き合ってますか?
+エラーを出さない事が一番ですが、完全に塞ぐ事は難しいと考えます。
+サービス運用の中で本番環境から発生するエラー(サーバー・クライアントサイド・サードパーティ起因のエラー)への監視体制と、
+エラー・バグ防御のためチームで行っているテストコード文化づくりの話をします。

+
+
+
+ +
+
+
+

昨年開催されたISUCON11にて問題(参考実装)のPHPへの移植を担当させていただきました。

+
+
+

最終的なソースコードこそシンプルなWebアプリケーションではありますが、その裏には
+・「(私の思う)良い設計」を実現するための意思決定
+・「ISUCONの問題」という位置付けに由来する取捨選択
+・移植中に遭遇したトラブルとその解決策
+といった文脈や葛藤が存在しています。

+
+
+

本発表はそれらを共有することで
+・PHPアプリケーションの設計、実装事例として役立ててもらう
+・ISUCONの言語移植に興味を持ってもらう
+・ISUCON問題移植の「実装や設計の練習をする教材」としての可能性を知ってもらう
+ことを目的とします。

+
+
+
+ +
+
+
+

サイボウズの大企業向けグループウェアのGaroon(ガルーン)は、PHPで開発されている20年目の製品です。ガルーン開発チームは日本で40名、ベトナムで50名の計90名ほどのチームになっています。また、コロナ禍でフルリモートでの活動がこの2年ほど継続してきました。

+
+
+

フルリモートになっても仕事はまわっており、継続的にリリースはしていましたが、一方でお互いの考えていることや感じている問題意識が見えづらくなり、モヤモヤを抱えているメンバーが増えていました。

+
+
+

このセッションでは、そういう状況で私がチーム外からジョインし、聴き役に徹しながら見える化することで状況を改善していった取り組みを紹介します。同じように大きなチームやリモートワークで難しさを感じている人に、難しさの原因への気づきや取り組みへのヒントがあれば幸いです。

+
+
+
+
+
+ + + + + +
+

+ + トークン問題の作成 + +

+
+
+

今回は、PHPer チャレンジ用に弊社のトークン問題を +3題作成した。こちらについては別途記事にしているので、そちらを参照されたい。

+
+
+
+ + + + + +
+

+ + PHPer チャレンジ + +

+
+
+

1位になった。
+また、賞品として Echo Show 15 +をいただいた。

+
+
+
+ + + + + +
+

+ + カンファレンス全体への感想 + +

+
+
+

去年の参加レポ +では、こんなことを書いた。

+
+
+
+
+

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/public/posts/2022-08-27/php-conference-okinawa-code-golf/index.html b/public/posts/2022-08-27/php-conference-okinawa-code-golf/index.html new file mode 100644 index 0000000..471678a --- /dev/null +++ b/public/posts/2022-08-27/php-conference-okinawa-code-golf/index.html @@ -0,0 +1,276 @@ + + + + + + + + + + + PHP カンファレンス沖縄で出題されたコードゴルフの問題を解いてみた | REPL: Rest-Eat-Program Loop + + + + + +
+ +
+
+
+
+

PHP カンファレンス沖縄で出題されたコードゴルフの問題を解いてみた

+ + + +
+
+
+

更新履歴

+
    + +
  1. + : 公開 +
  2. + +
+
+ + + + + +
+

+ + はじめに + +

+
+
+

本日 PHP カンファレンス沖縄 2022 +が開催された (らしい)。

+
+
+

カンファレンスには参加できなかったものの、懇親会の LT +で出題されたコードゴルフの問題が Twitter に流れてきたので、解いてみた。

+
+ +
+
+ + + + + +
+

+ + 解 + +

+
+
+

細かいレギュレーションは不明だったので、勝手に定めた。

+
+
+
    +
  • +

    コマンドライン引数の第1引数で受けとる

    +
  • +
  • +

    結果は標準出力に出す

    +
  • +
  • +

    コンマの直後にはスペースを1つ置く

    +
  • +
  • +

    末尾コンマは禁止

    +
  • +
  • +

    数字でないものは入ってこないものとする

    +
  • +
  • +

    負数は入ってこないものとする

    +
  • +
+
+
+

書いたものがこちら:

+
+
+
+
[<?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 バイトとなった (末尾改行を含めずにカウント)。

+
+
+

こちらは改行とスペースを追加したバージョン:

+
+
+
+
[<?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 +を用いた指数表記で、大きな数を短く表す。このコードでは +10000500020001000 を指数表記している。

+
+
+
+ + + + + +
+

+ + foreach や for の中身を1つの文に + +

+
+
+

foreachforif などの後ろには、通常 { +を続けて複数の文を連ねるが、中身の文を1つにしてしまえば、{} +を省略できる。C言語などでも使える。

+
+
+
+ + + + + +
+

+ + $r に初期値を入れない + +

+
+
+

PHP では、$r[] = …​ +のような配列の末尾に追加する式を実行したとき、$r が未定義だった場合は +$r +を勝手に定義して空の配列で初期化してくれる。これを利用すると、$r = []; +のような初期化が不要になる。

+
+
+

ただし、プログラムに 0 が渡されるとループを一度も回らないので、$r +が未定義になってしまい、implode() +に渡すところでエラーになる。それを防ぐために $r ?? [] を使っている。

+
+
+

もし 0 が渡されたケースを無視するなら、これが不要になるので 4 +バイト縮む。

+
+
+
+ + + + + +
+

+ + PHP タグの外に文字列を置く + +

+
+
+

PHP では、<?php ?> +で囲われた部分の外側にある文字列は、そのまま出力される。今回のケースでは、先頭と末尾に必ず +[] を出力するので、そのまま書いてやればよい。

+
+
+
+
+
+ + + + + +
+

+ + おわりに + +

+
+
+

最後になりましたが、https://twitter.com/m3m0r7[めもりー] +さん、楽しい問題をありがとうございました。

+
+
+
+
+
+
+ + + diff --git a/public/posts/2022-08-31/support-for-communty-is-employee-benefits/index.html b/public/posts/2022-08-31/support-for-communty-is-employee-benefits/index.html new file mode 100644 index 0000000..e452599 --- /dev/null +++ b/public/posts/2022-08-31/support-for-communty-is-employee-benefits/index.html @@ -0,0 +1,140 @@ + + + + + + + + + + + 弊社の PHP Foundation への寄付に寄せて | REPL: Rest-Eat-Program Loop + + + + + +
+ +
+
+
+
+

弊社の PHP Foundation への寄付に寄せて

+ +
+
+
+

更新履歴

+
    + +
  1. + : 公開 +
  2. + +
+
+ + + + + +
+

+ + はじめに + +

+
+
+

注: +これは私個人の意見であり、所属する組織を代表するものではありません。

+
+
+

先日、私の勤める デジタルサーカス株式会社 が +PHP Foundation へ $2,000 +の寄付をおこないました。

+
+ +
+

本件を社内でしつこく推進した1人として、推進の理由等を書き残しておきます。

+
+
+
+ + + + + +
+

+ + なぜ? + +

+
+
+

組織としての寄付理由は前掲した記事に譲るとして、ここでは、私が社内でこの件を推進した理由について書くことにします。

+
+
+

当時の考えを端的にまとめた社内チャットの投稿があったので、それを引用します:

+
+
+
+
+

結局これを通したい (私の中での) +最大の理由が、「自分の勤める会社が、これをやる会社であってほしい」というのがあり、↑にしても、感情ベースの理由しか出せていないというのが説得力に欠けている理由なのだと思いますが、寄付の報告が流れてきたり、OSS +のフリーライドの話が流れてきたりするたびに、自尊心が毀損される、というか +(これは大袈裟すぎる表現で、実際にはそこまで明確に傷ついているわけではありませんが)。

+
+
+

追記: 「肩身が狭くなる」というのがより適切でした。

+
+
+
+
+

※文中の「↑にしても」は、ここに載せていない別の投稿を指しています。

+
+
+

OSS を金銭的に支援したり、技術カンファレンスへ協賛したり (あるいは +CTO がカンファレンスを年2で主催したり: +iOSDC PHPerKaigi) +といった行為は、コミュニティへの貢献であると同時に、社員に対する精神的福利厚生でもあると言えるでしょう +(知らんけど)。これらは、技術や技術者を大切にする組織である、ということの、対外的にも対内的にも強力なメッセージなのです。

+
+
+

以上が、私が社内で寄付の件を進めた (かなり私的な) 理由です。

+
+
+
+ + + + + +
+

+ + おわりに + +

+
+
+

最終的に社としての寄付まで漕ぎ着けられたのは、もちろん私の力ではなく役員の方々の決定によるものです。この場を借りて感謝申し上げます。

+
+
+
+
+
+
+ + + diff --git a/public/posts/2022-09-29/write-fizzbuzz-in-php-2-letters-per-line/index.html b/public/posts/2022-09-29/write-fizzbuzz-in-php-2-letters-per-line/index.html new file mode 100644 index 0000000..c2dc8ab --- /dev/null +++ b/public/posts/2022-09-29/write-fizzbuzz-in-php-2-letters-per-line/index.html @@ -0,0 +1,929 @@ + + + + + + + + + + + 【PHP】fizzbuzz を書く。1行あたり2文字で。 | REPL: Rest-Eat-Program Loop + + + + + +
+ +
+
+
+
+

【PHP】fizzbuzz を書く。1行あたり2文字で。

+ + + +
+
+
+

更新履歴

+
    + +
  1. + : 公開 +
  2. + +
  3. + : 小さな文言の修正・変更 +
  4. + +
+
+ + + + + +
+

+ + 記事の構成について + +

+
+
+

この記事は、普通の fizzbuzz +を徐々に変形して最終形にしていく、という構成で書かれている。最終形を見てどのような仕組みで動いているのか解読してから解説を読みたい、というかたがいれば、 +このページ +にソースコードがあるので、そちらを先に見てほしい。

+
+
+
+ + + + + +
+

+ + レギュレーション + +

+
+
+

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でのこの制約はほとんど無意味に等しい。

+
+
+
+
#\
+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 +では文字列リテラル中に生の改行が書けるので

+
+
+
+
$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_ +系の関数を使って、適当に置き換えるとしよう。

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

array_walkrangeprintf といった for +よりも長いトークンが現れてしまったが、これは次節で直すことにする。なお、echo +は文 (statement) であり式 (expression) ではないので、式である printf +に置き換えた。

+
+
+
+ + + + + +
+

+ + 関数呼び出しの短縮 + +

+
+
+

rangearray_walkprintf +は長すぎるのでどうにかせねばならない。ここで、PHP +の可変関数を使う。可変関数とは、関数名が文字列として入った変数を経由して、関数を呼び出す機能である。

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

これで関数を呼び出している所は短くなった。では、$r$w や +$p、また 'Fizz''Buzz' はどうやって +1行2文字に収めるのか。次のテクニックへ移ろう。

+
+
+
+ + + + + +
+

+ + 余談: PHP 8.x で動作しなくてもいいなら + +

+
+
+

今回使ったテクニックを説明する前に、余談として、文字列リテラルの短縮法として今回採用しなかったものを紹介する。

+
+
+
+
+
    +
  • +

    PHP 7.4〜8.1 で動作すること

    +
  • +
+
+
+
+
+

というルールがない場合、「未定義の定数が評価された場合、その定数の名前が値になる」という +PHP 7.x までの仕様が利用できる。例えば、 Fizz +という文字列が欲しければ、次のようにする。

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

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

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

むしろ、このことがわかっていたからこそ PHP 8.x +での動作を要件に課したところがある。

+
+
+
+ + + + + +
+

+ + 文字列リテラルの短縮 + +

+
+
+

実際に使った手法の説明に移る。

+
+
+

ずばり、文字列同士のビット演算を使う。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
+
+
+
+

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

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

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

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

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

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

1行あたり2文字で、range +という文字列を生成することに成功した。他の必要な文字列にも、同様の処理をほどこす。

+
+
+

備考: Buzz 中にある小文字の u は、このロジックだと non-printable +な文字になってしまう。ここまでのテクニックを駆使すれば回避するのはそう難しくないので、考えてみてほしい。

+
+
+
+
+
+ + + + + +
+

+ + 完成系 + +

+
+
+

完成したものがこちら。

+
+
+
+
<?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 +なら大丈夫だろう。知らんけど)。

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

なお、ここでは簡単のため出力に printf をそのまま使っているが、実際には +printf という文字列を合成して可変関数で呼び出す。

+
+
+

ただし、これでは

+
+
+
+
+
    +
  • +

    スペースやタブを使用しないこと

    +
  • +
+
+
+
+
+

に違反してしまう。スペースが使えないと引数とコマンドを区切れない。これは困った。

+
+
+

もうこれ以上は不可能だと思っていたのだが、この記事の執筆中に解決する方法を思いついたので載せておく。

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

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

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

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

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

これは、前掲したコマンドと同じだ。かくして、スペースを陽に書かずにシェルをおおよそ自由に扱えるようになった。Fizzbuzz +のワンライナーくらいすぐ書けるだろうから、あとはなんとかなるだろう +(試してないけど)。

+
+
+

ということでこれは別解ということにしておく。

+
+
+

ちなみに、PHP 8.2 からは、この記法で Warning が出るようになるようだ。

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

最新版で警告が出るというのも美しくないので、私としては本編の解法を推す。

+
+
+
+
+
+
+ + + diff --git a/public/posts/2022-10-23/phperkaigi-2023-unused-token-quiz-1/index.html b/public/posts/2022-10-23/phperkaigi-2023-unused-token-quiz-1/index.html new file mode 100644 index 0000000..efb7239 --- /dev/null +++ b/public/posts/2022-10-23/phperkaigi-2023-unused-token-quiz-1/index.html @@ -0,0 +1,291 @@ + + + + + + + + + + + PHPerKaigi 2023: ボツになったトークン問題 その 1 | REPL: Rest-Eat-Program Loop + + + + + +
+ +
+
+
+
+

PHPerKaigi 2023: ボツになったトークン問題 その 1

+ + + +
+
+
+

更新履歴

+
    + +
  1. + : 公開 +
  2. + +
+
+ + + + + +
+

+ + はじめに + +

+
+
+

2023 年 3 月 23 日から 25 日にかけて開催予定 (記事執筆時点) +の、 PHPerKaigi 2023 +において、昨年と同様に、弊社 デジタルサーカス株式会社 +から、トークン問題を出題予定である。

+
+
+

昨年のトークン問題の記事はこちら: +PHPerKaigi 2022 +トークン問題の解説

+
+
+

すでに 2023 +年用の問題は作成済みであるが、その制作過程の中でいくつかボツ問ができた。せっかくなので、PHPerKaigi +開催を待つ間に紹介しようと思う。

+
+
+

10 月から 2 月まで、毎月 1 記事ずつ公開していく予定 (忘れていなければ)。

+
+
+
+ + + + + +
+

+ + 問題 + +

+
+
+

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

+
+
+
+
<?php
+
+ = $argv[1] ?? null;
+if ( === null) {
+  exit('No input.');
+}
+ = trim();
+if (!is_numeric()) {
+  exit('Invalid input.');
+}
+
+$s = implode(array_map(chr(...), str_split(, 2)));
+
+preg_match('/(\x23.+?) /', $s, $m);
+$t = $m[1] ?? '';
+
+if (md5($t) === '056e831a4146bf123e8ea16613303d2e') {
+  echo "Token: {$t}\n";
+} else {
+  echo "Failed.\n";
+}
+
+
+
+
+ + + + + +
+

+ + トークン入手方法 + +

+
+
+

ソースを見るとわかるとおり、$argv[1] を参照している。それを +なる変数に代入しているので、円周率を渡してみる。

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

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

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

だめだった。これを成功するまで繰り返す。

+
+
+

最初にトークンが得られるのは、小数点以下 16 +桁目まで入力したときで、こうなる。

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

めでたくトークン「#YO」が手に入った。

+
+
+
+ + + + + +
+

+ + 解説 + +

+
+
+

短いので頭から追っていく。

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

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

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

を 2 文字ごとに区切り (str_split)、数値を ASCII +コードと見做して文字に変換 (chr) して結合 (implode) している。

+
+
+

例えば、'656667' だったとすると、656667 に対応した +'A''B''C' へと変換され、'ABC' になる。

+
+
+
+
 = '656667';
+$s = implode(array_map(chr(...), str_split(, 2)));
+echo $s;
+// => ABC
+
+
+
+
+
preg_match('/(\x23.+?) /', $s, $m);
+$t = $m[1] ?? '';
+
+
+
+

正規表現でマッチングしている。\x23# +と同じであることに留意すると、この正規表現は「# から始まる 2 +以上の長さ (含 #) +の文字列で、最初に現れるスペースまで」にマッチする。つまりこれは、PHPerKaigi +におけるトークンである。

+
+
+

なお、# を直接書いていないのは、/#.+?) / と書くと、#.+?) +という意図せぬトークンが登録されてしまうからである。

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

最後にトークンのハッシュ値を見て、想定解かどうかを確認する。

+
+
+
+ + + + + +
+

+ + おわりに + +

+
+
+

円周率を何桁も計算して ASCII +コード経由で文字列化すれば、トークンっぽいものがどこかで出てくるのではないか、と考えて生まれた作品。

+
+
+

最初は真面目に円周率の計算プログラムを組んでいたのだが、いざ動かしてみるとやけに浅いところにあったので驚いた +(ちなみに、それでも M_PIpi() +では精度が足りない)。見つけたときは狂喜したものの、冷静になってみると大して面白くなかったのでボツになった。むしろ、100 +万桁目くらいに埋まっていてくれたほうがよかったかもしれない。

+
+
+
+
+
+
+ + + diff --git a/public/posts/2022-10-28/setup-server-for-this-site/index.html b/public/posts/2022-10-28/setup-server-for-this-site/index.html new file mode 100644 index 0000000..c070c03 --- /dev/null +++ b/public/posts/2022-10-28/setup-server-for-this-site/index.html @@ -0,0 +1,630 @@ + + + + + + + + + + + 【備忘録】このサイト用の VPS をセットアップしたときのメモ | REPL: Rest-Eat-Program Loop + + + + + +
+ +
+
+
+
+

【備忘録】このサイト用の VPS をセットアップしたときのメモ

+ + + +
+
+
+

更新履歴

+
    + +
  1. + : 公開 +
  2. + +
+
+ + + + + +
+

+ + はじめに + +

+
+
+

これまでこの blog は GitHub Pages でホストしていたのだが、先日 VPS +に移行した。そのときにおこなったサーバのセットアップ作業を書き残しておく。99 +% 自分用の備忘録。別のベンダに移したりしたくなったら見に来る。

+
+
+

未来の自分へ: 特に自動化してないので、せいぜい苦しんでくれ。

+
+
+
+ + + + + +
+

+ + VPS + +

+
+
+

さくらの VPS の 2 GB +プラン。そこまで真面目に選定していないので、困ったら移動するかも。

+
+
+
+ + + + + +
+

+ + 事前準備 + +

+
+ + + + + +
+

+ + サーバのホスト名を決める + +

+
+
+

モチベーションが上がるという効能がある。今回は藤原定家から取って +``teika'' +にした。たいていいつも源氏物語の帖か小倉百人一首の歌人から選んでいる。

+
+
+
+ + + + + +
+

+ + SSH の鍵生成 + +

+
+
+

ローカルマシンで鍵を生成する。

+
+
+
+
$ 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 に設定しておく。

+
+
+
+
Host teika
+    HostName **********
+    User **********
+    Port **********
+    IdentityFile ~/.ssh/teika.key
+
+
+
+
+
+
+ + + + + +
+

+ + 基本のセットアップ + +

+
+ + + + + +
+

+ + SSH 接続 + +

+
+
+

VPS 契約時に設定した管理者ユーザとパスワードを使ってログインする。

+
+
+
+ + + + + +
+

+ + ユーザを作成する + +

+
+
+

管理者ユーザで作業すると危ないので、メインで使うユーザを作成する。sudo +グループに追加して sudo できるようにし、su で切り替え。

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

+ + ホスト名を変える + +

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

+ + 公開鍵を置く + +

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

authorized_keys には、ローカルで生成した ~/.ssh/teika.key.pub と +~/.ssh/github2teika.key.pub の内容をコピーする。

+
+
+
+ + + + + +
+

+ + SSH の設定 + +

+
+
+

SSH の設定を変更し、少しでも安全にしておく。

+
+
+
+
$ sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak
+$ sudo vi /etc/ssh/sshd_config
+
+
+
+
    +
  • +

    Port を変更

    +
  • +
  • +

    PermitRootLoginno

    +
  • +
  • +

    PasswordAuthenticationno

    +
  • +
+
+
+

そして設定を反映。

+
+
+
+
$ sudo systemctl restart sshd
+$ sudo systemctl status sshd
+
+
+
+
+ + + + + +
+

+ + SSH で接続確認 + +

+
+
+

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

+
+
+
+
$ ssh teika
+
+
+
+
+ + + + + +
+

+ + ポートの遮断 + +

+
+
+

デフォルトの 22 番を閉じ、設定したポートだけ空ける。

+
+
+
+
$ sudo ufw deny ssh
+$ sudo ufw allow *******
+$ sudo ufw enable
+$ sudo ufw reload
+$ sudo ufw status
+
+
+
+

ここでもう一度 SSH の接続確認を挟む。

+
+
+
+ + + + + +
+

+ + GitHub 用の SSH 鍵 + +

+
+
+

GitHub に置いてある private リポジトリをサーバから clone したいので、SSH +鍵を生成して置いておく。

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

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

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

設定はこう。

+
+
+
+
Host github.com
+    HostName github.com
+    User git
+    IdentityFile ~/.ssh/github.key
+
+
+
+

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

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

+ + パッケージの更新 + +

+
+
+
+
$ sudo apt update
+$ sudo apt upgrade
+$ sudo apt update
+$ sudo apt upgrade
+$ sudo apt autoremove
+
+
+
+
+
+
+ + + + + +
+

+ + サイトホスティング用のセットアップ + +

+
+ + + + + +
+

+ + DNS に IP アドレスを登録する + +

+
+
+

このサーバは固定の IP アドレスがあるので、A +レコードに直接入れるだけで済んだ。

+
+
+
+ + + + + +
+

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

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

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

+
+
+
+
sudo adduser ********** docker
+
+
+
+
+ + + + + +
+

+ + HTTP/HTTPS を通す + +

+
+
+

80 番と 443 番を空ける。

+
+
+
+
$ sudo ufw allow 80/tcp
+$ sudo ufw allow 443/tcp
+$ sudo ufw reload
+$ sudo ufw status
+
+
+
+
+ + + + + +
+

+ + リポジトリのクローン + +

+
+
+
+
$ cd
+$ git clone git@github.com:nsfisis/nsfisis.dev.git
+$ cd nsfisis.dev
+$ git submodule update --init
+
+
+
+
+ + + + + +
+

+ + certbot で証明書取得 + +

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

+ + サーバを稼動させる + +

+
+
+
+
$ make serve
+
+
+
+
+
+
+ + + + + +
+

+ + 感想 + +

+
+
+

(業務でなく) +個人だと数年ぶりのサーバセットアップで、これだけでも割と時間を食ってしまった。とはいえ式年遷宮は楽しいので、これからも定期的にやっていきたい。コンテナデプロイにしたい気持ちもあるのだが、色々実験したい関係上、本物のサーバも欲しくはある。次の式年遷宮では、手順の一部だけでも自動化したいところ。

+
+
+
+
+
+
+ + + diff --git a/public/posts/index.html b/public/posts/index.html new file mode 100644 index 0000000..029251b --- /dev/null +++ b/public/posts/index.html @@ -0,0 +1,315 @@ + + + + + + + + + + + + + + + + + + + + Posts | REPL: Rest-Eat-Program Loop + + + + + +
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + diff --git a/public/style.css b/public/style.css new file mode 100644 index 0000000..ef2dea5 --- /dev/null +++ b/public/style.css @@ -0,0 +1,480 @@ +/* + * Paper + * A simple, clean, flexible Hugo theme + * https://github.com/nanxiaobei/hugo-paper + * Designed by MR.LEE (https://mrlee.me/) + * Updated in 2020.6.12 + */ + +/* Theme +-------------------------------------------------- */ +:root { + --gap: 24px; + --content-gap: 40px; + --nav-width: 1024px; + --main-width: 640px; + --header-height: 60px; + --footer-height: 60px; + --radius: 8px; + + --theme: #fff; + --entry: #fff; + --primary: rgba(0, 0, 0, 0.88); + --secondary: rgba(0, 0, 0, 0.56); + --tertiary: rgba(0, 0, 0, 0.16); + --content: rgba(0, 0, 0, 0.88); + + --code-bg: #f5f5f5; + --border: #eee; +} +.list { + background: var(--code-bg); +} +/* Reset +-------------------------------------------------- */ +*, +*::before, +*::after { + box-sizing: border-box; +} +html { + -webkit-tap-highlight-color: transparent; +} +body { + margin: 0; + color: var(--primary); + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, + 'Open Sans', 'Helvetica Neue', sans-serif; + font-size: 18px; + line-height: 1.8; + word-break: break-word; + background: var(--theme); +} +article, +aside, +figcaption, +figure, +footer, +header, +hgroup, +main, +nav, +section { + display: block; +} +h1, +h2, +h3, +h4, +h5, +h6 { + margin-top: 0; + margin-bottom: 0; + color: var(--primary); + line-height: 1.2; +} +p { + margin-top: 0; + margin-bottom: 0; +} +strong, +b { + font-weight: bold; +} +ul { + margin: 0; + padding: 0; +} +a { + color: var(--primary); + text-decoration: none; +} +figure { + margin: 0; +} +table { + width: 100%; + border-collapse: collapse; + border-spacing: 0; +} +button, +input, +textarea { + padding: 0; + font: inherit; + background: transparent; + border: 0; + -webkit-appearance: none; +} +button, +input[type='button'], +input[type='submit'] { + cursor: pointer; +} +input, +textarea { + padding: 0; + border: 0; + outline: 0; +} +input:-webkit-autofill, +textarea:-webkit-autofill { + box-shadow: 0 0 0 50px var(--theme) inset; +} +img { + display: block; + max-width: 100%; +} +/* Header +-------------------------------------------------- */ +.nav { + display: flex; + flex-wrap: wrap; + justify-content: space-between; + max-width: calc(var(--nav-width) + var(--gap) * 2); + margin-left: auto; + margin-right: auto; +} +.nav a { + display: block; + line-height: var(--header-height); +} +.logo, +.menu { + margin-left: var(--gap); + margin-right: var(--gap); +} +.logo a { + font-size: 24px; + font-weight: 700; +} +.menu { + display: flex; + list-style: none; + word-break: keep-all; + overflow-x: auto; +} +.menu li + li { + margin-left: var(--gap); +} +.menu a { + font-size: 16px; +} +/* Main +-------------------------------------------------- */ +.main { + position: relative; + min-height: calc(100vh - var(--header-height) - var(--footer-height)); + max-width: calc(var(--main-width) + var(--gap) * 2); + margin-left: auto; + margin-right: auto; + padding: var(--gap); +} +.page-header { + margin-bottom: 24px; +} +.page-header h1 { + font-size: 40px; +} +.pagination { + display: flex; +} +.pagination a { + color: var(--theme); + font-size: 13px; + line-height: 36px; + background: var(--primary); + border-radius: calc(36px / 2); +} +.pagination .prev { + padding-left: 16px; + padding-right: 18px; +} +.pagination .next { + margin-left: auto; + padding-left: 18px; + padding-right: 16px; +} +/* Post entry +-------------------------------------------------- */ +.post-entry { + position: relative; + margin-bottom: var(--gap); + padding: var(--gap); + background: var(--entry); + border-radius: var(--radius); + transition: transform 0.1s; +} +.post-entry:active { + transform: scale(0.96); +} +.tag-entry .entry-content, +.tag-entry .entry-footer { + display: none; +} +.entry-header h2 { + font-size: 24px; +} +.entry-content { + margin-top: 8px; + margin-bottom: 8px; + color: var(--secondary); + font-size: 14px; + line-height: 1.6; + overflow: hidden; + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 2; +} +.entry-footer { + color: var(--secondary); + font-size: 13px; +} +.entry-link { + position: absolute; + left: 0; + right: 0; + top: 0; + bottom: 0; +} +/* Post single +-------------------------------------------------- */ +.post-header { + margin-top: 24px; + margin-bottom: var(--content-gap); +} +.post-title { + margin-bottom: 2px; + font-size: 40px; + transform: translateX(-2px); +} +.post-meta { + color: var(--secondary); + font-size: 14px; +} +.post-content { + color: var(--content); +} +.post-content h1 { + margin-top: 40px; + margin-bottom: 32px; +} +.post-content h2 { + margin-top: 32px; + margin-bottom: 24px; +} +.post-content h3, +.post-content h4, +.post-content h5, +.post-content h6 { + margin-top: 24px; + margin-bottom: 16px; +} +.post-content h1 { + font-size: 40px; +} +.post-content h2 { + font-size: 32px; +} +.post-content h3 { + font-size: 24px; +} +.post-content h4 { + font-size: 16px; +} +.post-content h5 { + font-size: 14px; +} +.post-content h6 { + font-size: 12px; +} +.post-content a { + box-shadow: 0 1px 0 var(--primary); +} +.post-content a code { + margin-left: 0; + margin-right: 0; + border-radius: 0; + box-shadow: 0 -1px 0 var(--primary) inset; +} +.post-content del { + text-decoration: none; + background: linear-gradient(to right, var(--primary) 100%, transparent 0) 0 50% / 1px 1px repeat-x; +} +.post-content p, +.post-content ul, +.post-content ol, +.post-content dl { + margin-bottom: var(--content-gap); +} +.post-content ul, +.post-content ol { + padding-left: 20px; +} +.post-content li { + margin-top: 5px; +} +.post-content li p { + margin-bottom: 0; +} +.post-content dl { + display: flex; + flex-wrap: wrap; + margin: 0; +} +.post-content dt { + width: 25%; + font-weight: 700; +} +.post-content dd { + width: 75%; + margin-left: 0; + padding-left: 10px; +} +.post-content dt ~ dt, +.post-content dd ~ dd { + margin-top: 10px; +} +.post-content table { + margin-bottom: 32px; +} +.post-content table th, +.post-content table:not(.highlighttable) td { + min-width: 80px; + padding: 12px 8px; + line-height: 1.5; + border-bottom: 1px solid var(--border); +} +.post-content table th { + font-size: 14px; + text-align: left; +} +.post-content table:not(.highlighttable) td code:only-child { + margin-left: 0; + margin-right: 0; +} +.post-content .highlight, +.post-content pre { + margin-left: calc(var(--gap) * -1); + margin-right: calc(var(--gap) * -1); + margin-bottom: 32px; + border-radius: var(--radius); + overflow-x: auto; +} +.post-content ul pre { + margin-left: calc(var(--gap) * -2); +} +.post-content .highlight pre { + margin: 0; +} +/* table */ +.post-content .highlighttable { + table-layout: fixed; +} +.post-content .highlighttable td:first-child { + width: 40px; +} +.post-content .highlighttable td .linenodiv { + padding-right: 0 !important; +} +.post-content .highlighttable td .linenodiv pre, +.post-content .highlighttable td .highlight { + margin-bottom: 0; +} +.post-content .highlighttable td .highlight pre code::-webkit-scrollbar { + display: none; +} +/* inline */ +.post-content .highlight span { + background: transparent !important; +} + +.post-content code { + margin-left: 4px; + margin-right: 4px; + padding: 4px 6px; + font-family: Menlo, Monaco, 'Courier New', Courier, monospace; + font-size: 0.78em; + line-height: 1.5; + background: var(--code-bg); + border-radius: 2px; +} +.post-content pre code { + display: block; + margin-left: 0; + margin-right: 0; + padding: var(--gap); + background: transparent; + border-radius: 0; +} +.post-content blockquote { + margin: 0 0 0 calc(var(--gap) * -1); + padding: 0 0 0 21px; + border-left: 3px solid var(--primary); +} +.post-content hr { + height: 1px; + margin-top: 56px; + margin-bottom: 56px; + background: var(--tertiary); + border-top: 0; + border-bottom: 0; +} +.post-content iframe { + max-width: 100%; +} +.post-tags li { + display: inline-block; +} +.post-tags li + li { + margin-left: 3px; +} +.post-tags a { + display: block; + padding-left: 14px; + padding-right: 14px; + color: var(--secondary); + font-size: 14px; + line-height: 34px; + background: var(--code-bg); + border-radius: 4px; +} +.post-tags a:hover { + background: var(--border); +} +/* Footer +-------------------------------------------------- */ +.footer { + max-width: calc(var(--main-width) + var(--gap) * 2); + margin-left: auto; + margin-right: auto; + padding: calc((var(--footer-height) - var(--gap)) / 2) var(--gap); + color: var(--secondary); + font-size: 12px; + text-align: center; + line-height: 24px; +} +.footer span { + margin-left: 1px; + margin-right: 1px; +} +.footer a { + color: inherit; +} +.footer a:hover { + color: var(--primary); + border-bottom: 1px solid var(--primary); +} +/* 404 +-------------------------------------------------- */ +.not-found { + position: absolute; + left: 0; + right: 0; + display: flex; + align-items: center; + justify-content: center; + height: 80%; + font-size: 160px; + font-weight: 700; +} diff --git a/public/tags/conference/index.html b/public/tags/conference/index.html new file mode 100644 index 0000000..4423eee --- /dev/null +++ b/public/tags/conference/index.html @@ -0,0 +1,107 @@ + + + + + + + + + + + + + + + + + + + + カンファレンス | REPL: Rest-Eat-Program Loop + + + + + +
+ +
+
+ + + + + + + + + + +
+ + + diff --git a/public/tags/cpp/index.html b/public/tags/cpp/index.html new file mode 100644 index 0000000..5cc157b --- /dev/null +++ b/public/tags/cpp/index.html @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + C++ | REPL: Rest-Eat-Program Loop + + + + + +
+ +
+
+ + + + +
+ + + diff --git a/public/tags/cpp17/index.html b/public/tags/cpp17/index.html new file mode 100644 index 0000000..5585a26 --- /dev/null +++ b/public/tags/cpp17/index.html @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + C++ 17 | REPL: Rest-Eat-Program Loop + + + + + +
+ +
+
+ + + + +
+ + + diff --git a/public/tags/note-to-self/index.html b/public/tags/note-to-self/index.html new file mode 100644 index 0000000..4cdabd6 --- /dev/null +++ b/public/tags/note-to-self/index.html @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + 備忘録 | REPL: Rest-Eat-Program Loop + + + + + +
+ +
+
+ + + + +
+ + + diff --git a/public/tags/php/index.html b/public/tags/php/index.html new file mode 100644 index 0000000..cd9a853 --- /dev/null +++ b/public/tags/php/index.html @@ -0,0 +1,139 @@ + + + + + + + + + + + + + + + + + + + + PHP | REPL: Rest-Eat-Program Loop + + + + + +
+ +
+
+ + + + + + + + + + + + + + +
+ + + diff --git a/public/tags/phpcon/index.html b/public/tags/phpcon/index.html new file mode 100644 index 0000000..45fe74f --- /dev/null +++ b/public/tags/phpcon/index.html @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + PHP カンファレンス | REPL: Rest-Eat-Program Loop + + + + + +
+ +
+
+ + + + +
+ + + diff --git a/public/tags/phperkaigi/index.html b/public/tags/phperkaigi/index.html new file mode 100644 index 0000000..e59c245 --- /dev/null +++ b/public/tags/phperkaigi/index.html @@ -0,0 +1,107 @@ + + + + + + + + + + + + + + + + + + + + PHPerKaigi | REPL: Rest-Eat-Program Loop + + + + + +
+ +
+
+ + + + + + + + + + +
+ + + diff --git a/public/tags/python/index.html b/public/tags/python/index.html new file mode 100644 index 0000000..b15dfb6 --- /dev/null +++ b/public/tags/python/index.html @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + Python | REPL: Rest-Eat-Program Loop + + + + + +
+ +
+
+ + + + +
+ + + diff --git a/public/tags/python3/index.html b/public/tags/python3/index.html new file mode 100644 index 0000000..2824a04 --- /dev/null +++ b/public/tags/python3/index.html @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + Python 3 | REPL: Rest-Eat-Program Loop + + + + + +
+ +
+
+ + + + +
+ + + diff --git a/public/tags/ruby/index.html b/public/tags/ruby/index.html new file mode 100644 index 0000000..40c7b4f --- /dev/null +++ b/public/tags/ruby/index.html @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + Ruby | REPL: Rest-Eat-Program Loop + + + + + +
+ +
+
+ + + + + + +
+ + + diff --git a/public/tags/ruby3/index.html b/public/tags/ruby3/index.html new file mode 100644 index 0000000..47a47b7 --- /dev/null +++ b/public/tags/ruby3/index.html @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + Ruby 3 | REPL: Rest-Eat-Program Loop + + + + + +
+ +
+
+ + + + +
+ + + diff --git a/public/tags/rust/index.html b/public/tags/rust/index.html new file mode 100644 index 0000000..da5efee --- /dev/null +++ b/public/tags/rust/index.html @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + Rust | REPL: Rest-Eat-Program Loop + + + + + +
+ +
+
+ + + + +
+ + + diff --git a/public/tags/vim/index.html b/public/tags/vim/index.html new file mode 100644 index 0000000..e76b079 --- /dev/null +++ b/public/tags/vim/index.html @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + Vim | REPL: Rest-Eat-Program Loop + + + + + +
+ +
+
+ + + + + + +
+ + + -- cgit v1.2.3-70-g09d2