From 6209453817da9922f28bac1bb1522c6d380630ab Mon Sep 17 00:00:00 2001 From: nsfisis Date: Sat, 19 Nov 2022 14:23:32 +0900 Subject: Hugo to Asciidoctor --- .../index.html | 129 -------------- .../python-unbound-local-error/index.html | 97 ---------- .../ruby-detect-running-implementation/index.html | 125 ------------- .../ruby-then-keyword-and-case-in/index.html | 195 --------------------- .../rust-where-are-primitive-types-from/index.html | 182 ------------------- .../index.html | 132 -------------- .../vim-swap-order-of-selected-lines/index.html | 148 ---------------- 7 files changed, 1008 deletions(-) delete mode 100644 docs/posts/2021-10-02/cpp-you-can-use-keywords-in-attributes/index.html delete mode 100644 docs/posts/2021-10-02/python-unbound-local-error/index.html delete mode 100644 docs/posts/2021-10-02/ruby-detect-running-implementation/index.html delete mode 100644 docs/posts/2021-10-02/ruby-then-keyword-and-case-in/index.html delete mode 100644 docs/posts/2021-10-02/rust-where-are-primitive-types-from/index.html delete mode 100644 docs/posts/2021-10-02/vim-difference-between-autocmd-bufwrite-and-bufwritepre/index.html delete mode 100644 docs/posts/2021-10-02/vim-swap-order-of-selected-lines/index.html (limited to 'docs/posts/2021-10-02') 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 deleted file mode 100644 index 38c34aa..0000000 --- a/docs/posts/2021-10-02/cpp-you-can-use-keywords-in-attributes/index.html +++ /dev/null @@ -1,129 +0,0 @@ - - - - - - - - [C++] 属性構文の属性名にはキーワードが使える [[void]] [[for]] | REPL: Rest-Eat-Program Loop - - - - - - - - - - - - - - -
- -
-
- - -
-
-

[C++] 属性構文の属性名にはキーワードが使える [[void]] [[for]]

- -
-
-
-

更新履歴

-
    -
  • 2021-10-02: Qiita から移植
  • -
-
-

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

-
-

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

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

コンパイラのバージョン -$ clang++ –version -Apple clang version 11.0.0 (clang-1100.0.33.8) -Target: x86_64-apple-darwin19.6.0 -Thread model: posix -InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin

-

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

-
-

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

-

別件で cppreference.com の identifier のページ を読んでいた時、次の文が目に止まった。

-
-
    -
  • the identifiers that are keywords cannot be used for other purposes; -
      -
    • The only place they can be used as non-keywords is in an attribute-token. (e.g. [[private]] is a valid attribute) (since C++11)
    • -
    -
  • -
-
-

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

-

