From 994e0114d76ae19768d5c303874a968cf6369fd0 Mon Sep 17 00:00:00 2001 From: nsfisis Date: Thu, 7 Sep 2023 22:27:48 +0900 Subject: meta: migrate to monorepo --- .../cpp-you-can-use-keywords-in-attributes.xml | 143 +++++++++++ .../2021-10-02/python-unbound-local-error.xml | 87 +++++++ .../ruby-detect-running-implementation.xml | 122 ++++++++++ .../2021-10-02/ruby-then-keyword-and-case-in.xml | 270 +++++++++++++++++++++ .../rust-where-are-primitive-types-from.xml | 237 ++++++++++++++++++ ...ce-between-autocmd-bufwrite-and-bufwritepre.xml | 165 +++++++++++++ .../vim-swap-order-of-selected-lines.xml | 215 ++++++++++++++++ 7 files changed, 1239 insertions(+) create mode 100644 vhosts/blog/content/posts/2021-10-02/cpp-you-can-use-keywords-in-attributes.xml create mode 100644 vhosts/blog/content/posts/2021-10-02/python-unbound-local-error.xml create mode 100644 vhosts/blog/content/posts/2021-10-02/ruby-detect-running-implementation.xml create mode 100644 vhosts/blog/content/posts/2021-10-02/ruby-then-keyword-and-case-in.xml create mode 100644 vhosts/blog/content/posts/2021-10-02/rust-where-are-primitive-types-from.xml create mode 100644 vhosts/blog/content/posts/2021-10-02/vim-difference-between-autocmd-bufwrite-and-bufwritepre.xml create mode 100644 vhosts/blog/content/posts/2021-10-02/vim-swap-order-of-selected-lines.xml (limited to 'vhosts/blog/content/posts/2021-10-02') diff --git a/vhosts/blog/content/posts/2021-10-02/cpp-you-can-use-keywords-in-attributes.xml b/vhosts/blog/content/posts/2021-10-02/cpp-you-can-use-keywords-in-attributes.xml new file mode 100644 index 00000000..f87d3a65 --- /dev/null +++ b/vhosts/blog/content/posts/2021-10-02/cpp-you-can-use-keywords-in-attributes.xml @@ -0,0 +1,143 @@ + +
+ + 【C++】 属性構文の属性名にはキーワードが使える + + C++ の属性構文の属性名には、キーワードが使える。ネタ記事。 + + + cpp + cpp17 + + + + 2021-10-02 + Qiita から移植 + + + + + この記事は Qiita から移植してきたものです。 + 元 URL: https://qiita.com/nsfisis/items/94090937bcf860cfa93b + + + タイトル落ち。まずはこのコードを見て欲しい。 + + + + + [[alignas]] [[alignof]] [[and]] [[and_eq]] [[asm]] [[auto]] [[bitand]] + [[bitor]] [[bool]] [[break]] [[case]] [[catch]] [[char]] [[char16_t]] + [[char32_t]] [[class]] [[compl]] [[const]] [[const_cast]] [[constexpr]] + [[continue]] [[decltype]] [[default]] [[delete]] [[do]] [[double]] + [[dynamic_cast]] [[else]] [[enum]] [[explicit]] [[export]] [[extern]] [[false]] + [[final]] [[float]] [[for]] [[friend]] [[goto]] [[if]] [[inline]] [[int]] + [[long]] [[mutable]] [[namespace]] [[new]] [[noexcept]] [[not]] [[not_eq]] + [[nullptr]] [[operator]] [[or]] [[or_eq]] [[override]] [[private]] + [[protected]] [[public]] [[register]] [[reinterpret_cast]] [[return]] [[short]] + [[signed]] [[sizeof]] [[static]] [[static_assert]] [[static_cast]] [[struct]] + [[switch]] [[template]] [[this]] [[thread_local]] [[throw]] [[true]] [[try]] + [[typedef]] [[typeid]] [[typename]] [[union]] [[unsigned]] + [[virtual]] [[void]] [[volatile]] [[wchar_t]] [[while]] [[xor]] [[xor_eq]] + // [[using]] + int main() { + std::cout << "Hello, World!" << std::endl; + } + ]]> + +
+ + コンパイラのバージョン $ clang++ –version Apple clang version 11.0.0 + (clang-1100.0.33.8) Target: x86_64-apple-darwin19.6.0 Thread model: + posix InstalledDir: + /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin + + + コンパイルコマンド (C17指定) $ clang –std=c++17 hoge.cpp + +
+ + この記事から得られるものはこれ以上ないので以下は蛇足になる。 + + + 別件で cppreference.com の + identifier + のページ を読んでいた時、次の文が目に止まった。 + +
+ + + the identifiers that are keywords cannot be used for other purposes; + + + The only place they can be used as non-keywords is in an + attribute-token. (e.g. [[private]] is a valid attribute) (since C++11) + + + + +
+ + キーワードでも属性として指定する場合は非キーワードとして使えるらしい。 + 実際にやってみる。 + + + 同サイトの keywords のページ + から一覧を拝借し、上のコードが出来上がった (C++17 + においてキーワードでないものなど、一部省いている)。 大量の警告 (unknown + attribute `〇〇' ignored) + がコンパイラから出力されるが、コンパイルできる。 + + + 上のコードでは [[using]] をコメントアウトしているが、これは using + キーワードのみ属性構文の中で意味を持つからであり、このコメントアウトを外すとコンパイルに失敗する。 + + + + + + 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/vhosts/blog/content/posts/2021-10-02/python-unbound-local-error.xml b/vhosts/blog/content/posts/2021-10-02/python-unbound-local-error.xml new file mode 100644 index 00000000..eb8aa452 --- /dev/null +++ b/vhosts/blog/content/posts/2021-10-02/python-unbound-local-error.xml @@ -0,0 +1,87 @@ + +
+ + 【Python】 クロージャとUnboundLocalError: local variable 'x' referenced before assignment + + Python における UnboundLocalError の理由と対処法。 + + + python + python3 + + + + 2021-10-02 + Qiita から移植 + + + + + この記事は Qiita から移植してきたものです。 + 元 URL: https://qiita.com/nsfisis/items/5d733703afcb35bbf399 + + + 本記事は Python 3.7.6 の動作結果を元にして書かれている。 + + + Python でクロージャを作ろうと、次のようなコードを書いた。 + + + + + + 関数 g から 関数 f のスコープ内で定義された変数 x を参照し、それに + 1 を足そうとしている。 これを実行すると x += 1 + の箇所でエラーが発生する。 + +
+ + UnboundLocalError: local variable `x' referenced before assignment + +
+ + local変数 x が代入前に参照された、とある。これは、fx + を参照するのではなく、新しく別の変数を g 内に作ってしまっているため。 + 前述のコードを宣言と代入を便宜上分けて書き直すと次のようになる。var + を変数宣言のための構文として擬似的に利用している。 + + + + + + 当初の意図を表現するには、次のように書けばよい。 + + + + + + (*) のように、nonlocal を追加する。これにより一つ外側のスコープ (g + の一つ外側 = f) で定義されている x を探しに行くようになる。 + +
diff --git a/vhosts/blog/content/posts/2021-10-02/ruby-detect-running-implementation.xml b/vhosts/blog/content/posts/2021-10-02/ruby-detect-running-implementation.xml new file mode 100644 index 00000000..7c0c960d --- /dev/null +++ b/vhosts/blog/content/posts/2021-10-02/ruby-detect-running-implementation.xml @@ -0,0 +1,122 @@ + +
+ + 【Ruby】 自身を実行している処理系の種類を判定する + + Ruby には複数の実装があるが、自身を実行している処理系の種類をスクリプト上からどのように判定すればよいだろうか。 + + + ruby + + + + 2021-10-02 + Qiita から移植 + + + + + この記事は Qiita から移植してきたものです。 + 元 URL: https://qiita.com/nsfisis/items/74d7ffeeebc51b20d791 + + + Ruby + という言語には複数の実装があるが、それらをスクリプト上からどのようにして + programmatically に見分ければよいだろうか。 + + + Object クラスに定義されている RUBY_ENGINE + という定数がこの用途に使える。 + + + 参考: + Object::RUBY_ENGINE + + + 上記ページの例から引用する: + + + + + + それぞれの処理系がどのような値を返すかだが、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' + が返ってくることを確認済み。 + + + この表にない主要な処理系として、https://mruby.org[mruby] は 'mruby' + を返す。 + + + mruby + 該当部分のソース より引用: + + + + +
diff --git a/vhosts/blog/content/posts/2021-10-02/ruby-then-keyword-and-case-in.xml b/vhosts/blog/content/posts/2021-10-02/ruby-then-keyword-and-case-in.xml new file mode 100644 index 00000000..0a799a3b --- /dev/null +++ b/vhosts/blog/content/posts/2021-10-02/ruby-then-keyword-and-case-in.xml @@ -0,0 +1,270 @@ + +
+ + 【Ruby】 then キーワードと case in + + Ruby 3.0 で追加される case in 構文と、then キーワードについて。 + + + ruby + ruby3 + + + + 2021-10-02 + Qiita から移植 + + + + + この記事は Qiita から移植してきたものです。 + 元 URL: https://qiita.com/nsfisis/items/787a8cf888a304497223 + +
+ TL; DR + + case - in によるパターンマッチング構文でも、case - when + と同じように then が使える (場合によっては使う必要がある)。 + +
+
+ <literal>then</literal> とは + + 使われることは稀だが、Ruby では then + がキーワードになっている。次のように使う: + + + + + + このキーワードが現れうる場所はいくつかあり、ifunlessrescuecase + 構文がそれに当たる。 上記のように、何か条件を書いた後 then + を置き、式がそこで終了していることを示すマーカーとして機能する。 + + + + +
+
+ なぜ普段は書かなくてもよいのか + + 普通 Ruby のコードで then + を書くことはない。なぜか。次のコードを実行してみるとわかる。 + + + + + + 次のような構文エラーが出力される。 + + + + + + 二つ目のメッセージは無視して一つ目を読むと、then; + か改行が来るはずのところ変数だかメソッドだかが現れたことによりエラーとなっているようだ。 + + + ポイントは改行が then (や ;) の代わりとなることである。true + の後に改行を入れてみる。 + + + + + + 無事 Hello, World! と出力されるようになった。 + +
+
+ なぜ <literal>then</literal> や <literal>;</literal> や改行が必要か + + なぜ then; や改行 (以下 「then 等」) + が必要なのだろうか。次の例を見てほしい: + + + + + + then; + も改行もないのでエラーになるが、これは条件式がどこまで続いているのかわからないためだ。 + この例は二通りに解釈できる。 + + + + + + + + + then 等はこの曖昧性を排除するためにあり、条件式は if から then + 等までの間にある、ということを明確にする。 C系の if 後に来る (/) + や、Python の :、Rust/Go/Swift などの { も同じ役割を持つ。 + + + Ruby の場合、プログラマーが書きやすいよう改行でもって then + が代用できるので、ほとんどの場合 then は必要ない。 + +
+
+ <literal>case</literal> - <literal>in</literal> における <literal>then</literal> + + ようやく本題にたどり着いた。来る Ruby 3.0 では casein + キーワードを使ったパターンマッチングの構文が入る予定である。この構文でもパターン部との区切りとして + then 等が必要になる。 (現在の) Ruby には formal + な形式での文法仕様は存在しないので、yacc の定義ファイルを参照した (yacc + の説明は省略)。 + + + https://github.com/ruby/ruby/blob/221ca0f8281d39f0dfdfe13b2448875384bbf735/parse.y#L3961-L3986 + + + command_start = FALSE; + $1 = p->ctxt; + p->ctxt.in_kwarg = 1; + $$ = push_pvtbl(p); + } + { + $$ = push_pktbl(p); + } + p_top_expr then + { + pop_pktbl(p, $3); + pop_pvtbl(p, $2); + p->ctxt.in_kwarg = $1.in_kwarg; + } + compstmt + p_cases + { + /*%%%*/ + $$ = NEW_IN($4, $7, $8, &@$); + /*% %*/ + /*% ripper: in!($4, $7, escape_Qundef($8)) %*/ + } + ; + ]]> + + + 簡略版: + + + + + + ここで、keyword_in は文字通り inp_top_expr + はいわゆるパターン、thenthen + キーワードのことではなく、この記事で then 等と呼んでいるもの、つまり + then キーワード、;、改行のいずれかである。 + + + これにより、case - when による従来の構文と同じように、then + 等をパターンの後ろに挿入すればよいことがわかった。つまり次の3通りのいずれかになる: + + + + + + ところで、p_top_expr には if による guard clause + が書けるので、その場合は if - then と似たような見た目になる。 + + + + +
+
+ まとめ + + + ifcase の条件の後ろには then;、改行のいずれかが必要 + + 通常は改行しておけばよい + + + 3.0 で入る予定の case - in でも then 等が必要になる + Ruby の構文を正確に知るには (現状) parse.y を直接読めばよい + +
+
diff --git a/vhosts/blog/content/posts/2021-10-02/rust-where-are-primitive-types-from.xml b/vhosts/blog/content/posts/2021-10-02/rust-where-are-primitive-types-from.xml new file mode 100644 index 00000000..3aaca63f --- /dev/null +++ b/vhosts/blog/content/posts/2021-10-02/rust-where-are-primitive-types-from.xml @@ -0,0 +1,237 @@ + +
+ + Rust のプリミティブ型はどこからやって来るか + + Rust のプリミティブ型は予約語ではなく普通の識別子である。どのようにこれが名前解決されるのかを調べた。 + + + rust + + + + 2021-10-02 + Qiita から移植 + + + + + この記事は Qiita から移植してきたものです。 + 元 URL: https://qiita.com/nsfisis/items/9a429432258bbcd6c565 + +
+ 前置き + + Rust + において、プリミティブ型の名前は予約語でない。したがって、次のコードは合法である。 + + + + + + では、普段単に 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 してみる。 + + + + + + 165 + 程度であれば探すことができそうだ。今回は、クレート名を見ておおよその当たりをつけた。 + + + + + + rustc_resolve + というのはいかにも名前解決を担いそうなクレート名である。該当箇所を見てみる。 + + + , + } + + 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 + の使用箇所を追う。追うと言っても使われている箇所は次の一箇所しかない。なお説明に不要な箇所は大きく削っている。 + + + Option> { + // (略) + + 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() + の最後に書かれている点である。つまり、最初に挙げたプリミティブ型の識別子は、「名前解決の最終段階で」、「他に同名の型が見つかっていなければ」プリミティブ型として解決される。 + + + 動作がわかったところで、例として次のコードを考える。 + + + + + + ここで main()boolstruct bool + として解決される。なぜなら、プリミティブ型の判定をする前に bool + という名前の別の型が見つかるからだ。 + +
+
+ まとめ + + Rust + のプリミティブ型は予約語ではない。名前解決の最終段階で特別扱いされ、他に同名の型が見つかっていなければ対応するプリミティブ型に解決される。 + +
+
diff --git a/vhosts/blog/content/posts/2021-10-02/vim-difference-between-autocmd-bufwrite-and-bufwritepre.xml b/vhosts/blog/content/posts/2021-10-02/vim-difference-between-autocmd-bufwrite-and-bufwritepre.xml new file mode 100644 index 00000000..ef17ae38 --- /dev/null +++ b/vhosts/blog/content/posts/2021-10-02/vim-difference-between-autocmd-bufwrite-and-bufwritepre.xml @@ -0,0 +1,165 @@ + +
+ + 【Vim】 autocmd events の BufWrite/BufWritePre の違い + + Vim の autocmd events における BufWrite/BufWritePre がどう違うのかを調べた結果、違いはないことがわかった。 + + + vim + + + + 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 + + + + + + https://github.com/vim/vim/blob/8e6be34338f13a6a625f19bcef82019c9adc65f2/src/autocmd.c#L95-L97 + + + + + + https://github.com/vim/vim/blob/8e6be34338f13a6a625f19bcef82019c9adc65f2/src/autocmd.c#L103-L105 + + + + +
+
+ neovim のソースコード + + neovim の場合でも同様のマッピングが定義されているが、こちらの場合は Lua + で書かれている。以下にある通り、はっきり aliases と書かれている。 + + + https://github.com/neovim/neovim/blob/71d4f5851f068eeb432af34850dddda8cc1c71e3/src/nvim/auevents.lua#L119-L124 + + + + + + ところで、上では取り上げなかった FileEncoding だが、これは + :help FileEncoding にしっかりと書いてある。 + + + + +
+
+
+ まとめ + + 記事タイトルについて言えば、どちらも変わらないので好きな方を使えばよい。あえて言えば、次のようになるだろう。 + + + + 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/vhosts/blog/content/posts/2021-10-02/vim-swap-order-of-selected-lines.xml b/vhosts/blog/content/posts/2021-10-02/vim-swap-order-of-selected-lines.xml new file mode 100644 index 00000000..c4c26ca8 --- /dev/null +++ b/vhosts/blog/content/posts/2021-10-02/vim-swap-order-of-selected-lines.xml @@ -0,0 +1,215 @@ + +
+ + Vimで選択した行の順番を入れ替える + + Vim で選択した行の順番を入れ替える方法。 + + + vim + + + + 2021-10-02 + Qiita から移植 + + + + + この記事は Qiita から移植してきたものです。 + 元 URL: https://qiita.com/nsfisis/items/4fefb361d9a693803520 + +
+ TL; DR + + ,g/^/m-1 + ]]> + +
+
+ バージョン情報 + + :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. + +
+
+
+ よく紹介されている手法 +
+ <literal>tac</literal> / <literal>tail</literal> + + tactail -r などの外部コマンドを ! + を使って呼び出し、置き換える。 + +
+ + :h v_! + +
+ + tac コマンドや tail-r + オプションは環境によって利用できないことがあり、複数の環境を行き来する場合に採用しづらい + +
+
+ <literal>:g/^/m0</literal> + + こちらは外部コマンドに頼らず、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行目を処理範囲とするよう拡張できる。手でこれを入力するわけにはいかないので、次のようなコマンドを用意する。 + + + ,g/^/m-1 + ]]> + + + これは望みの動作をするが、実際に実行してみると全行がハイライトされてしまう。次節で詳細を述べる。 + +
+
+
+ <literal>:g/^/m0</literal> の問題点 + + :global + コマンドは各行に対してマッチングを行う際、現在の検索パターンを上書きしてしまう。^ + は行の先頭にマッチするため、結果として全ての行がハイライトされてしまう。'hlsearch' + オプションを無効にしている場合その限りではないが、その場合でも直前の検索パターンが失われてしまうと + n コマンドなどの際に不便である。 + +
+ + :h @/ + +
+
+
+ 解決策 +
+ + [2020/9/28追記] より簡潔な方法を見つけたので次節に追記した + +
+ + 前述した :Reverse コマンドの定義を少し変えて、次のようにする: + + + reverse_lines(, ) + ]]> + + + 実行しているコマンドが変わったわけではないが、関数呼び出しを経由するようにした。これだけで前述の問題が解決する。 + + + この理由は、ユーザー定義関数を実行する際は検索パターンが一度保存され、実行が終了したあと復元されるため。結果として検索パターンが + ^ で上書きされることがなくなる。 + + + 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追記] より簡潔な方法を見つけたため追記する + +
+ + ,g/^/m-1 + ]]> + + + まさにこのための Exコマンド、:keeppatterns + が存在する。:keeppatterns {command} + のように使い、読んで字の如く、後ろに続く + Exコマンドを「現在の検索パターンを保ったまま」実行する。はるかに分かりやすく意図を表現できる。 + +
+ + :h :keeppatterns + +
+
+
-- cgit v1.2.3-70-g09d2