From 5d5367ed00c22cc194b8a2411b2b4b828751003b Mon Sep 17 00:00:00 2001 From: nsfisis Date: Wed, 31 Aug 2022 23:30:53 +0900 Subject: update hugo --- .gitignore | 1 + docs/404.html | 2 +- docs/feed.xml | 940 ++++++++++----------- docs/index.html | 2 +- docs/page/1/index.html | 11 +- docs/page/2/index.html | 2 +- docs/posts/2021-03-05/my-first-post/index.html | 2 +- docs/posts/2021-03-30/phperkaigi-2021/index.html | 2 +- .../index.html | 48 +- .../python-unbound-local-error/index.html | 54 +- .../ruby-detect-running-implementation/index.html | 26 +- .../ruby-then-keyword-and-case-in/index.html | 150 ++-- .../rust-where-are-primitive-types-from/index.html | 178 ++-- .../index.html | 38 +- .../vim-swap-order-of-selected-lines/index.html | 46 +- .../2022-04-09/phperkaigi-2022-tokens/index.html | 388 ++++----- .../index.html | 4 +- docs/posts/2022-05-01/phperkaigi-2022/index.html | 2 +- .../php-conference-okinawa-code-golf/index.html | 28 +- .../index.html | 11 +- docs/posts/feed.xml | 940 ++++++++++----------- docs/posts/index.html | 2 +- docs/posts/my-first-post/index.html | 11 +- docs/posts/page/1/index.html | 11 +- docs/posts/page/2/index.html | 2 +- docs/posts/phperkaigi-2021/index.html | 11 +- docs/posts/phperkaigi-2022-tokens/index.html | 11 +- docs/posts/python-unbound-local-error/index.html | 11 +- .../ruby-detect-running-implementation/index.html | 11 +- .../posts/ruby-then-keyword-and-case-in/index.html | 11 +- .../rust-where-are-primitive-types-from/index.html | 11 +- .../index.html | 11 +- .../vim-swap-order-of-selected-lines/index.html | 11 +- docs/style.css | 2 +- docs/tags/conference/feed.xml | 412 ++++----- docs/tags/conference/index.html | 2 +- docs/tags/conference/page/1/index.html | 11 +- docs/tags/cpp/feed.xml | 46 +- docs/tags/cpp/index.html | 2 +- docs/tags/cpp/page/1/index.html | 11 +- docs/tags/cpp17/feed.xml | 46 +- docs/tags/cpp17/index.html | 2 +- docs/tags/cpp17/page/1/index.html | 11 +- docs/tags/index.html | 2 +- docs/tags/my-programs/feed.xml | 2 +- docs/tags/my-programs/index.html | 2 +- docs/tags/my-programs/page/1/index.html | 11 +- docs/tags/page/1/index.html | 11 +- docs/tags/php/feed.xml | 412 ++++----- docs/tags/php/index.html | 2 +- docs/tags/php/page/1/index.html | 11 +- docs/tags/phpcon/feed.xml | 26 +- docs/tags/phpcon/index.html | 2 +- docs/tags/phpcon/page/1/index.html | 11 +- docs/tags/phperkaigi/feed.xml | 386 ++++----- docs/tags/phperkaigi/index.html | 2 +- docs/tags/phperkaigi/page/1/index.html | 11 +- docs/tags/python/feed.xml | 52 +- docs/tags/python/index.html | 2 +- docs/tags/python/page/1/index.html | 11 +- docs/tags/python3/feed.xml | 52 +- docs/tags/python3/index.html | 2 +- docs/tags/python3/page/1/index.html | 11 +- docs/tags/ruby/feed.xml | 172 ++-- docs/tags/ruby/index.html | 2 +- docs/tags/ruby/page/1/index.html | 11 +- docs/tags/ruby3/feed.xml | 148 ++-- docs/tags/ruby3/index.html | 2 +- docs/tags/ruby3/page/1/index.html | 11 +- docs/tags/rust/feed.xml | 176 ++-- docs/tags/rust/index.html | 2 +- docs/tags/rust/page/1/index.html | 11 +- docs/tags/vim/feed.xml | 80 +- docs/tags/vim/index.html | 2 +- docs/tags/vim/page/1/index.html | 11 +- 75 files changed, 2709 insertions(+), 2474 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2a8645f --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.hugo_build.lock diff --git a/docs/404.html b/docs/404.html index 002a854..7936109 100644 --- a/docs/404.html +++ b/docs/404.html @@ -15,7 +15,7 @@ - + diff --git a/docs/feed.xml b/docs/feed.xml index 5fbdb0e..afff771 100644 --- a/docs/feed.xml +++ b/docs/feed.xml @@ -16,7 +16,7 @@ はじめに

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

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

-

ツイート: https://twitter.com/m3m0r7/status/1563397620231712772
+

ツイート: https://twitter.com/m3m0r7/status/1563397620231712772
スライド: https://speakerdeck.com/memory1994/php-conference-okinawa-2022-extra?slide=3

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