同サイトの [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 の仕様も見てみる (正確には標準化前のドラフト)。

-

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

-
-

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

-
-

identifier の構文上の要件を満たすキーワードまたは代替トークンが attribute-token に含まれている場合、identifier とみなされる」とある。どうやら間違いないようだ。

-

ところで、代替トークン (alternative token) とは and (&) や bitor (|) などのことだが、identifier の構文上の要件を満たさないような代替トークンなどあるのか? -疑問に思って調べたところ、代替トークンという語にはダイグラフも含まれるらしい (参考: 同ドラフト)

-
    -
  • <%{
  • -
  • %>}
  • -
  • <:[
  • -
  • :>]
  • -
  • %:#
  • -
  • %:%:##
  • -
-

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 deleted file mode 100644 index 35a880d..0000000 --- a/docs/posts/2021-10-02/python-unbound-local-error/index.html +++ /dev/null @@ -1,97 +0,0 @@ - - - - - - - - [Python] クロージャとUnboundLocalError: local variable 'x' referenced before assignment | REPL: Rest-Eat-Program Loop - - - - - - - - - - - - - - -
- -
-
- - -
-
-

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

- -
-
-
-

更新履歴

-
    -
  • 2021-10-02: Qiita から移植
  • -
-
-

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

-
-

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

-

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

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

関数 g から 関数 f のスコープ内で定義された変数 x を参照し、それに 1 を足そうとしている。 -これを実行すると x += 1 の箇所でエラーが発生する。

-
-

UnboundLocalError: local variable ‘x’ referenced before assignment

-
-

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

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

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

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

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

- -
-
- - - - - - diff --git a/docs/posts/2021-10-02/ruby-detect-running-implementation/index.html b/docs/posts/2021-10-02/ruby-detect-running-implementation/index.html deleted file mode 100644 index 71eadba..0000000 --- a/docs/posts/2021-10-02/ruby-detect-running-implementation/index.html +++ /dev/null @@ -1,125 +0,0 @@ - - - - - - - - [Ruby] 自身を実行している処理系の種類を判定する | REPL: Rest-Eat-Program Loop - - - - - - - - - - - - - - -
- -
-
- - -
-
-

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

- -
-
-
-

更新履歴

-
    -
  • 2021-10-02: Qiita から移植
  • -
-
-

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

-
-

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

-

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

-

参考: Object::RUBY_ENGINE

-

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

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

それぞれの処理系がどのような値を返すかだが、stack overflow に良い質問と回答があった。

-

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

-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
RUBY_ENGINEImplementation
<undefined>MRI < 1.9
‘ruby’MRI >= 1.9 or REE
‘jruby’JRuby
‘macruby’MacRuby
‘rbx’Rubinius
‘maglev’MagLev
‘ironruby’IronRuby
‘cardinal’Cardinal
-
-

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

-

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

-

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 deleted file mode 100644 index 7d62273..0000000 --- a/docs/posts/2021-10-02/ruby-then-keyword-and-case-in/index.html +++ /dev/null @@ -1,195 +0,0 @@ - - - - - - - - [Ruby] then キーワードと case in | REPL: Rest-Eat-Program Loop - - - - - - - - - - - - - - -
- -
-
- - -
-
-

[Ruby] then キーワードと case in

- -
-
-
-

更新履歴

-
    -
  • 2021-10-02: Qiita から移植
  • -
-
-

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

-
-

TL; DR

-

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

-

then とは

-

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

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

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

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

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

-

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

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

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

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

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

-

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

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

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

-

なぜ then; や改行が必要か

-

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

-
if a b end
-

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

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

then 等はこの曖昧性を排除するためにあり、条件式は if から then 等までの間にある、ということを明確にする。 -C系の if 後に来る (/) や、Python の :、Rust/Go/Swift などの { も同じ役割を持つ。

-

Ruby の場合、プログラマーが書きやすいよう改行でもって then が代用できるので、ほとんどの場合 then は必要ない。

-

case - in における then

-

ようやく本題にたどり着いた。来る Ruby 3.0 では casein キーワードを使ったパターンマッチングの構文が入る予定である。この構文でもパターン部との区切りとして then 等が必要になる。 -(現在の) Ruby には formal な形式での文法仕様は存在しないので、yacc の定義ファイルを参照した (yacc の説明は省略)。

-

https://github.com/ruby/ruby/blob/221ca0f8281d39f0dfdfe13b2448875384bbf735/parse.y#L3961-L3986

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

簡略版:

-
p_case_body	: keyword_in p_top_expr then compstmt p_cases
-		;
-

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

-

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

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

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

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

まとめ

-
    -
  • ifcase の条件の後ろには then;、改行のいずれかが必要 -
      -
    • 通常は改行しておけばよい
    • -
    -
  • -
  • 3.0 で入る予定の case - in でも then 等が必要になる
  • -
  • Ruby の構文を正確に知るには (現状) parse.y を直接読めばよい
  • -
- -
-
- - - - - - diff --git a/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 deleted file mode 100644 index 3aba89f..0000000 --- a/docs/posts/2021-10-02/rust-where-are-primitive-types-from/index.html +++ /dev/null @@ -1,182 +0,0 @@ - - - - - - - - Rust のプリミティブ型はどこからやって来るか | REPL: Rest-Eat-Program Loop - - - - - - - - - - - - - - -
- -
-
- - -
-
-

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

- -
-
-
-

更新履歴

-
    -
  • 2021-10-02: Qiita から移植
  • -
-
-

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

-
-

前置き

-

Rust において、プリミティブ型の名前は予約語でない。したがって、次のコードは合法である。

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

では、普段単に bool と書いたとき、この bool は一体どこから来ているのか。rustc のソースを追ってみた。

-
-

前提知識: 一般的なコンパイラの構造、用語。rustc そのものの知識は不要 (というよりも筆者自身がよく知らない)

-
-

調査

-

調査に使用したソース (調査時点での最新 master)

-

https://github.com/rust-lang/rust/tree/511ed9f2356af365ad8affe046b3dd33f7ac3c98

-

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

-

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

-

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

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

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

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

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

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

これは初めに列挙したプリミティブ型の一覧と一致している。doc comment にも、

-
-

All other types are defined somewhere and possibly imported, but the primitive ones need special handling, since they have no place of origin.

-
-

とある。次はこの struct の使用箇所を追う。追うと言っても使われている箇所は次の一箇所しかない。なお説明に不要な箇所は大きく削っている。

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

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

-

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

-

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

-

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

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

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

-

まとめ

-

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

- -
-
- - - - - - diff --git a/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 deleted file mode 100644 index 91708ad..0000000 --- a/docs/posts/2021-10-02/vim-difference-between-autocmd-bufwrite-and-bufwritepre/index.html +++ /dev/null @@ -1,132 +0,0 @@ - - - - - - - - [Vim] autocmd events の BufWrite/BufWritePre の違い | REPL: Rest-Eat-Program Loop - - - - - - - - - - - - - - -
- -
-
- - -
-
-

[Vim] autocmd events の BufWrite/BufWritePre の違い

- -
-
-
-

更新履歴

-
    -
  • 2021-10-02: Qiita から移植
  • -
-
-

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

-
-

TL; DR

-

違いはない。ただのエイリアス。

-

調査記録

-

Vim の autocmd events には似通った名前のものがいくつかある。大抵は :help に説明があるが、この記事のタイトルにある2つを含めた以下のイベントには、その違いについて説明がない。

-
    -
  • BufRead/BufReadPost
  • -
  • BufWrite/BufWritePre
  • -
  • BufAdd/BufCreate
  • -
-

このうち、BufAdd/BufCreate に関しては、:help BufCreate

-
-

The BufCreate event is for historic reasons.

-
-

とあり、おそらくは BufAdd のエイリアスであろうということがわかる。他の2組も同様ではないかと予想されるが、確認のため vim と neovim のソースコードを調査した。

-
-

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

-
-

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 のソースコード

-

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 にしっかりと書いてある。

-
                                                           *FileEncoding*
-FileEncoding                    Obsolete.  It still works and is equivalent
-                                to |EncodingChanged|.
-

まとめ

-

記事タイトルについて言えば、どちらも変わらないので好きな方を使えばよい。あえて言えば、次のようになるだろう。

-
    -
  • BufAdd/BufCreate -
      -
    • BufCreate は歴史的な理由により (“for historic reasons”) 存在しているため、新しい方 (BufAdd) を使う
    • -
    -
  • -
  • BufRead/BufReadPost -
      -
    • BufReadPre との対称性のため、あるいは BufWritePost との対称性のため BufReadPost を使う
    • -
    -
  • -
  • BufWrite/BufWritePre -
      -
    • BufWritePost との対称性のため、あるいは BufReadPre との対称性のため BufWritePre を使う
    • -
    -
  • -
  • FileEncoding/EncodingChanged -
      -
    • FileEncoding は “Obsolete” と明言されているので、EncodingChanged を使う
    • -
    -
  • -
-

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

- -
-
- - - - - - diff --git a/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 deleted file mode 100644 index eac828f..0000000 --- a/docs/posts/2021-10-02/vim-swap-order-of-selected-lines/index.html +++ /dev/null @@ -1,148 +0,0 @@ - - - - - - - - Vimで選択した行の順番を入れ替える | REPL: Rest-Eat-Program Loop - - - - - - - - - - - - - - -
- -
-
- - -
-
-

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

- -
-
-
-

更新履歴

-
    -
  • 2021-10-02: Qiita から移植
  • -
-
-

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

-
-

バージョン情報

-

:version の一部

-
-

VIM - Vi IMproved 8.2 (2019 Dec 12, compiled Jan 26 2020 11:30:30) -macOS version -Included patches: 1-148 -Huge version without GUI.

-
-

よく紹介されている手法

-

tac / tail

-

tactail -r などの外部コマンドを ! を使って呼び出し、置き換える。

-
-

:h v_!

-
-

tac コマンドや tail-r オプションは環境によって利用できないことがあり、複数の環境を行き来する場合に採用しづらい

-

:g/^/m0

-

こちらは外部コマンドに頼らず、Vim の機能のみを使う。g:global コマンドの、m:move コマンドの略

-

:global コマンドは :[range]global/{pattern}/[command] のように使い、[range] で指定された範囲の行のうち、{pattern} で指定された検索パターンにマッチする行に対して、順番に [command] で指定された Ex コマンドを呼び出す。

-
-

:h :global

-
-

:move コマンドは [range]:move {address} のように使い、[range] で指定された範囲の行を {address} で指定された位置に移動させる。

-
-

:h :move

-
-

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

-

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

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

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

-

:g/^/m0 の問題点

-

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

-
-

:h @/

-
-

解決策

-
-

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

-
-

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

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

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

-

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

-

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

-
-

:h autocmd-searchpat

-

Autocommands do not change the current search patterns. Vim saves the current -search patterns before executing autocommands then restores them after the -autocommands finish. This means that autocommands do not affect the strings -highlighted with the ‘hlsearch’ option.

-
-

これは autocommand の実行に関しての記述だが、これと同じことがユーザー定義関数の実行時にも適用される。このことは :nohlsearch のヘルプにある。同じく該当箇所を引用する (強調は筆者による)。

-
-

:h :nohlsearch

-

(略) This command doesn’t work in an autocommand, because -the highlighting state is saved and restored when -executing autocommands |autocmd-searchpat|. -Same thing for when invoking a user function.

-
-

この仕様により、:g/^/m0 の呼び出しをユーザー定義関数に切り出すことで上述の問題を解決できる。

-

解決策 (改訂版)

-
-

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

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

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

-
-

:h :keeppatterns

-
-

コピペ用再掲

-
" License: Public Domain
-
-command! -bar -range=%
-    \ Reverse
-    \ keeppatterns <line1>,<line2>g/^/m<line1>-1
-
-
-
- - - - - - -- cgit v1.2.3-70-g09d2