diff options
Diffstat (limited to 'content/posts/2021-10-02')
7 files changed, 843 insertions, 0 deletions
diff --git a/content/posts/2021-10-02/cpp-you-can-use-keywords-in-attributes.md b/content/posts/2021-10-02/cpp-you-can-use-keywords-in-attributes.md new file mode 100644 index 0000000..78267d7 --- /dev/null +++ b/content/posts/2021-10-02/cpp-you-can-use-keywords-in-attributes.md @@ -0,0 +1,93 @@ +--- +title: "[C++] 属性構文の属性名にはキーワードが使える [[void]] [[for]]" +date: 2021-10-02T09:38:30+09:00 +draft: false +tags: ["cpp", "cpp17"] +aliases: ['/posts/cpp-you-can-use-keywords-in-attributes/'] +--- + + +この記事は Qiita から移植してきたものです。 +元 URL: https://qiita.com/nsfisis/items/94090937bcf860cfa93b + + +----------------------------------- + + +タイトル落ち。まずはこのコードを見て欲しい。 + +```cpp +#include <iostream> + +[[alignas]] [[alignof]] [[and]] [[and_eq]] [[asm]] [[auto]] [[bitand]] +[[bitor]] [[bool]] [[break]] [[case]] [[catch]] [[char]] [[char16_t]] +[[char32_t]] [[class]] [[compl]] [[const]] [[const_cast]] [[constexpr]] +[[continue]] [[decltype]] [[default]] [[delete]] [[do]] [[double]] +[[dynamic_cast]] [[else]] [[enum]] [[explicit]] [[export]] [[extern]] [[false]] +[[final]] [[float]] [[for]] [[friend]] [[goto]] [[if]] [[inline]] [[int]] +[[long]] [[mutable]] [[namespace]] [[new]] [[noexcept]] [[not]] [[not_eq]] +[[nullptr]] [[operator]] [[or]] [[or_eq]] [[override]] [[private]] +[[protected]] [[public]] [[register]] [[reinterpret_cast]] [[return]] [[short]] +[[signed]] [[sizeof]] [[static]] [[static_assert]] [[static_cast]] [[struct]] +[[switch]] [[template]] [[this]] [[thread_local]] [[throw]] [[true]] [[try]] +[[typedef]] [[typeid]] [[typename]] [[union]] [[unsigned]] +[[virtual]] [[void]] [[volatile]] [[wchar_t]] [[while]] [[xor]] [[xor_eq]] +// [[using]] +int main() { + std::cout << "Hello, World!" << std::endl; +} +``` + +> コンパイラのバージョン +> $ clang++ --version +> Apple clang version 11.0.0 (clang-1100.0.33.8) +> Target: x86_64-apple-darwin19.6.0 +> Thread model: posix +> InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin +> +> コンパイルコマンド (C++17指定) +> $ clang++ --std=c++17 hoge.cpp + +この記事から得られるものはこれ以上ないので以下は蛇足になる。 + +別件で cppreference.com の [identifier のページ](https://en.cppreference.com/w/cpp/language/identifiers) を読んでいた時、次の文が目に止まった。 + +> * 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` キーワードのみ属性構文の中で意味を持つからであり、このコメントアウトを外すとコンパイルに失敗する。 + +```cpp +// using の例 +[[using foo: attr1, attr2]] int x; // [[foo::attr1, foo::attr2]] の糖衣構文 +``` + +C++17 の仕様も見てみる (正確には標準化前のドラフト)。 + +引用元: https://timsong-cpp.github.io/cppwp/n4659/dcl.attr#grammar-4 + +> If a keyword or an alternative token that satisfies the syntactic requirements of an identifier is contained in an attribute-token, it is considered an identifier. + +「`identifier` の構文上の要件を満たすキーワードまたは代替トークンが `attribute-token` に含まれている場合、`identifier` とみなされる」とある。どうやら間違いないようだ。 + +ところで、代替トークン (alternative token) とは `and` (`&`) や `bitor` (`|`) などのことだが、`identifier` の構文上の要件を満たさないような代替トークンなどあるのか? +疑問に思って調べたところ、代替トークンという語にはダイグラフも含まれるらしい (参考: [同ドラフト](https://timsong-cpp.github.io/cppwp/n4659/lex.digraph)) + +- `<%` → `{` +- `%>` → `}` +- `<:` → `[` +- `:>` → `]` +- `%:` → `#` +- `%:%:` → `##` + +「`identifier` の構文上の要件を満たさないような代替トークン」はこれらが当てはまると思われる。 + + +調べた感想: 字句解析器か構文解析器が辛そう + diff --git a/content/posts/2021-10-02/python-unbound-local-error.md b/content/posts/2021-10-02/python-unbound-local-error.md new file mode 100644 index 0000000..45baac6 --- /dev/null +++ b/content/posts/2021-10-02/python-unbound-local-error.md @@ -0,0 +1,65 @@ +--- +title: "[Python] クロージャとUnboundLocalError: local variable 'x' referenced before assignment" +date: 2021-10-02T09:32:37+09:00 +draft: false +tags: ["python", "python3"] +aliases: ['/posts/python-unbound-local-error/'] +--- + +この記事は Qiita から移植してきたものです。 +元 URL: https://qiita.com/nsfisis/items/5d733703afcb35bbf399 + + +----------------------------------- + + +本記事は Python 3.7.6 の動作結果を元にして書かれている。 + + +Python でクロージャを作ろうと、次のようなコードを書いた。 + +```python +def f(): + x = 0 + def g(): + x += 1 + g() + +f() +``` + +関数 `g` から 関数 `f` のスコープ内で定義された変数 `x` を参照し、それに 1 を足そうとしている。 +これを実行すると `x += 1` の箇所でエラーが発生する。 + +> UnboundLocalError: local variable 'x' referenced before assignment + +local変数 `x` が代入前に参照された、とある。これは、`f` の `x` を参照するのではなく、新しく別の変数を `g` 内に作ってしまっているため。 +前述のコードを宣言と代入を便宜上分けて書き直すと次のようになる。`var` を変数宣言のための構文として擬似的に利用している。 + +```python +# 注: var は正しい Python の文法ではない。上記参照のこと +def f(): + var x # f の local変数 'x' を宣言 + x = 0 # x に 0 を代入 + def g(): # f の内部関数 g を定義 + var x # g の local変数 'x' を宣言 + # たまたま f にも同じ名前の変数があるが、それとは別の変数 + x += 1 # x に 1 を加算 (x = x + 1 の糖衣構文) + # 加算する前の値を参照しようとするが、まだ代入されていないためエラー + g() +``` + +当初の意図を表現するには、次のように書けばよい。 + +```python +def f(): + x = 0 + def g(): + nonlocal x ## (*) + x += 1 + g() +``` + +`(*)` のように、`nonlocal` を追加する。これにより一つ外側のスコープ (`g` の一つ外側 = `f`) で定義されている `x` を探しに行くようになる。 + + diff --git a/content/posts/2021-10-02/ruby-detect-running-implementation.md b/content/posts/2021-10-02/ruby-detect-running-implementation.md new file mode 100644 index 0000000..1f1d78b --- /dev/null +++ b/content/posts/2021-10-02/ruby-detect-running-implementation.md @@ -0,0 +1,62 @@ +--- +title: "[Ruby] 自身を実行している処理系の種類を判定する" +date: 2021-10-02T09:37:50+09:00 +draft: false +tags: ["ruby"] +aliases: ['/posts/ruby-detect-running-implementation/'] +--- + +この記事は Qiita から移植してきたものです。 +元 URL: https://qiita.com/nsfisis/items/74d7ffeeebc51b20d791 + + +----------------------------------- + + + +Ruby という言語には複数の実装があるが、それらをスクリプト上からどのようにして programmatically に見分ければよいだろうか。 + +`Object` クラスに定義されている `RUBY_ENGINE` という定数がこの用途に使える。 + +参考: [Object::RUBY_ENGINE](https://docs.ruby-lang.org/ja/latest/method/Object/c/RUBY_ENGINE.html) + +上記ページの例から引用する: + +```shell-session +$ ruby-1.9.1 -ve 'p RUBY_ENGINE' +ruby 1.9.1p0 (2009-03-04 revision 22762) [x86_64-linux] +"ruby" +$ jruby -ve 'p RUBY_ENGINE' +jruby 1.2.0 (ruby 1.8.6 patchlevel 287) (2009-03-16 rev 9419) [i386-java] +"jruby" +``` + +それぞれの処理系がどのような値を返すかだが、stack overflow に良い質問と回答があった。 + +[What values for RUBY_ENGINE correspond to which Ruby implementations?](https://stackoverflow.com/a/9894232) より引用: + +> | RUBY_ENGINE | Implementation | +> |:-----------:|:------------------| +> | \<undefined\> | MRI < 1.9 | +> | 'ruby' | MRI >= 1.9 or REE | +> | 'jruby' | JRuby | +> | 'macruby' | MacRuby | +> | 'rbx' | Rubinius | +> | 'maglev' | MagLev | +> | 'ironruby' | IronRuby | +> | 'cardinal' | Cardinal | + + +なお、この質問・回答は 2014年になされたものであり、値は変わっている可能性がある。MRI (aka CRuby) については執筆時現在 (2020/12/8) も `'ruby'` が返ってくることを確認済み。 + +この表にない主要な処理系として、[mruby](https://mruby.org) は `'mruby'` を返す。 + +[mruby 該当部分のソース](https://github.com/mruby/mruby/blob/ed29d74bfd95362eaeb946fcf7e865d80346b62b/include/mruby/version.h#L32-L35) より引用: + +```c +/* + * Ruby engine. + */ +#define MRUBY_RUBY_ENGINE "mruby" +``` + diff --git a/content/posts/2021-10-02/ruby-then-keyword-and-case-in.md b/content/posts/2021-10-02/ruby-then-keyword-and-case-in.md new file mode 100644 index 0000000..ce311e6 --- /dev/null +++ b/content/posts/2021-10-02/ruby-then-keyword-and-case-in.md @@ -0,0 +1,203 @@ +--- +title: "[Ruby] then キーワードと case in" +date: 2021-10-02T09:38:50+09:00 +draft: false +tags: ["ruby", "ruby3"] +aliases: ['/posts/ruby-then-keyword-and-case-in/'] +--- + +この記事は Qiita から移植してきたものです。 +元 URL: https://qiita.com/nsfisis/items/787a8cf888a304497223 + + +----------------------------------- + + +# TL; DR + +`case` - `in` によるパターンマッチング構文でも、`case` - `when` と同じように `then` が使える (場合によっては使う必要がある)。 + +# `then` とは + +使われることは稀だが、Ruby では `then` がキーワードになっている。次のように使う: + +```ruby +if cond then + puts "Y" +else + puts "N" +end +``` + +このキーワードが現れうる場所はいくつかあり、`if`、`unless`、`rescue`、`case` 構文がそれに当たる。 +上記のように、何か条件を書いた後 `then` を置き、式がそこで終了していることを示すマーカーとして機能する。 + +```ruby +# Example: + +if x then + a +end + +unless x then + a +end + +begin + a +rescue then + b +end + +case x +when p then + a +end +``` + +# なぜ普段は書かなくてもよいのか + +普通 Ruby のコードで `then` を書くことはない。なぜか。次のコードを実行してみるとわかる。 + +```ruby +if true puts 'Hello, World!' end +``` + +次のような構文エラーが出力される。 + +``` +20:1: syntax error, unexpected local variable or method, expecting `then' or ';' or '\n' +if true puts 'Hello, World!' end + ^~~~ +20:1: syntax error, unexpected `end', expecting end-of-input +...f true puts 'Hello, World!' end +``` + +二つ目のメッセージは無視して一つ目を読むと、`then` か `;` か改行が来るはずのところ変数だかメソッドだかが現れたことによりエラーとなっているようだ。 + +ポイントは改行が `then` (や `;`) の代わりとなることである。`true` の後に改行を入れてみる。 + +```ruby +if true +puts 'Hello, World!' end +``` + +無事 Hello, World! と出力されるようになった。 + +# なぜ `then` や `;` や改行が必要か + +なぜ `then` や `;` や改行 (以下 「`then` 等」) が必要なのだろうか。次の例を見てほしい: + +```ruby +if a b end +``` + +`then` も `;` も改行もないのでエラーになるが、これは条件式がどこまで続いているのかわからないためだ。 +この例は二通りに解釈できる。 + +```ruby +# a という変数かメソッドの評価結果が truthy なら b という変数かメソッドを評価 +if a then + b +end +``` + +```ruby +# a というメソッドに b という変数かメソッドの評価結果を渡して呼び出し、 +# その結果が truthy なら何もしない +if a(b) then +end +``` + +`then` 等はこの曖昧性を排除するためにあり、条件式は `if` から `then` 等までの間にある、ということを明確にする。 +C系の `if` 後に来る `(`/`)` や、Python の `:`、Rust/Go/Swift などの `{` も同じ役割を持つ。 + +Ruby の場合、プログラマーが書きやすいよう改行でもって `then` が代用できるので、ほとんどの場合 `then` は必要ない。 + +# `case` - `in` における `then` + +ようやく本題にたどり着いた。来る Ruby 3.0 では `case` と `in` キーワードを使ったパターンマッチングの構文が入る予定である。この構文でもパターン部との区切りとして `then` 等が必要になる。 +(現在の) Ruby には formal な形式での文法仕様は存在しないので、yacc の定義ファイルを参照した (yacc の説明は省略)。 + +https://github.com/ruby/ruby/blob/221ca0f8281d39f0dfdfe13b2448875384bbf735/parse.y#L3961-L3986 + +```yacc +p_case_body : keyword_in + { + SET_LEX_STATE(EXPR_BEG|EXPR_LABEL); + p->command_start = FALSE; + $<ctxt>1 = p->ctxt; + p->ctxt.in_kwarg = 1; + $<tbl>$ = push_pvtbl(p); + } + { + $<tbl>$ = push_pktbl(p); + } + p_top_expr then + { + pop_pktbl(p, $<tbl>3); + pop_pvtbl(p, $<tbl>2); + p->ctxt.in_kwarg = $<ctxt>1.in_kwarg; + } + compstmt + p_cases + { + /*%%%*/ + $$ = NEW_IN($4, $7, $8, &@$); + /*% %*/ + /*% ripper: in!($4, $7, escape_Qundef($8)) %*/ + } + ; +``` + +簡略版: + +```yacc +p_case_body : keyword_in p_top_expr then compstmt p_cases + ; +``` + +ここで、`keyword_in` は文字通り `in`、`p_top_expr` はいわゆるパターン、`then` は `then` キーワードのことではなく、この記事で `then` 等と呼んでいるもの、つまり `then` キーワード、`;`、改行のいずれかである。 + +これにより、`case` - `when` による従来の構文と同じように、`then` 等をパターンの後ろに挿入すればよいことがわかった。つまり次の3通りのいずれかになる: + +```ruby +case x +in 1 then a +in 2 then b +in 3 then c +end + +case x +in 1 + a +in 2 + b +in 3 + c +end + +case x +in 1; a +in 2; b +in 3; c +end +``` + +ところで、`p_top_expr` には `if` による guard clause が書けるので、その場合は `if` - `then` と似たような見た目になる。 + +```ruby +case x +in 0 then a +in n if n < 0 then b +in n then c +end +``` + +# まとめ + +* `if` や `case` の条件の後ろには `then`、`;`、改行のいずれかが必要 + * 通常は改行しておけばよい +* 3.0 で入る予定の `case` - `in` でも `then` 等が必要になる +* Ruby の構文を正確に知るには (現状) `parse.y` を直接読めばよい + diff --git a/content/posts/2021-10-02/rust-where-are-primitive-types-from.md b/content/posts/2021-10-02/rust-where-are-primitive-types-from.md new file mode 100644 index 0000000..2f2f743 --- /dev/null +++ b/content/posts/2021-10-02/rust-where-are-primitive-types-from.md @@ -0,0 +1,173 @@ +--- +title: "Rust のプリミティブ型はどこからやって来るか" +date: 2021-10-02T09:39:27+09:00 +draft: false +tags: ["rust"] +aliases: ['/posts/rust-where-are-primitive-types-from/'] +--- + +この記事は Qiita から移植してきたものです。 +元 URL: https://qiita.com/nsfisis/items/9a429432258bbcd6c565 + + +----------------------------------- + + + +# 前置き + +Rust において、プリミティブ型の名前は予約語でない。したがって、次のコードは合法である。 + +```rust +#![allow(non_camel_case_types)] +#![allow(dead_code)] + +struct bool; +struct char; +struct i8; +struct i16; +struct i32; +struct i64; +struct i128; +struct isize; +struct u8; +struct u16; +struct u32; +struct u64; +struct u128; +struct usize; +struct f32; +struct f64; +struct str; +``` + +では、普段単に `bool` と書いたとき、この `bool` は一体どこから来ているのか。rustc のソースを追ってみた。 + +> 前提知識: 一般的なコンパイラの構造、用語。`rustc` そのものの知識は不要 (というよりも筆者自身がよく知らない) + +# 調査 + +調査に使用したソース (調査時点での最新 master) + +https://github.com/rust-lang/rust/tree/511ed9f2356af365ad8affe046b3dd33f7ac3c98 + +どのようにして調べるか。rustc の構造には詳しくないため、すぐに当たりをつけるのは難しい。 + +大雑把な構造としては、`compiler` フォルダ以下に `rustc_*` という名前のクレートが数十個入っている。これがどうやら `rustc` コマンドの実装部のようだ。 + +`rustc` はセルフホストされている (= `rustc` 自身が Rust で書かれている) ので、`bool` や `char` などで適当に検索をかけてもノイズが多すぎて話にならない。 +しかし、お誂え向きなことに `i128`/`u128` というコンパイラ自身が使うことがなさそうな型が存在するのでこれを使って `git grep` してみる。 + +``` +$ git grep "\bi128\b" | wc # i128 + 165 1069 15790 + +$ git grep "\bu128\b" | wc # u128 + 293 2127 26667 + +$ git grep "\bbool\b" | wc # cf. bool の結果 + 3563 23577 294659 +``` + +165 程度であれば探すことができそうだ。今回は、クレート名を見ておおよその当たりをつけた。 + +``` +$ git grep "\bi128\b" +... +rustc_resolve/src/lib.rs: table.insert(sym::i128, Int(IntTy::I128)); +... +``` + +`rustc_resolve` というのはいかにも名前解決を担いそうなクレート名である。該当箇所を見てみる。 + +```rust +/// Interns the names of the primitive types. +/// +/// All other types are defined somewhere and possibly imported, but the primitive ones need +/// special handling, since they have no place of origin. +struct PrimitiveTypeTable { + primitive_types: FxHashMap<Symbol, PrimTy>, +} + +impl PrimitiveTypeTable { + fn new() -> PrimitiveTypeTable { + let mut table = FxHashMap::default(); + + table.insert(sym::bool, Bool); + table.insert(sym::char, Char); + table.insert(sym::f32, Float(FloatTy::F32)); + table.insert(sym::f64, Float(FloatTy::F64)); + table.insert(sym::isize, Int(IntTy::Isize)); + table.insert(sym::i8, Int(IntTy::I8)); + table.insert(sym::i16, Int(IntTy::I16)); + table.insert(sym::i32, Int(IntTy::I32)); + table.insert(sym::i64, Int(IntTy::I64)); + table.insert(sym::i128, Int(IntTy::I128)); + table.insert(sym::str, Str); + table.insert(sym::usize, Uint(UintTy::Usize)); + table.insert(sym::u8, Uint(UintTy::U8)); + table.insert(sym::u16, Uint(UintTy::U16)); + table.insert(sym::u32, Uint(UintTy::U32)); + table.insert(sym::u64, Uint(UintTy::U64)); + table.insert(sym::u128, Uint(UintTy::U128)); + Self { primitive_types: table } + } +} +``` + +これは初めに列挙したプリミティブ型の一覧と一致している。doc comment にも、 + +> All other types are defined somewhere and possibly imported, but the primitive ones need special handling, since they have no place of origin. + +とある。次はこの struct の使用箇所を追う。追うと言っても使われている箇所は次の一箇所しかない。なお説明に不要な箇所は大きく削っている。 + +```rust + /// This resolves the identifier `ident` in the namespace `ns` in the current lexical scope. + /// (略) + fn resolve_ident_in_lexical_scope( + &mut self, + mut ident: Ident, + ns: Namespace, + // (略) + ) -> Option<LexicalScopeBinding<'a>> { + // (略) + + if ns == TypeNS { + if let Some(prim_ty) = self.primitive_type_table.primitive_types.get(&ident.name) { + let binding = + (Res::PrimTy(*prim_ty), ty::Visibility::Public, DUMMY_SP, ExpnId::root()) + .to_name_binding(self.arenas); + return Some(LexicalScopeBinding::Item(binding)); + } + } + + None + } +``` + +関数名や doc comment が示している通り、この関数は識別子 (identifier, ident) を現在のレキシカルスコープ内で解決 (resolve) する。 +`if ns == TypeNS` のブロック内では、`primitive_type_table` (上記の `PrimitiveTypeTable::new()` で作られた変数) に含まれている識別子 (`bool`、`i32` など) かどうか判定し、そうであればそれに紐づけられたプリミティブ型を返している。 + +なお、`ns` は「名前空間」を示す変数である。Rust における名前空間はC言語におけるそれとほとんど同じで、今探している名前が関数名/変数名なのか型なのかマクロなのかを区別している。この `if` は、プリミティブ型に解決されるのは型を探しているときだけだ、と言っている。 + +重要なのは、これが `resolve_ident_in_lexical_scope()` の最後に書かれている点である。つまり、最初に挙げたプリミティブ型の識別子は、「名前解決の最終段階で」、「他に同名の型が見つかっていなければ」プリミティブ型として解決される。 + +動作がわかったところで、例として次のコードを考える。 + +```rust +#![allow(non_camel_case_types)] + +struct bool; + +fn main() { + let _: bool = bool; +} +``` + +ここで `main()` の `bool` は `struct bool` として解決される。なぜなら、プリミティブ型の判定をする前に `bool` という名前の別の型が見つかるからだ。 + + +# まとめ + +Rust のプリミティブ型は予約語ではない。名前解決の最終段階で特別扱いされ、他に同名の型が見つかっていなければ対応するプリミティブ型に解決される。 + diff --git a/content/posts/2021-10-02/vim-difference-between-autocmd-bufwrite-and-bufwritepre.md b/content/posts/2021-10-02/vim-difference-between-autocmd-bufwrite-and-bufwritepre.md new file mode 100644 index 0000000..acf3704 --- /dev/null +++ b/content/posts/2021-10-02/vim-difference-between-autocmd-bufwrite-and-bufwritepre.md @@ -0,0 +1,104 @@ +--- +title: "[Vim] autocmd events の BufWrite/BufWritePre の違い" +date: 2021-10-02T09:37:12+09:00 +draft: false +tags: ["vim"] +aliases: ['/posts/vim-difference-between-autocmd-bufwrite-and-bufwritepre/'] +--- + +この記事は 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)](https://github.com/vim/vim/tree/8e6be34338f13a6a625f19bcef82019c9adc65f2) +> [neovim (上に同じ)](https://github.com/neovim/neovim/tree/71d4f5851f068eeb432af34850dddda8cc1c71e3) + +## vim のソースコード + +以下は、autocmd events の名前と内部で使われている整数値とのマッピングを定義している箇所である。見ての通り、上でエイリアスではないかと述べた3組には、それぞれ同じ内部値が使われている。 + +https://github.com/vim/vim/blob/8e6be34338f13a6a625f19bcef82019c9adc65f2/src/autocmd.c#L85-L86 + +```c + {"BufAdd", EVENT_BUFADD}, + {"BufCreate", EVENT_BUFADD}, +``` + +https://github.com/vim/vim/blob/8e6be34338f13a6a625f19bcef82019c9adc65f2/src/autocmd.c#L95-L97 + +```c + {"BufRead", EVENT_BUFREADPOST}, + {"BufReadCmd", EVENT_BUFREADCMD}, + {"BufReadPost", EVENT_BUFREADPOST}, +``` + +https://github.com/vim/vim/blob/8e6be34338f13a6a625f19bcef82019c9adc65f2/src/autocmd.c#L103-L105 + +```c + {"BufWrite", EVENT_BUFWRITEPRE}, + {"BufWritePost", EVENT_BUFWRITEPOST}, + {"BufWritePre", EVENT_BUFWRITEPRE}, +``` + +## neovim のソースコード + +neovim の場合でも同様のマッピングが定義されているが、こちらの場合は Lua で書かれている。以下にある通り、はっきり `aliases` と書かれている。 + +https://github.com/neovim/neovim/blob/71d4f5851f068eeb432af34850dddda8cc1c71e3/src/nvim/auevents.lua#L119-L124 + +```lua + aliases = { + BufCreate = 'BufAdd', + BufRead = 'BufReadPost', + BufWrite = 'BufWritePre', + FileEncoding = 'EncodingChanged', + }, +``` + +ところで、上では取り上げなかった `FileEncoding` だが、これは `:help FileEncoding` にしっかりと書いてある。 + +``` + *FileEncoding* +FileEncoding Obsolete. It still works and is equivalent + to |EncodingChanged|. +``` + +## まとめ + +記事タイトルについて言えば、どちらも変わらないので好きな方を使えばよい。あえて言えば、次のようになるだろう。 + +* `BufAdd`/`BufCreate` + * → `BufCreate` は歴史的な理由により ("for historic reasons") 存在しているため、新しい方 (`BufAdd`) を使う +* `BufRead`/`BufReadPost` + * → `BufReadPre` との対称性のため、あるいは `BufWritePost` との対称性のため `BufReadPost` を使う +* `BufWrite`/`BufWritePre` + * → `BufWritePost` との対称性のため、あるいは `BufReadPre` との対称性のため `BufWritePre` を使う +* `FileEncoding`/`EncodingChanged` + * → `FileEncoding` は "Obsolete" と明言されているので、`EncodingChanged` を使う + +ところでこの調査で知ったのだが、`BufRead` と `BufWrite` は上にある通り発火するタイミングが「後」と「前」で対称性がない。可能なら `Pre`/`Post` 付きのものを使った方が分かりやすいだろう。 + diff --git a/content/posts/2021-10-02/vim-swap-order-of-selected-lines.md b/content/posts/2021-10-02/vim-swap-order-of-selected-lines.md new file mode 100644 index 0000000..f49ed90 --- /dev/null +++ b/content/posts/2021-10-02/vim-swap-order-of-selected-lines.md @@ -0,0 +1,143 @@ +--- +title: "Vimで選択した行の順番を入れ替える" +date: 2021-10-02T09:37:25+09:00 +draft: false +tags: ["vim"] +aliases: ['/posts/vim-swap-order-of-selected-lines/'] +--- + +この記事は Qiita から移植してきたものです。 +元 URL: https://qiita.com/nsfisis/items/4fefb361d9a693803520 + + +----------------------------------- + + + +# バージョン情報 + +`:version` の一部 + +> VIM - Vi IMproved 8.2 (2019 Dec 12, compiled Jan 26 2020 11:30:30) +> macOS version +> Included patches: 1-148 +> Huge version without GUI. + + + +# よく紹介されている手法 + +## `tac` / `tail` + +`tac` や `tail -r` などの外部コマンドを `!` を使って呼び出し、置き換える。 + +> :h v_! + +`tac` コマンドや `tail` の `-r` オプションは環境によって利用できないことがあり、複数の環境を行き来する場合に採用しづらい + + + + +## `:g/^/m0` + +こちらは外部コマンドに頼らず、Vim の機能のみを使う。`g` は `:global` コマンドの、`m` は `:move` コマンドの略 + +`:global` コマンドは `:[range]global/{pattern}/[command]` のように使い、`[range]` で指定された範囲の行のうち、`{pattern}` で指定された検索パターンにマッチする行に対して、順番に `[command]` で指定された Ex コマンドを呼び出す。 + +> :h :global + +`:move` コマンドは `[range]:move {address}` のように使い、`[range]` で指定された範囲の行を `{address}` で指定された位置に移動させる。 + +> :h :move + +`:g/^/m0` のように組み合わせると、「すべての行を1行ずつ 0行目(1行目の上)に動かす」という動きをする。これは確かに行の入れ替えになっている。 + +なお、`:g/^/m0` は全ての行を入れ替えるが、`:N,Mg/^/mN-1` とすることで N行目から M行目を処理範囲とするよう拡張できる。手でこれを入力するわけにはいかないので、次のようなコマンドを用意する。 + +```vim +command! -bar -range=% + \ Reverse + \ <line1>,<line2>g/^/m<line1>-1 +``` + +これは望みの動作をするが、実際に実行してみると全行がハイライトされてしまう。次節で詳細を述べる。 + + + +# `:g/^/m0` の問題点 + +`:global` コマンドは各行に対してマッチングを行う際、現在の検索パターンを上書きしてしまう。`^` は行の先頭にマッチするため、結果として全ての行がハイライトされてしまう。`'hlsearch'` オプションを無効にしている場合その限りではないが、その場合でも直前の検索パターンが失われてしまうと `n` コマンドなどの際に不便である。 + +> :h @/ + + + +# 解決策 + +> [2020/9/28追記] +> より簡潔な方法を見つけたので次節に追記した + +前述した `:Reverse` コマンドの定義を少し変えて、次のようにする: + +```vim +function! s:reverse_lines(from, to) abort + execute printf("%d,%dg/^/m%d", a:from, a:to, a:from - 1) +endfunction + +command! -bar -range=% + \ Reverse + \ call <SID>reverse_lines(<line1>, <line2>) +``` + +実行しているコマンドが変わったわけではないが、関数呼び出しを経由するようにした。これだけで前述の問題が解決する。 + +この理由は、ユーザー定義関数を実行する際は検索パターンが一度保存され、実行が終了したあと復元されるため。結果として検索パターンが `^` で上書きされることがなくなる。 + +Vim のヘルプから該当箇所を引用する (強調は筆者による)。 + +> :h autocmd-searchpat +> +> **Autocommands do not change the current search patterns.** Vim saves the current +> search patterns before executing autocommands then restores them after the +> autocommands finish. This means that autocommands do not affect the strings +> highlighted with the 'hlsearch' option. + +これは autocommand の実行に関しての記述だが、これと同じことがユーザー定義関数の実行時にも適用される。このことは `:nohlsearch` のヘルプにある。同じく該当箇所を引用する (強調は筆者による)。 + +> :h :nohlsearch +> +> (略) This command doesn't work in an autocommand, because +> the highlighting state is saved and restored when +> executing autocommands |autocmd-searchpat|. +> **Same thing for when invoking a user function.** + +この仕様により、`:g/^/m0` の呼び出しをユーザー定義関数に切り出すことで上述の問題を解決できる。 + + +# 解決策 (改訂版) + +> [2020/9/28追記] +> より簡潔な方法を見つけたため追記する + +```vim +command! -bar -range=% + \ Reverse + \ keeppatterns <line1>,<line2>g/^/m<line1>-1 +``` + +まさにこのための Exコマンド、`:keeppatterns` が存在する。`:keeppatterns {command}` のように使い、読んで字の如く、後ろに続く Exコマンドを「現在の検索パターンを保ったまま」実行する。はるかに分かりやすく意図を表現できる。 + +> :h :keeppatterns + + +# コピペ用再掲 + + +```vim +" License: Public Domain + +command! -bar -range=% + \ Reverse + \ keeppatterns <line1>,<line2>g/^/m<line1>-1 +``` + |