@@ -29,19 +29,19 @@
  • 負数は入ってこないものとする
  • 書いたものがこちら:

    -
    [<?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??[]);?>]
    +

    しめて 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 ?? []);
    -
    -?>]
    -

    使用したテクニック

    +
    [<?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つの文に

    @@ -146,7 +146,7 @@ $n = $argv[1https://blog.nsfisis.dev/posts/2022-04-24/term-banner-write-tool-showing-banner-in-terminal/ はじめに

    こんなものを作った。

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

    term-banner のスクリーンショット

    コマンドライン引数として渡した文字列をターミナルに大きく表示する。

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

    @@ -200,76 +200,76 @@ PNG が標準ライブラリにあったり、Shift-JIS のエンコーディン

    リポジトリはこちら: 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
    +
    +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 で動かせばトークンが得られる。

    解説

    絵文字

    まず目につくのは大量の絵文字だろう。 @@ -323,18 +323,18 @@ Brainf*ck とは、難解プログラミング言語のひとつであり、こ 今回は、PHP 8.1 から追加された、0O または 0o から始まる八進数リテラルを使った。

    URL

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

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

    完全に合法な PHP のコードである。 +

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

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

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

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

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

    []! を適用すると true が返ってくる。それに + +

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

    []! を適用すると true が返ってくる。それに + を適用すると、bool から int ヘの型変換が走り、1 が生成される。10 はさらにトリッキーだ。まず 10 を作り、. で文字列として結合する ('10')。これに + を適用すると、string から int @@ -354,57 +354,57 @@ 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";
    -}
    -

    さて、この問題はさきほどのように単純に実行しただけでは、謎のブロックが表示されるだけでトークンは得られない。 +

    <?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文字ごとに区切ったあと、改行で結合している。

    +
    $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文字ごとに区切ったあと、改行で結合している。

    ヒント

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

      @@ -418,76 +418,76 @@ $token = [

      解く

      ここまでわかれば、あと一歩で解ける。すなわち、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 が得られる。

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

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

      +
      <?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 (風) のプログラムになっている。 @@ -522,27 +522,27 @@ Quine とは、自分のソースコードをそっくりそのまま出力す


      前置き

      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 のソースを追ってみた。

      +
      #![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 そのものの知識は不要 (というよりも筆者自身がよく知らない)

      @@ -553,91 +553,91 @@ Quine とは、自分のソースコードをそっくりそのまま出力す

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

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

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

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

      -
      $ git grep "\bi128\b"
      +
      $ 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 にも、

      +
      /// 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) する。 +

          /// 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 という名前の別の型が見つかるからだ。

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

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

      まとめ

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

      ]]> @@ -656,61 +656,61 @@ rustc_resolve/src/lib.rs: table.insert(sym::i128, Int(IntTy::I128));

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

      then とは

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

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

      このキーワードが現れうる場所はいくつかあり、ifunlessrescuecase 構文がそれに当たる。 +

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

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

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

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

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

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

      普通 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
      +
      if true puts 'Hello, World!' end
      +

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

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

      二つ目のメッセージは無視して一つ目を読むと、then; か改行が来るはずのところ変数だかメソッドだかが現れたことによりエラーとなっているようだ。

      ポイントは改行が then (や ;) の代わりとなることである。true の後に改行を入れてみる。

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

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

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

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

      なぜ then; や改行が必要か

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

      -
      if a b end
      -

      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 等までの間にある、ということを明確にする。 +

      # 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

      @@ -748,33 +748,33 @@ C系の if 後に来る (/) や、Python ;

      ここで、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
      -

      まとめ

      +
      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;、改行のいずれかが必要
          @@ -797,26 +797,26 @@ C系の if 後に来る (/) や、Python 元 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;
          -}
          -
          +
          #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) @@ -842,9 +842,9 @@ $ clang++ –std=c++17 hoge.cpp

          同サイトの [keywords のページ] (https://en.cppreference.com/w/cpp/keyword) から一覧を拝借し、上のコードが出来上がった (C++17 においてキーワードでないものなど、一部省いている)。 大量の警告 (unknown attribute ‘〇〇’ ignored) がコンパイラから出力されるが、コンパイルできる。

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

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

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

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

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

          引用元: https://timsong-cpp.github.io/cppwp/n4659/dcl.attr#grammar-4

          If a keyword or an alternative token that satisfies the syntactic requirements of an identifier is contained in an attribute-token, it is considered an identifier.

          @@ -878,13 +878,13 @@ $ clang++ –std=c++17 hoge.cpp

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

          What values for RUBY_ENGINE correspond to which Ruby implementations? より引用:

          @@ -933,11 +933,11 @@ jruby 1.2.0 (ruby 1.8.6 patchlevel 287) (2009-03-16 rev 9419) [i386-java]

          なお、この質問・回答は 2014年になされたものであり、値は変わっている可能性がある。MRI (aka CRuby) については執筆時現在 (2020/12/8) も 'ruby' が返ってくることを確認済み。

          この表にない主要な処理系として、mruby'mruby' を返す。

          mruby 該当部分のソース より引用:

          -
          /*
          - * Ruby engine.
          - */
          -#define MRUBY_RUBY_ENGINE  "mruby"
          -
          ]]> +
          /*
          + * Ruby engine.
          + */
          +#define MRUBY_RUBY_ENGINE  "mruby"
          +
          ]]> @@ -976,10 +976,10 @@ Huge version without GUI.

          :g/^/m0 のように組み合わせると、「すべての行を1行ずつ 0行目(1行目の上)に動かす」という動きをする。これは確かに行の入れ替えになっている。

          なお、:g/^/m0 は全ての行を入れ替えるが、:N,Mg/^/mN-1 とすることで N行目から M行目を処理範囲とするよう拡張できる。手でこれを入力するわけにはいかないので、次のようなコマンドを用意する。

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

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

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

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

          :g/^/m0 の問題点

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

          @@ -991,14 +991,14 @@ Huge version without GUI.

          より簡潔な方法を見つけたので次節に追記した

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

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

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

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

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

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

          Vim のヘルプから該当箇所を引用する (強調は筆者による)。

          @@ -1022,20 +1022,20 @@ executing autocommands |autocmd-searchpat|.

          [2020/9/28追記] より簡潔な方法を見つけたため追記する

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

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

          +
          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
          -
          ]]> +
          " License: Public Domain
          +
          +command! -bar -range=%
          +    \ Reverse
          +    \ keeppatterns <line1>,<line2>g/^/m<line1>-1
          +
          ]]>
          @@ -1069,26 +1069,26 @@ executing autocommands |autocmd-searchpat|.

          vim のソースコード

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

          https://github.com/vim/vim/blob/8e6be34338f13a6a625f19bcef82019c9adc65f2/src/autocmd.c#L85-L86

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

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

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

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

          -
              {"BufWrite",	EVENT_BUFWRITEPRE},
          -    {"BufWritePost",	EVENT_BUFWRITEPOST},
          -    {"BufWritePre",	EVENT_BUFWRITEPRE},
          -

          neovim のソースコード

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

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

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

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

          +
              {"BufWrite",	EVENT_BUFWRITEPRE},
          +    {"BufWritePost",	EVENT_BUFWRITEPOST},
          +    {"BufWritePre",	EVENT_BUFWRITEPRE},
          +

          neovim のソースコード

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

          https://github.com/neovim/neovim/blob/71d4f5851f068eeb432af34850dddda8cc1c71e3/src/nvim/auevents.lua#L119-L124

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

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

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

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

                                                                     *FileEncoding*
           FileEncoding                    Obsolete.  It still works and is equivalent
                                           to |EncodingChanged|.
          @@ -1131,38 +1131,38 @@ FileEncoding                    Obsolete.  It still works and is equivalent
           

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

          Python でクロージャを作ろうと、次のようなコードを書いた。

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

          関数 g から 関数 f のスコープ内で定義された変数 x を参照し、それに 1 を足そうとしている。 +

          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 を探しに行くようになる。

          +
          # 注: 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/docs/index.html b/docs/index.html index 128abc3..aed015e 100644 --- a/docs/index.html +++ b/docs/index.html @@ -15,7 +15,7 @@ - + diff --git a/docs/page/1/index.html b/docs/page/1/index.html index e0486f7..81da958 100644 --- a/docs/page/1/index.html +++ b/docs/page/1/index.html @@ -1 +1,10 @@ -https://blog.nsfisis.dev/ \ No newline at end of file + + + + https://blog.nsfisis.dev/ + + + + + + diff --git a/docs/page/2/index.html b/docs/page/2/index.html index fcb0be7..6efc6f4 100644 --- a/docs/page/2/index.html +++ b/docs/page/2/index.html @@ -15,7 +15,7 @@ - + diff --git a/docs/posts/2021-03-05/my-first-post/index.html b/docs/posts/2021-03-05/my-first-post/index.html index 65fa969..cd1bbe8 100644 --- a/docs/posts/2021-03-05/my-first-post/index.html +++ b/docs/posts/2021-03-05/my-first-post/index.html @@ -15,7 +15,7 @@ - + diff --git a/docs/posts/2021-03-30/phperkaigi-2021/index.html b/docs/posts/2021-03-30/phperkaigi-2021/index.html index 242e034..a10cc08 100644 --- a/docs/posts/2021-03-30/phperkaigi-2021/index.html +++ b/docs/posts/2021-03-30/phperkaigi-2021/index.html @@ -15,7 +15,7 @@ - + diff --git a/docs/posts/2021-10-02/cpp-you-can-use-keywords-in-attributes/index.html b/docs/posts/2021-10-02/cpp-you-can-use-keywords-in-attributes/index.html index 7261530..38c34aa 100644 --- a/docs/posts/2021-10-02/cpp-you-can-use-keywords-in-attributes/index.html +++ b/docs/posts/2021-10-02/cpp-you-can-use-keywords-in-attributes/index.html @@ -15,7 +15,7 @@ - + @@ -47,26 +47,26 @@ 元 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;
          -}
          -
          +
          #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) @@ -92,9 +92,9 @@ $ clang++ –std=c++17 hoge.cpp

          同サイトの [keywords のページ] (https://en.cppreference.com/w/cpp/keyword) から一覧を拝借し、上のコードが出来上がった (C++17 においてキーワードでないものなど、一部省いている)。 大量の警告 (unknown attribute ‘〇〇’ ignored) がコンパイラから出力されるが、コンパイルできる。

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

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

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

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

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

          引用元: https://timsong-cpp.github.io/cppwp/n4659/dcl.attr#grammar-4

          If a keyword or an alternative token that satisfies the syntactic requirements of an identifier is contained in an attribute-token, it is considered an identifier.

          diff --git a/docs/posts/2021-10-02/python-unbound-local-error/index.html b/docs/posts/2021-10-02/python-unbound-local-error/index.html index bfad1b3..35a880d 100644 --- a/docs/posts/2021-10-02/python-unbound-local-error/index.html +++ b/docs/posts/2021-10-02/python-unbound-local-error/index.html @@ -15,7 +15,7 @@ - + @@ -48,38 +48,38 @@

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

          Python でクロージャを作ろうと、次のようなコードを書いた。

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

          関数 g から 関数 f のスコープ内で定義された変数 x を参照し、それに 1 を足そうとしている。 +

          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 を探しに行くようになる。

          +
          # 注: 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/docs/posts/2021-10-02/ruby-detect-running-implementation/index.html b/docs/posts/2021-10-02/ruby-detect-running-implementation/index.html index 864dc28..71eadba 100644 --- a/docs/posts/2021-10-02/ruby-detect-running-implementation/index.html +++ b/docs/posts/2021-10-02/ruby-detect-running-implementation/index.html @@ -15,7 +15,7 @@ - + @@ -49,13 +49,13 @@

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

          What values for RUBY_ENGINE correspond to which Ruby implementations? より引用:

          @@ -104,11 +104,11 @@ jruby 1.2.0 (ruby 1.8.6 patchlevel 287) (2009-03-16 rev 9419) [i386-java]

          なお、この質問・回答は 2014年になされたものであり、値は変わっている可能性がある。MRI (aka CRuby) については執筆時現在 (2020/12/8) も 'ruby' が返ってくることを確認済み。

          この表にない主要な処理系として、mruby'mruby' を返す。

          mruby 該当部分のソース より引用:

          -
          /*
          - * Ruby engine.
          - */
          -#define MRUBY_RUBY_ENGINE  "mruby"
          -
          +
          /*
          + * Ruby engine.
          + */
          +#define MRUBY_RUBY_ENGINE  "mruby"
          +
          diff --git a/docs/posts/2021-10-02/ruby-then-keyword-and-case-in/index.html b/docs/posts/2021-10-02/ruby-then-keyword-and-case-in/index.html index 2ef9f05..7d62273 100644 --- a/docs/posts/2021-10-02/ruby-then-keyword-and-case-in/index.html +++ b/docs/posts/2021-10-02/ruby-then-keyword-and-case-in/index.html @@ -15,7 +15,7 @@ - + @@ -50,61 +50,61 @@

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

          then とは

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

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

          このキーワードが現れうる場所はいくつかあり、ifunlessrescuecase 構文がそれに当たる。 +

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

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

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

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

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

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

          普通 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
          +
          if true puts 'Hello, World!' end
          +

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

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

          二つ目のメッセージは無視して一つ目を読むと、then; か改行が来るはずのところ変数だかメソッドだかが現れたことによりエラーとなっているようだ。

          ポイントは改行が then (や ;) の代わりとなることである。true の後に改行を入れてみる。

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

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

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

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

          なぜ then; や改行が必要か

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

          -
          if a b end
          -

          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 等までの間にある、ということを明確にする。 +

          # 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

          @@ -142,33 +142,33 @@ C系の if 後に来る (/) や、Python ;

          ここで、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
          -

          まとめ

          +
          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;、改行のいずれかが必要
              diff --git a/docs/posts/2021-10-02/rust-where-are-primitive-types-from/index.html b/docs/posts/2021-10-02/rust-where-are-primitive-types-from/index.html index 5c1fcc5..3aba89f 100644 --- a/docs/posts/2021-10-02/rust-where-are-primitive-types-from/index.html +++ b/docs/posts/2021-10-02/rust-where-are-primitive-types-from/index.html @@ -15,7 +15,7 @@ - + @@ -47,27 +47,27 @@

              前置き

              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 のソースを追ってみた。

              +
              #![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 そのものの知識は不要 (というよりも筆者自身がよく知らない)

              @@ -78,91 +78,91 @@

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

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

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

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

              -
              $ git grep "\bi128\b"
              +
              $ 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 にも、

              +
              /// 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) する。 +

                  /// 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 という名前の別の型が見つかるからだ。

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

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

              まとめ

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

              diff --git a/docs/posts/2021-10-02/vim-difference-between-autocmd-bufwrite-and-bufwritepre/index.html b/docs/posts/2021-10-02/vim-difference-between-autocmd-bufwrite-and-bufwritepre/index.html index 30b03ba..91708ad 100644 --- a/docs/posts/2021-10-02/vim-difference-between-autocmd-bufwrite-and-bufwritepre/index.html +++ b/docs/posts/2021-10-02/vim-difference-between-autocmd-bufwrite-and-bufwritepre/index.html @@ -15,7 +15,7 @@ - + @@ -67,26 +67,26 @@

              vim のソースコード

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

              https://github.com/vim/vim/blob/8e6be34338f13a6a625f19bcef82019c9adc65f2/src/autocmd.c#L85-L86

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

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

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

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

              -
                  {"BufWrite",	EVENT_BUFWRITEPRE},
              -    {"BufWritePost",	EVENT_BUFWRITEPOST},
              -    {"BufWritePre",	EVENT_BUFWRITEPRE},
              -

              neovim のソースコード

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

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

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

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

              +
                  {"BufWrite",	EVENT_BUFWRITEPRE},
              +    {"BufWritePost",	EVENT_BUFWRITEPOST},
              +    {"BufWritePre",	EVENT_BUFWRITEPRE},
              +

              neovim のソースコード

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

              https://github.com/neovim/neovim/blob/71d4f5851f068eeb432af34850dddda8cc1c71e3/src/nvim/auevents.lua#L119-L124

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

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

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

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

                                                                         *FileEncoding*
               FileEncoding                    Obsolete.  It still works and is equivalent
                                               to |EncodingChanged|.
              diff --git a/docs/posts/2021-10-02/vim-swap-order-of-selected-lines/index.html b/docs/posts/2021-10-02/vim-swap-order-of-selected-lines/index.html
              index 4b530b3..eac828f 100644
              --- a/docs/posts/2021-10-02/vim-swap-order-of-selected-lines/index.html
              +++ b/docs/posts/2021-10-02/vim-swap-order-of-selected-lines/index.html
              @@ -15,7 +15,7 @@
                   
                   
                   
              -    
              +    
                   
                   
                 
              @@ -72,10 +72,10 @@ Huge version without GUI.

              :g/^/m0 のように組み合わせると、「すべての行を1行ずつ 0行目(1行目の上)に動かす」という動きをする。これは確かに行の入れ替えになっている。

              なお、:g/^/m0 は全ての行を入れ替えるが、:N,Mg/^/mN-1 とすることで N行目から M行目を処理範囲とするよう拡張できる。手でこれを入力するわけにはいかないので、次のようなコマンドを用意する。

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

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

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

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

              :g/^/m0 の問題点

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

              @@ -87,14 +87,14 @@ Huge version without GUI.

              より簡潔な方法を見つけたので次節に追記した

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

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

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

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

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

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

              Vim のヘルプから該当箇所を引用する (強調は筆者による)。

              @@ -118,20 +118,20 @@ executing autocommands |autocmd-searchpat|.

              [2020/9/28追記] より簡潔な方法を見つけたため追記する

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

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

              +
              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
              -
              +
              " License: Public Domain
              +
              +command! -bar -range=%
              +    \ Reverse
              +    \ keeppatterns <line1>,<line2>g/^/m<line1>-1
              +
              diff --git a/docs/posts/2022-04-09/phperkaigi-2022-tokens/index.html b/docs/posts/2022-04-09/phperkaigi-2022-tokens/index.html index c74be74..4d4918e 100644 --- a/docs/posts/2022-04-09/phperkaigi-2022-tokens/index.html +++ b/docs/posts/2022-04-09/phperkaigi-2022-tokens/index.html @@ -15,7 +15,7 @@ - + @@ -50,76 +50,76 @@

              リポジトリはこちら: 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
              +
              +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 で動かせばトークンが得られる。

              解説

              絵文字

              まず目につくのは大量の絵文字だろう。 @@ -173,18 +173,18 @@ Brainf*ck とは、難解プログラミング言語のひとつであり、こ 今回は、PHP 8.1 から追加された、0O または 0o から始まる八進数リテラルを使った。

              URL

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

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

              完全に合法な PHP のコードである。 +

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

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

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

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

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

              []! を適用すると true が返ってくる。それに + +

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

              []! を適用すると true が返ってくる。それに + を適用すると、bool から int ヘの型変換が走り、1 が生成される。10 はさらにトリッキーだ。まず 10 を作り、. で文字列として結合する ('10')。これに + を適用すると、string から int @@ -204,57 +204,57 @@ 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";
              -}
              -

              さて、この問題はさきほどのように単純に実行しただけでは、謎のブロックが表示されるだけでトークンは得られない。 +

              <?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文字ごとに区切ったあと、改行で結合している。

              +
              $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文字ごとに区切ったあと、改行で結合している。

              ヒント

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

                @@ -268,76 +268,76 @@ $token = [

                解く

                ここまでわかれば、あと一歩で解ける。すなわち、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 が得られる。

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

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

                +
                <?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 (風) のプログラムになっている。 diff --git a/docs/posts/2022-04-24/term-banner-write-tool-showing-banner-in-terminal/index.html b/docs/posts/2022-04-24/term-banner-write-tool-showing-banner-in-terminal/index.html index dc60586..a1e9ca7 100644 --- a/docs/posts/2022-04-24/term-banner-write-tool-showing-banner-in-terminal/index.html +++ b/docs/posts/2022-04-24/term-banner-write-tool-showing-banner-in-terminal/index.html @@ -15,7 +15,7 @@ - + @@ -45,7 +45,7 @@

                はじめに

                こんなものを作った。

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

                term-banner のスクリーンショット

                コマンドライン引数として渡した文字列をターミナルに大きく表示する。

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

                diff --git a/docs/posts/2022-05-01/phperkaigi-2022/index.html b/docs/posts/2022-05-01/phperkaigi-2022/index.html index 0799216..b56c70a 100644 --- a/docs/posts/2022-05-01/phperkaigi-2022/index.html +++ b/docs/posts/2022-05-01/phperkaigi-2022/index.html @@ -15,7 +15,7 @@ - + diff --git a/docs/posts/2022-08-27/php-conference-okinawa-code-golf/index.html b/docs/posts/2022-08-27/php-conference-okinawa-code-golf/index.html index 113d7e0..b9c337e 100644 --- a/docs/posts/2022-08-27/php-conference-okinawa-code-golf/index.html +++ b/docs/posts/2022-08-27/php-conference-okinawa-code-golf/index.html @@ -15,7 +15,7 @@ - + @@ -47,7 +47,7 @@

                はじめに

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

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

                -

                ツイート: https://twitter.com/m3m0r7/status/1563397620231712772
                +

                ツイート: https://twitter.com/m3m0r7/status/1563397620231712772
                スライド: https://speakerdeck.com/memory1994/php-conference-okinawa-2022-extra?slide=3

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

                @@ -60,19 +60,19 @@
              • 負数は入ってこないものとする

              書いたものがこちら:

              -
              [<?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??[]);?>]
              +

              しめて 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 ?? []);
              -
              -?>]
              -

              使用したテクニック

              +
              [<?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つの文に

              diff --git a/docs/posts/cpp-you-can-use-keywords-in-attributes/index.html b/docs/posts/cpp-you-can-use-keywords-in-attributes/index.html index 73a0b03..98a764b 100644 --- a/docs/posts/cpp-you-can-use-keywords-in-attributes/index.html +++ b/docs/posts/cpp-you-can-use-keywords-in-attributes/index.html @@ -1 +1,10 @@ -https://blog.nsfisis.dev/posts/2021-10-02/cpp-you-can-use-keywords-in-attributes/ \ No newline at end of file + + + + https://blog.nsfisis.dev/posts/2021-10-02/cpp-you-can-use-keywords-in-attributes/ + + + + + + diff --git a/docs/posts/feed.xml b/docs/posts/feed.xml index 6f76772..06f7d5b 100644 --- a/docs/posts/feed.xml +++ b/docs/posts/feed.xml @@ -16,7 +16,7 @@ はじめに

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

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

              -

              ツイート: https://twitter.com/m3m0r7/status/1563397620231712772
              +

              ツイート: https://twitter.com/m3m0r7/status/1563397620231712772
              スライド: https://speakerdeck.com/memory1994/php-conference-okinawa-2022-extra?slide=3

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

              @@ -29,19 +29,19 @@
            • 負数は入ってこないものとする

            書いたものがこちら:

            -
            [<?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??[]);?>]
            +

            しめて 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 ?? []);
            -
            -?>]
            -

            使用したテクニック

            +
            [<?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つの文に

            @@ -146,7 +146,7 @@ $n = $argv[1https://blog.nsfisis.dev/posts/2022-04-24/term-banner-write-tool-showing-banner-in-terminal/ はじめに

            こんなものを作った。

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

            term-banner のスクリーンショット

            コマンドライン引数として渡した文字列をターミナルに大きく表示する。

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

            @@ -200,76 +200,76 @@ PNG が標準ライブラリにあったり、Shift-JIS のエンコーディン

            リポジトリはこちら: 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
            +
            +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 で動かせばトークンが得られる。

            解説

            絵文字

            まず目につくのは大量の絵文字だろう。 @@ -323,18 +323,18 @@ Brainf*ck とは、難解プログラミング言語のひとつであり、こ 今回は、PHP 8.1 から追加された、0O または 0o から始まる八進数リテラルを使った。

            URL

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

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

            完全に合法な PHP のコードである。 +

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

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

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

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

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

            []! を適用すると true が返ってくる。それに + +

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

            []! を適用すると true が返ってくる。それに + を適用すると、bool から int ヘの型変換が走り、1 が生成される。10 はさらにトリッキーだ。まず 10 を作り、. で文字列として結合する ('10')。これに + を適用すると、string から int @@ -354,57 +354,57 @@ 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";
            -}
            -

            さて、この問題はさきほどのように単純に実行しただけでは、謎のブロックが表示されるだけでトークンは得られない。 +

            <?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文字ごとに区切ったあと、改行で結合している。

            +
            $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文字ごとに区切ったあと、改行で結合している。

            ヒント

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

              @@ -418,76 +418,76 @@ $token = [

              解く

              ここまでわかれば、あと一歩で解ける。すなわち、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 が得られる。

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

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

              +
              <?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 (風) のプログラムになっている。 @@ -522,27 +522,27 @@ Quine とは、自分のソースコードをそっくりそのまま出力す


              前置き

              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 のソースを追ってみた。

              +
              #![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 そのものの知識は不要 (というよりも筆者自身がよく知らない)

              @@ -553,91 +553,91 @@ Quine とは、自分のソースコードをそっくりそのまま出力す

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

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

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

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

              -
              $ git grep "\bi128\b"
              +
              $ 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 にも、

              +
              /// 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) する。 +

                  /// 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 という名前の別の型が見つかるからだ。

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

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

              まとめ

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

              ]]> @@ -656,61 +656,61 @@ rustc_resolve/src/lib.rs: table.insert(sym::i128, Int(IntTy::I128));

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

              then とは

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

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

              このキーワードが現れうる場所はいくつかあり、ifunlessrescuecase 構文がそれに当たる。 +

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

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

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

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

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

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

              普通 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
              +
              if true puts 'Hello, World!' end
              +

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

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

              二つ目のメッセージは無視して一つ目を読むと、then; か改行が来るはずのところ変数だかメソッドだかが現れたことによりエラーとなっているようだ。

              ポイントは改行が then (や ;) の代わりとなることである。true の後に改行を入れてみる。

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

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

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

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

              なぜ then; や改行が必要か

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

              -
              if a b end
              -

              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 等までの間にある、ということを明確にする。 +

              # 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

              @@ -748,33 +748,33 @@ C系の if 後に来る (/) や、Python ;

              ここで、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
              -

              まとめ

              +
              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;、改行のいずれかが必要
                  @@ -797,26 +797,26 @@ C系の if 後に来る (/) や、Python 元 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;
                  -}
                  -
                  +
                  #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) @@ -842,9 +842,9 @@ $ clang++ –std=c++17 hoge.cpp

                  同サイトの [keywords のページ] (https://en.cppreference.com/w/cpp/keyword) から一覧を拝借し、上のコードが出来上がった (C++17 においてキーワードでないものなど、一部省いている)。 大量の警告 (unknown attribute ‘〇〇’ ignored) がコンパイラから出力されるが、コンパイルできる。

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

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

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

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

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

                  引用元: https://timsong-cpp.github.io/cppwp/n4659/dcl.attr#grammar-4

                  If a keyword or an alternative token that satisfies the syntactic requirements of an identifier is contained in an attribute-token, it is considered an identifier.

                  @@ -878,13 +878,13 @@ $ clang++ –std=c++17 hoge.cpp

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

                  What values for RUBY_ENGINE correspond to which Ruby implementations? より引用:

          @@ -933,11 +933,11 @@ jruby 1.2.0 (ruby 1.8.6 patchlevel 287) (2009-03-16 rev 9419) [i386-java]

          なお、この質問・回答は 2014年になされたものであり、値は変わっている可能性がある。MRI (aka CRuby) については執筆時現在 (2020/12/8) も 'ruby' が返ってくることを確認済み。

          この表にない主要な処理系として、mruby'mruby' を返す。

          mruby 該当部分のソース より引用:

          -
          /*
          - * Ruby engine.
          - */
          -#define MRUBY_RUBY_ENGINE  "mruby"
          -
          ]]> +
          /*
          + * Ruby engine.
          + */
          +#define MRUBY_RUBY_ENGINE  "mruby"
          +
          ]]> @@ -976,10 +976,10 @@ Huge version without GUI.

          :g/^/m0 のように組み合わせると、「すべての行を1行ずつ 0行目(1行目の上)に動かす」という動きをする。これは確かに行の入れ替えになっている。

          なお、:g/^/m0 は全ての行を入れ替えるが、:N,Mg/^/mN-1 とすることで N行目から M行目を処理範囲とするよう拡張できる。手でこれを入力するわけにはいかないので、次のようなコマンドを用意する。

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

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

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

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

          :g/^/m0 の問題点

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

          @@ -991,14 +991,14 @@ Huge version without GUI.

          より簡潔な方法を見つけたので次節に追記した

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

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

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

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

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

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

          Vim のヘルプから該当箇所を引用する (強調は筆者による)。

          @@ -1022,20 +1022,20 @@ executing autocommands |autocmd-searchpat|.

          [2020/9/28追記] より簡潔な方法を見つけたため追記する

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

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

          +
          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
          -
          ]]> +
          " License: Public Domain
          +
          +command! -bar -range=%
          +    \ Reverse
          +    \ keeppatterns <line1>,<line2>g/^/m<line1>-1
          +
          ]]>
          @@ -1069,26 +1069,26 @@ executing autocommands |autocmd-searchpat|.

          vim のソースコード

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

          https://github.com/vim/vim/blob/8e6be34338f13a6a625f19bcef82019c9adc65f2/src/autocmd.c#L85-L86

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

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

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

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

          -
              {"BufWrite",	EVENT_BUFWRITEPRE},
          -    {"BufWritePost",	EVENT_BUFWRITEPOST},
          -    {"BufWritePre",	EVENT_BUFWRITEPRE},
          -

          neovim のソースコード

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

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

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

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

          +
              {"BufWrite",	EVENT_BUFWRITEPRE},
          +    {"BufWritePost",	EVENT_BUFWRITEPOST},
          +    {"BufWritePre",	EVENT_BUFWRITEPRE},
          +

          neovim のソースコード

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

          https://github.com/neovim/neovim/blob/71d4f5851f068eeb432af34850dddda8cc1c71e3/src/nvim/auevents.lua#L119-L124

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

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

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

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

                                                                     *FileEncoding*
           FileEncoding                    Obsolete.  It still works and is equivalent
                                           to |EncodingChanged|.
          @@ -1131,38 +1131,38 @@ FileEncoding                    Obsolete.  It still works and is equivalent
           

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

          Python でクロージャを作ろうと、次のようなコードを書いた。

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

          関数 g から 関数 f のスコープ内で定義された変数 x を参照し、それに 1 を足そうとしている。 +

          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 を探しに行くようになる。

          +
          # 注: 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/docs/posts/index.html b/docs/posts/index.html index e6c9d5d..9cd87b1 100644 --- a/docs/posts/index.html +++ b/docs/posts/index.html @@ -15,7 +15,7 @@ - + diff --git a/docs/posts/my-first-post/index.html b/docs/posts/my-first-post/index.html index 137f715..960644b 100644 --- a/docs/posts/my-first-post/index.html +++ b/docs/posts/my-first-post/index.html @@ -1 +1,10 @@ -https://blog.nsfisis.dev/posts/2021-03-05/my-first-post/ \ No newline at end of file + + + + https://blog.nsfisis.dev/posts/2021-03-05/my-first-post/ + + + + + + diff --git a/docs/posts/page/1/index.html b/docs/posts/page/1/index.html index e866bbc..1439f8c 100644 --- a/docs/posts/page/1/index.html +++ b/docs/posts/page/1/index.html @@ -1 +1,10 @@ -https://blog.nsfisis.dev/posts/ \ No newline at end of file + + + + https://blog.nsfisis.dev/posts/ + + + + + + diff --git a/docs/posts/page/2/index.html b/docs/posts/page/2/index.html index 6f829fc..7448a90 100644 --- a/docs/posts/page/2/index.html +++ b/docs/posts/page/2/index.html @@ -15,7 +15,7 @@ - + diff --git a/docs/posts/phperkaigi-2021/index.html b/docs/posts/phperkaigi-2021/index.html index 7089015..c6f9ff3 100644 --- a/docs/posts/phperkaigi-2021/index.html +++ b/docs/posts/phperkaigi-2021/index.html @@ -1 +1,10 @@ -https://blog.nsfisis.dev/posts/2021-03-30/phperkaigi-2021/ \ No newline at end of file + + + + https://blog.nsfisis.dev/posts/2021-03-30/phperkaigi-2021/ + + + + + + diff --git a/docs/posts/phperkaigi-2022-tokens/index.html b/docs/posts/phperkaigi-2022-tokens/index.html index d57b31a..5b270d8 100644 --- a/docs/posts/phperkaigi-2022-tokens/index.html +++ b/docs/posts/phperkaigi-2022-tokens/index.html @@ -1 +1,10 @@ -https://blog.nsfisis.dev/posts/2022-04-09/phperkaigi-2022-tokens/ \ No newline at end of file + + + + https://blog.nsfisis.dev/posts/2022-04-09/phperkaigi-2022-tokens/ + + + + + + diff --git a/docs/posts/python-unbound-local-error/index.html b/docs/posts/python-unbound-local-error/index.html index 92641cf..fc80756 100644 --- a/docs/posts/python-unbound-local-error/index.html +++ b/docs/posts/python-unbound-local-error/index.html @@ -1 +1,10 @@ -https://blog.nsfisis.dev/posts/2021-10-02/python-unbound-local-error/ \ No newline at end of file + + + + https://blog.nsfisis.dev/posts/2021-10-02/python-unbound-local-error/ + + + + + + diff --git a/docs/posts/ruby-detect-running-implementation/index.html b/docs/posts/ruby-detect-running-implementation/index.html index 1613d13..cfb702f 100644 --- a/docs/posts/ruby-detect-running-implementation/index.html +++ b/docs/posts/ruby-detect-running-implementation/index.html @@ -1 +1,10 @@ -https://blog.nsfisis.dev/posts/2021-10-02/ruby-detect-running-implementation/ \ No newline at end of file + + + + https://blog.nsfisis.dev/posts/2021-10-02/ruby-detect-running-implementation/ + + + + + + diff --git a/docs/posts/ruby-then-keyword-and-case-in/index.html b/docs/posts/ruby-then-keyword-and-case-in/index.html index 0136304..0b84d2a 100644 --- a/docs/posts/ruby-then-keyword-and-case-in/index.html +++ b/docs/posts/ruby-then-keyword-and-case-in/index.html @@ -1 +1,10 @@ -https://blog.nsfisis.dev/posts/2021-10-02/ruby-then-keyword-and-case-in/ \ No newline at end of file + + + + https://blog.nsfisis.dev/posts/2021-10-02/ruby-then-keyword-and-case-in/ + + + + + + diff --git a/docs/posts/rust-where-are-primitive-types-from/index.html b/docs/posts/rust-where-are-primitive-types-from/index.html index b500d92..56c823a 100644 --- a/docs/posts/rust-where-are-primitive-types-from/index.html +++ b/docs/posts/rust-where-are-primitive-types-from/index.html @@ -1 +1,10 @@ -https://blog.nsfisis.dev/posts/2021-10-02/rust-where-are-primitive-types-from/ \ No newline at end of file + + + + https://blog.nsfisis.dev/posts/2021-10-02/rust-where-are-primitive-types-from/ + + + + + + diff --git a/docs/posts/vim-difference-between-autocmd-bufwrite-and-bufwritepre/index.html b/docs/posts/vim-difference-between-autocmd-bufwrite-and-bufwritepre/index.html index 48cc850..3ea6c6b 100644 --- a/docs/posts/vim-difference-between-autocmd-bufwrite-and-bufwritepre/index.html +++ b/docs/posts/vim-difference-between-autocmd-bufwrite-and-bufwritepre/index.html @@ -1 +1,10 @@ -https://blog.nsfisis.dev/posts/2021-10-02/vim-difference-between-autocmd-bufwrite-and-bufwritepre/ \ No newline at end of file + + + + https://blog.nsfisis.dev/posts/2021-10-02/vim-difference-between-autocmd-bufwrite-and-bufwritepre/ + + + + + + diff --git a/docs/posts/vim-swap-order-of-selected-lines/index.html b/docs/posts/vim-swap-order-of-selected-lines/index.html index 416fd38..f985a9a 100644 --- a/docs/posts/vim-swap-order-of-selected-lines/index.html +++ b/docs/posts/vim-swap-order-of-selected-lines/index.html @@ -1 +1,10 @@ -https://blog.nsfisis.dev/posts/2021-10-02/vim-swap-order-of-selected-lines/ \ No newline at end of file + + + + https://blog.nsfisis.dev/posts/2021-10-02/vim-swap-order-of-selected-lines/ + + + + + + diff --git a/docs/style.css b/docs/style.css index 3430aaf..16d5710 100644 --- a/docs/style.css +++ b/docs/style.css @@ -80,7 +80,7 @@ p { } strong, b { - font-weight: 500; + font-weight: bold; } ul { margin: 0; diff --git a/docs/tags/conference/feed.xml b/docs/tags/conference/feed.xml index 5ba7503..f536cbb 100644 --- a/docs/tags/conference/feed.xml +++ b/docs/tags/conference/feed.xml @@ -16,7 +16,7 @@ はじめに

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

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

          -

          ツイート: https://twitter.com/m3m0r7/status/1563397620231712772
          +

          ツイート: https://twitter.com/m3m0r7/status/1563397620231712772
          スライド: https://speakerdeck.com/memory1994/php-conference-okinawa-2022-extra?slide=3

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

          @@ -29,19 +29,19 @@
        • 負数は入ってこないものとする
        • 書いたものがこちら:

          -
          [<?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??[]);?>]
          +

          しめて 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 ?? []);
          -
          -?>]
          -

          使用したテクニック

          +
          [<?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つの文に

          @@ -149,76 +149,76 @@ $n = $argv[1リポジトリはこちら: 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
          +
          +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 で動かせばトークンが得られる。

          解説

          絵文字

          まず目につくのは大量の絵文字だろう。 @@ -272,18 +272,18 @@ Brainf*ck とは、難解プログラミング言語のひとつであり、こ 今回は、PHP 8.1 から追加された、0O または 0o から始まる八進数リテラルを使った。

          URL

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

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

          完全に合法な PHP のコードである。 +

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

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

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

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

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

          []! を適用すると true が返ってくる。それに + +

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

          []! を適用すると true が返ってくる。それに + を適用すると、bool から int ヘの型変換が走り、1 が生成される。10 はさらにトリッキーだ。まず 10 を作り、. で文字列として結合する ('10')。これに + を適用すると、string から int @@ -303,57 +303,57 @@ 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";
          -}
          -

          さて、この問題はさきほどのように単純に実行しただけでは、謎のブロックが表示されるだけでトークンは得られない。 +

          <?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文字ごとに区切ったあと、改行で結合している。

          +
          $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文字ごとに区切ったあと、改行で結合している。

          ヒント

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

            @@ -367,76 +367,76 @@ $token = [

            解く

            ここまでわかれば、あと一歩で解ける。すなわち、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 が得られる。

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

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

            +
            <?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 (風) のプログラムになっている。 diff --git a/docs/tags/conference/index.html b/docs/tags/conference/index.html index 1ff69f1..40c8675 100644 --- a/docs/tags/conference/index.html +++ b/docs/tags/conference/index.html @@ -15,7 +15,7 @@ - + diff --git a/docs/tags/conference/page/1/index.html b/docs/tags/conference/page/1/index.html index 0a923d6..740f29e 100644 --- a/docs/tags/conference/page/1/index.html +++ b/docs/tags/conference/page/1/index.html @@ -1 +1,10 @@ -https://blog.nsfisis.dev/tags/conference/ \ No newline at end of file + + + + https://blog.nsfisis.dev/tags/conference/ + + + + + + diff --git a/docs/tags/cpp/feed.xml b/docs/tags/cpp/feed.xml index cac96cd..435e2f9 100644 --- a/docs/tags/cpp/feed.xml +++ b/docs/tags/cpp/feed.xml @@ -17,26 +17,26 @@ 元 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;
            -}
            -
            +
            #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) @@ -62,9 +62,9 @@ $ clang++ –std=c++17 hoge.cpp

            同サイトの [keywords のページ] (https://en.cppreference.com/w/cpp/keyword) から一覧を拝借し、上のコードが出来上がった (C++17 においてキーワードでないものなど、一部省いている)。 大量の警告 (unknown attribute ‘〇〇’ ignored) がコンパイラから出力されるが、コンパイルできる。

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

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

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

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

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

            引用元: https://timsong-cpp.github.io/cppwp/n4659/dcl.attr#grammar-4

            If a keyword or an alternative token that satisfies the syntactic requirements of an identifier is contained in an attribute-token, it is considered an identifier.

            diff --git a/docs/tags/cpp/index.html b/docs/tags/cpp/index.html index 40b8fd8..73b29b1 100644 --- a/docs/tags/cpp/index.html +++ b/docs/tags/cpp/index.html @@ -15,7 +15,7 @@ - + diff --git a/docs/tags/cpp/page/1/index.html b/docs/tags/cpp/page/1/index.html index c7ce5f2..ee45690 100644 --- a/docs/tags/cpp/page/1/index.html +++ b/docs/tags/cpp/page/1/index.html @@ -1 +1,10 @@ -https://blog.nsfisis.dev/tags/cpp/ \ No newline at end of file + + + + https://blog.nsfisis.dev/tags/cpp/ + + + + + + diff --git a/docs/tags/cpp17/feed.xml b/docs/tags/cpp17/feed.xml index b1e9aa9..b1be398 100644 --- a/docs/tags/cpp17/feed.xml +++ b/docs/tags/cpp17/feed.xml @@ -17,26 +17,26 @@ 元 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;
            -}
            -
            +
            #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) @@ -62,9 +62,9 @@ $ clang++ –std=c++17 hoge.cpp

            同サイトの [keywords のページ] (https://en.cppreference.com/w/cpp/keyword) から一覧を拝借し、上のコードが出来上がった (C++17 においてキーワードでないものなど、一部省いている)。 大量の警告 (unknown attribute ‘〇〇’ ignored) がコンパイラから出力されるが、コンパイルできる。

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

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

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

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

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

            引用元: https://timsong-cpp.github.io/cppwp/n4659/dcl.attr#grammar-4

            If a keyword or an alternative token that satisfies the syntactic requirements of an identifier is contained in an attribute-token, it is considered an identifier.

            diff --git a/docs/tags/cpp17/index.html b/docs/tags/cpp17/index.html index 0b728b1..3dd9d68 100644 --- a/docs/tags/cpp17/index.html +++ b/docs/tags/cpp17/index.html @@ -15,7 +15,7 @@ - + diff --git a/docs/tags/cpp17/page/1/index.html b/docs/tags/cpp17/page/1/index.html index fbe22d9..74e12d4 100644 --- a/docs/tags/cpp17/page/1/index.html +++ b/docs/tags/cpp17/page/1/index.html @@ -1 +1,10 @@ -https://blog.nsfisis.dev/tags/cpp17/ \ No newline at end of file + + + + https://blog.nsfisis.dev/tags/cpp17/ + + + + + + diff --git a/docs/tags/index.html b/docs/tags/index.html index 8978816..79ef62b 100644 --- a/docs/tags/index.html +++ b/docs/tags/index.html @@ -15,7 +15,7 @@ - + diff --git a/docs/tags/my-programs/feed.xml b/docs/tags/my-programs/feed.xml index 4d4ca44..34cd4bb 100644 --- a/docs/tags/my-programs/feed.xml +++ b/docs/tags/my-programs/feed.xml @@ -15,7 +15,7 @@ https://blog.nsfisis.dev/posts/2022-04-24/term-banner-write-tool-showing-banner-in-terminal/ はじめに

            こんなものを作った。

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

            term-banner のスクリーンショット

            コマンドライン引数として渡した文字列をターミナルに大きく表示する。

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

            diff --git a/docs/tags/my-programs/index.html b/docs/tags/my-programs/index.html index 22420a7..bc71599 100644 --- a/docs/tags/my-programs/index.html +++ b/docs/tags/my-programs/index.html @@ -15,7 +15,7 @@ - + diff --git a/docs/tags/my-programs/page/1/index.html b/docs/tags/my-programs/page/1/index.html index 7aedb41..36f7d78 100644 --- a/docs/tags/my-programs/page/1/index.html +++ b/docs/tags/my-programs/page/1/index.html @@ -1 +1,10 @@ -https://blog.nsfisis.dev/tags/my-programs/ \ No newline at end of file + + + + https://blog.nsfisis.dev/tags/my-programs/ + + + + + + diff --git a/docs/tags/page/1/index.html b/docs/tags/page/1/index.html index 71c6610..2dbd4ae 100644 --- a/docs/tags/page/1/index.html +++ b/docs/tags/page/1/index.html @@ -1 +1,10 @@ -https://blog.nsfisis.dev/tags/ \ No newline at end of file + + + + https://blog.nsfisis.dev/tags/ + + + + + + diff --git a/docs/tags/php/feed.xml b/docs/tags/php/feed.xml index 5b39f4f..248d3ec 100644 --- a/docs/tags/php/feed.xml +++ b/docs/tags/php/feed.xml @@ -16,7 +16,7 @@ はじめに

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

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

            -

            ツイート: https://twitter.com/m3m0r7/status/1563397620231712772
            +

            ツイート: https://twitter.com/m3m0r7/status/1563397620231712772
            スライド: https://speakerdeck.com/memory1994/php-conference-okinawa-2022-extra?slide=3

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

            @@ -29,19 +29,19 @@
          • 負数は入ってこないものとする

          書いたものがこちら:

          -
          [<?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??[]);?>]
          +

          しめて 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 ?? []);
          -
          -?>]
          -

          使用したテクニック

          +
          [<?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つの文に

          @@ -149,76 +149,76 @@ $n = $argv[1リポジトリはこちら: 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
          +
          +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 で動かせばトークンが得られる。

          解説

          絵文字

          まず目につくのは大量の絵文字だろう。 @@ -272,18 +272,18 @@ Brainf*ck とは、難解プログラミング言語のひとつであり、こ 今回は、PHP 8.1 から追加された、0O または 0o から始まる八進数リテラルを使った。

          URL

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

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

          完全に合法な PHP のコードである。 +

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

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

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

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

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

          []! を適用すると true が返ってくる。それに + +

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

          []! を適用すると true が返ってくる。それに + を適用すると、bool から int ヘの型変換が走り、1 が生成される。10 はさらにトリッキーだ。まず 10 を作り、. で文字列として結合する ('10')。これに + を適用すると、string から int @@ -303,57 +303,57 @@ 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";
          -}
          -

          さて、この問題はさきほどのように単純に実行しただけでは、謎のブロックが表示されるだけでトークンは得られない。 +

          <?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文字ごとに区切ったあと、改行で結合している。

          +
          $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文字ごとに区切ったあと、改行で結合している。

          ヒント

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

            @@ -367,76 +367,76 @@ $token = [

            解く

            ここまでわかれば、あと一歩で解ける。すなわち、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 が得られる。

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

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

            +
            <?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 (風) のプログラムになっている。 diff --git a/docs/tags/php/index.html b/docs/tags/php/index.html index c949caf..bb2338f 100644 --- a/docs/tags/php/index.html +++ b/docs/tags/php/index.html @@ -15,7 +15,7 @@ - + diff --git a/docs/tags/php/page/1/index.html b/docs/tags/php/page/1/index.html index abae162..9d866e9 100644 --- a/docs/tags/php/page/1/index.html +++ b/docs/tags/php/page/1/index.html @@ -1 +1,10 @@ -https://blog.nsfisis.dev/tags/php/ \ No newline at end of file + + + + https://blog.nsfisis.dev/tags/php/ + + + + + + diff --git a/docs/tags/phpcon/feed.xml b/docs/tags/phpcon/feed.xml index 69ebe09..b5b8325 100644 --- a/docs/tags/phpcon/feed.xml +++ b/docs/tags/phpcon/feed.xml @@ -16,7 +16,7 @@ はじめに

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

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

            -

            ツイート: https://twitter.com/m3m0r7/status/1563397620231712772
            +

            ツイート: https://twitter.com/m3m0r7/status/1563397620231712772
            スライド: https://speakerdeck.com/memory1994/php-conference-okinawa-2022-extra?slide=3

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

            @@ -29,19 +29,19 @@
          • 負数は入ってこないものとする

          書いたものがこちら:

          -
          [<?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??[]);?>]
          +

          しめて 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 ?? []);
          -
          -?>]
          -

          使用したテクニック

          +
          [<?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つの文に

          diff --git a/docs/tags/phpcon/index.html b/docs/tags/phpcon/index.html index 637e229..d3d3cba 100644 --- a/docs/tags/phpcon/index.html +++ b/docs/tags/phpcon/index.html @@ -15,7 +15,7 @@ - + diff --git a/docs/tags/phpcon/page/1/index.html b/docs/tags/phpcon/page/1/index.html index 05742cd..736f0cb 100644 --- a/docs/tags/phpcon/page/1/index.html +++ b/docs/tags/phpcon/page/1/index.html @@ -1 +1,10 @@ -https://blog.nsfisis.dev/tags/phpcon/ \ No newline at end of file + + + + https://blog.nsfisis.dev/tags/phpcon/ + + + + + + diff --git a/docs/tags/phperkaigi/feed.xml b/docs/tags/phperkaigi/feed.xml index a750f1e..9c19c75 100644 --- a/docs/tags/phperkaigi/feed.xml +++ b/docs/tags/phperkaigi/feed.xml @@ -99,76 +99,76 @@

          リポジトリはこちら: 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
          +
          +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 で動かせばトークンが得られる。

          解説

          絵文字

          まず目につくのは大量の絵文字だろう。 @@ -222,18 +222,18 @@ Brainf*ck とは、難解プログラミング言語のひとつであり、こ 今回は、PHP 8.1 から追加された、0O または 0o から始まる八進数リテラルを使った。

          URL

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

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

          完全に合法な PHP のコードである。 +

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

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

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

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

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

          []! を適用すると true が返ってくる。それに + +

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

          []! を適用すると true が返ってくる。それに + を適用すると、bool から int ヘの型変換が走り、1 が生成される。10 はさらにトリッキーだ。まず 10 を作り、. で文字列として結合する ('10')。これに + を適用すると、string から int @@ -253,57 +253,57 @@ 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";
          -}
          -

          さて、この問題はさきほどのように単純に実行しただけでは、謎のブロックが表示されるだけでトークンは得られない。 +

          <?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文字ごとに区切ったあと、改行で結合している。

          +
          $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文字ごとに区切ったあと、改行で結合している。

          ヒント

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

            @@ -317,76 +317,76 @@ $token = [

            解く

            ここまでわかれば、あと一歩で解ける。すなわち、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 が得られる。

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

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

            +
            <?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 (風) のプログラムになっている。 diff --git a/docs/tags/phperkaigi/index.html b/docs/tags/phperkaigi/index.html index 7da5581..ae48d05 100644 --- a/docs/tags/phperkaigi/index.html +++ b/docs/tags/phperkaigi/index.html @@ -15,7 +15,7 @@ - + diff --git a/docs/tags/phperkaigi/page/1/index.html b/docs/tags/phperkaigi/page/1/index.html index ecafc09..38c704d 100644 --- a/docs/tags/phperkaigi/page/1/index.html +++ b/docs/tags/phperkaigi/page/1/index.html @@ -1 +1,10 @@ -https://blog.nsfisis.dev/tags/phperkaigi/ \ No newline at end of file + + + + https://blog.nsfisis.dev/tags/phperkaigi/ + + + + + + diff --git a/docs/tags/python/feed.xml b/docs/tags/python/feed.xml index 68dfe16..3b8ba29 100644 --- a/docs/tags/python/feed.xml +++ b/docs/tags/python/feed.xml @@ -18,38 +18,38 @@


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

            Python でクロージャを作ろうと、次のようなコードを書いた。

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

            関数 g から 関数 f のスコープ内で定義された変数 x を参照し、それに 1 を足そうとしている。 +

            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 を探しに行くようになる。

            +
            # 注: 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/docs/tags/python/index.html b/docs/tags/python/index.html index 2551526..884872e 100644 --- a/docs/tags/python/index.html +++ b/docs/tags/python/index.html @@ -15,7 +15,7 @@ - + diff --git a/docs/tags/python/page/1/index.html b/docs/tags/python/page/1/index.html index 9560b32..ca045d7 100644 --- a/docs/tags/python/page/1/index.html +++ b/docs/tags/python/page/1/index.html @@ -1 +1,10 @@ -https://blog.nsfisis.dev/tags/python/ \ No newline at end of file + + + + https://blog.nsfisis.dev/tags/python/ + + + + + + diff --git a/docs/tags/python3/feed.xml b/docs/tags/python3/feed.xml index 114063d..260f94a 100644 --- a/docs/tags/python3/feed.xml +++ b/docs/tags/python3/feed.xml @@ -18,38 +18,38 @@

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

            Python でクロージャを作ろうと、次のようなコードを書いた。

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

            関数 g から 関数 f のスコープ内で定義された変数 x を参照し、それに 1 を足そうとしている。 +

            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 を探しに行くようになる。

            +
            # 注: 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/docs/tags/python3/index.html b/docs/tags/python3/index.html index a59912a..5e18ffd 100644 --- a/docs/tags/python3/index.html +++ b/docs/tags/python3/index.html @@ -15,7 +15,7 @@ - + diff --git a/docs/tags/python3/page/1/index.html b/docs/tags/python3/page/1/index.html index 2e5623f..1c8bbe3 100644 --- a/docs/tags/python3/page/1/index.html +++ b/docs/tags/python3/page/1/index.html @@ -1 +1,10 @@ -https://blog.nsfisis.dev/tags/python3/ \ No newline at end of file + + + + https://blog.nsfisis.dev/tags/python3/ + + + + + + diff --git a/docs/tags/ruby/feed.xml b/docs/tags/ruby/feed.xml index a2923c1..50612e5 100644 --- a/docs/tags/ruby/feed.xml +++ b/docs/tags/ruby/feed.xml @@ -20,61 +20,61 @@

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

            then とは

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

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

            このキーワードが現れうる場所はいくつかあり、ifunlessrescuecase 構文がそれに当たる。 +

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

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

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

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

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

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

            普通 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
            +
            if true puts 'Hello, World!' end
            +

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

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

            二つ目のメッセージは無視して一つ目を読むと、then; か改行が来るはずのところ変数だかメソッドだかが現れたことによりエラーとなっているようだ。

            ポイントは改行が then (や ;) の代わりとなることである。true の後に改行を入れてみる。

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

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

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

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

            なぜ then; や改行が必要か

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

            -
            if a b end
            -

            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 等までの間にある、ということを明確にする。 +

            # 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

            @@ -112,33 +112,33 @@ C系の if 後に来る (/) や、Python ;

            ここで、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
            -

            まとめ

            +
            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;、改行のいずれかが必要
                @@ -164,13 +164,13 @@ C系の if 後に来る (/) や、Python

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

                What values for RUBY_ENGINE correspond to which Ruby implementations? より引用:

          @@ -219,11 +219,11 @@ jruby 1.2.0 (ruby 1.8.6 patchlevel 287) (2009-03-16 rev 9419) [i386-java]

          なお、この質問・回答は 2014年になされたものであり、値は変わっている可能性がある。MRI (aka CRuby) については執筆時現在 (2020/12/8) も 'ruby' が返ってくることを確認済み。

          この表にない主要な処理系として、mruby'mruby' を返す。

          mruby 該当部分のソース より引用:

          -
          /*
          - * Ruby engine.
          - */
          -#define MRUBY_RUBY_ENGINE  "mruby"
          -
          ]]> +
          /*
          + * Ruby engine.
          + */
          +#define MRUBY_RUBY_ENGINE  "mruby"
          +
          ]]> diff --git a/docs/tags/ruby/index.html b/docs/tags/ruby/index.html index c20b848..a23cbbf 100644 --- a/docs/tags/ruby/index.html +++ b/docs/tags/ruby/index.html @@ -15,7 +15,7 @@ - + diff --git a/docs/tags/ruby/page/1/index.html b/docs/tags/ruby/page/1/index.html index 62d3407..9374bc8 100644 --- a/docs/tags/ruby/page/1/index.html +++ b/docs/tags/ruby/page/1/index.html @@ -1 +1,10 @@ -https://blog.nsfisis.dev/tags/ruby/ \ No newline at end of file + + + + https://blog.nsfisis.dev/tags/ruby/ + + + + + + diff --git a/docs/tags/ruby3/feed.xml b/docs/tags/ruby3/feed.xml index 066258a..530cc62 100644 --- a/docs/tags/ruby3/feed.xml +++ b/docs/tags/ruby3/feed.xml @@ -20,61 +20,61 @@

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

          then とは

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

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

          このキーワードが現れうる場所はいくつかあり、ifunlessrescuecase 構文がそれに当たる。 +

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

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

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

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

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

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

          普通 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
          +
          if true puts 'Hello, World!' end
          +

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

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

          二つ目のメッセージは無視して一つ目を読むと、then; か改行が来るはずのところ変数だかメソッドだかが現れたことによりエラーとなっているようだ。

          ポイントは改行が then (や ;) の代わりとなることである。true の後に改行を入れてみる。

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

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

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

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

          なぜ then; や改行が必要か

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

          -
          if a b end
          -

          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 等までの間にある、ということを明確にする。 +

          # 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

          @@ -112,33 +112,33 @@ C系の if 後に来る (/) や、Python ;

          ここで、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
          -

          まとめ

          +
          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;、改行のいずれかが必要
              diff --git a/docs/tags/ruby3/index.html b/docs/tags/ruby3/index.html index 9f2b111..b204a64 100644 --- a/docs/tags/ruby3/index.html +++ b/docs/tags/ruby3/index.html @@ -15,7 +15,7 @@ - + diff --git a/docs/tags/ruby3/page/1/index.html b/docs/tags/ruby3/page/1/index.html index 5db85b3..15caf8a 100644 --- a/docs/tags/ruby3/page/1/index.html +++ b/docs/tags/ruby3/page/1/index.html @@ -1 +1,10 @@ -https://blog.nsfisis.dev/tags/ruby3/ \ No newline at end of file + + + + https://blog.nsfisis.dev/tags/ruby3/ + + + + + + diff --git a/docs/tags/rust/feed.xml b/docs/tags/rust/feed.xml index 6b5c112..60f98d3 100644 --- a/docs/tags/rust/feed.xml +++ b/docs/tags/rust/feed.xml @@ -18,27 +18,27 @@

              前置き

              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 のソースを追ってみた。

              +
              #![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 そのものの知識は不要 (というよりも筆者自身がよく知らない)

              @@ -49,91 +49,91 @@

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

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

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

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

              -
              $ git grep "\bi128\b"
              +
              $ 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 にも、

              +
              /// 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) する。 +

                  /// 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 という名前の別の型が見つかるからだ。

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

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

              まとめ

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

              ]]> diff --git a/docs/tags/rust/index.html b/docs/tags/rust/index.html index 2381508..7b89cc4 100644 --- a/docs/tags/rust/index.html +++ b/docs/tags/rust/index.html @@ -15,7 +15,7 @@ - + diff --git a/docs/tags/rust/page/1/index.html b/docs/tags/rust/page/1/index.html index d041e49..5cf7d43 100644 --- a/docs/tags/rust/page/1/index.html +++ b/docs/tags/rust/page/1/index.html @@ -1 +1,10 @@ -https://blog.nsfisis.dev/tags/rust/ \ No newline at end of file + + + + https://blog.nsfisis.dev/tags/rust/ + + + + + + diff --git a/docs/tags/vim/feed.xml b/docs/tags/vim/feed.xml index 42c6110..73bb677 100644 --- a/docs/tags/vim/feed.xml +++ b/docs/tags/vim/feed.xml @@ -43,10 +43,10 @@ Huge version without GUI.

              :g/^/m0 のように組み合わせると、「すべての行を1行ずつ 0行目(1行目の上)に動かす」という動きをする。これは確かに行の入れ替えになっている。

              なお、:g/^/m0 は全ての行を入れ替えるが、:N,Mg/^/mN-1 とすることで N行目から M行目を処理範囲とするよう拡張できる。手でこれを入力するわけにはいかないので、次のようなコマンドを用意する。

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

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

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

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

              :g/^/m0 の問題点

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

              @@ -58,14 +58,14 @@ Huge version without GUI.

              より簡潔な方法を見つけたので次節に追記した

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

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

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

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

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

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

              Vim のヘルプから該当箇所を引用する (強調は筆者による)。

              @@ -89,20 +89,20 @@ executing autocommands |autocmd-searchpat|.

              [2020/9/28追記] より簡潔な方法を見つけたため追記する

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

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

              +
              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
              -
              ]]> +
              " License: Public Domain
              +
              +command! -bar -range=%
              +    \ Reverse
              +    \ keeppatterns <line1>,<line2>g/^/m<line1>-1
              +
              ]]> @@ -136,26 +136,26 @@ executing autocommands |autocmd-searchpat|.

              vim のソースコード

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

              https://github.com/vim/vim/blob/8e6be34338f13a6a625f19bcef82019c9adc65f2/src/autocmd.c#L85-L86

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

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

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

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

              -
                  {"BufWrite",	EVENT_BUFWRITEPRE},
              -    {"BufWritePost",	EVENT_BUFWRITEPOST},
              -    {"BufWritePre",	EVENT_BUFWRITEPRE},
              -

              neovim のソースコード

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

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

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

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

              +
                  {"BufWrite",	EVENT_BUFWRITEPRE},
              +    {"BufWritePost",	EVENT_BUFWRITEPOST},
              +    {"BufWritePre",	EVENT_BUFWRITEPRE},
              +

              neovim のソースコード

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

              https://github.com/neovim/neovim/blob/71d4f5851f068eeb432af34850dddda8cc1c71e3/src/nvim/auevents.lua#L119-L124

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

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

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

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

                                                                         *FileEncoding*
               FileEncoding                    Obsolete.  It still works and is equivalent
                                               to |EncodingChanged|.
              diff --git a/docs/tags/vim/index.html b/docs/tags/vim/index.html
              index dfdca95..5964ab8 100644
              --- a/docs/tags/vim/index.html
              +++ b/docs/tags/vim/index.html
              @@ -15,7 +15,7 @@
                   
                   
                   
              -    
              +    
                   
                   
                   
              diff --git a/docs/tags/vim/page/1/index.html b/docs/tags/vim/page/1/index.html
              index 3ac6e63..52be41d 100644
              --- a/docs/tags/vim/page/1/index.html
              +++ b/docs/tags/vim/page/1/index.html
              @@ -1 +1,10 @@
              -https://blog.nsfisis.dev/tags/vim/
              \ No newline at end of file
              +
              +
              +  
              +    https://blog.nsfisis.dev/tags/vim/
              +    
              +    
              +    
              +    
              +  
              +
              -- 
              cgit v1.2.3-70-g09d2