vim9script入門

とほほのwww入門にあるプログラミング言語入門風の情報が欲しかったので、vimのヘルプからコピペ。
旧来の記載が残っているかも。

# 参考文献というかコピペ元

[vimdoc-ja](https://vim-jp.org/vimdoc-ja/)

- [Vim9 script](https://vim-jp.org/vimdoc-ja/vim9.html)
- [VIM REFERENCE MANUAL - 式の評価](https://vim-jp.org/vimdoc-ja/eval.html)
- [VIM REFERENCE MANUAL - ユーザー定義関数](https://vim-jp.org/vimdoc-ja/userfunc.html)
- [Vim script 書法](https://vim-jp.org/vimdoc-ja/usr_41.html)

# ライセンス

コピペ元のライセンスに従います。

[CC-BY-4.0 license](https://creativecommons.org/licenses/by/4.0/deed.ja)

# vim script 実行方法　

## カレントバッファのスクリプトを実行

1. vim で対象スクリプトのファイルを開く

       $ vim test.vim

2. カレントバッファのスクリプトを以下のコマンドで実行

       :source %

## コマンドラインから実行

    $ vim -s test.vim

## オートロードスクリプトを実行

1. autoload ディレクトリにスクリプトを置く

       $ cp test.vim ~/.vim/autoload/

2. vim から以下のコマンドで実行

       :call filename#function()

   これらの関数は常にグローバルで、Vim9 script なら "g:" が必要:

       :call g:filename#function()

# コメント

```
# コメント
```

# ヒアドキュメント

内部変数 {var-name} を文字列 {endmarker} で囲まれたテキスト行を含むリスト List に設定する。
```
var {var-name} =<< [trim] [eval] {endmarker}
text...
text...
{endmarker}
```

- {endmarker} は空白を含んではならない
- {endmarker} は小文字で始めることはできない
- {endmarker} の行は他の文字を含めることができない
- `trim` がない場合、テキスト行内のすべての空白文字は保持される。
- `trim` が指定されている場合、最初の行のインデントがすべての行から取り除かれる

  ```
  let text =<< trim END
    if ok
      echo 'done'
    endif
  END

  結果: ["if ok", "  echo 'done'", "endif"]
  ```

- `eval` が指定されない場合、リテラル文字列として扱われるが、
  シングルクォートは除外され2重にする必要はない。
- `eval` が指定されている場合、`{expr}` 形式の任意の Vim の式が評価され、
  結果が文字列補間のように式を置き換える。

サンプル

```
var lines =<< trim END
  1行目
  2行目
  3行目
END
echo join(lines, "\n")
```

# データタイプ

```
bool
number
float
string
blob
list<{type}>
dict<{type}>
job
channel
func
func: {type}
func({type}, ...)
func({type}, ...): {type}
void
tuple<a: {type}, b: {type}, ...>  # まだサポートされてない
```

# 値

```
true, false                     # 真偽値
-123, 0x10, 0177 0o177 0b1011   # 数値
123.456, 1.15e-6, -1.1e3        # 浮動小数点
"hello, world", 'hello!'        # 文字列
[1, 2, ['a', 'b']]              # リスト
{'blue': "#00f", 'red': "#f00"} # 辞書
function("strlen")              # 関数への参照
v:false, v:true, v:null         # 特殊値
job_start()を参照               # ジョブ
ch_open() を参照                # チャンネル
0zFF00ED015DAF                  # Blog (バイナリラージオブジェクト)
```

# 変数、定数 (var, const, final)

```
var lnum = 1
```

変数とその値、両方を定数とするには、`:const` を使用。

```
const myList = [1, 2]
myList = [3, 4]		# エラー!
myList[0] = 9		# エラー!
myList->add(3)		# エラー!
```

変数の変更のみを禁止するには、`:final` を使用

```
final myList = [1, 2]
myList = [3, 4]		# エラー!
myList[0] = 9		# OK
myList->add(3)		# OK
```

# 演算子

式文法一覧、優先順位の低いものから高い順に:

```
expr2 ? expr1 : expr1       # if-then-else
expr2 ?? expr1              # Falsy演算子
expr3 || expr3 ...          # 論理和
expr4 || expr4 ...          # 論理積
expr5 == expr5              # 等しい
expr5 != expr5              # とくしくない
expt5 >  expt5              # より大きい
expr5 >= expr5              # より大きいか等しい
expr5 <  expr5              # より小さい
expr5 <= expr5              # 小さいか等しい
expr5 =~ expr5              # 正規表現にマッチする
expr5 !~ expr5              # 正規表現にマッチしない
expr5 ==? expr5             # 文字列として等しい(大文字/小文字区別無し)
expr5 ==# expr5             # 文字列として等しい(大文字/小文字区別有り)
expr5 is expr5              # 同一のリスト List、辞書 Dictionary またはBlob のインスタンス
expr5 isnot expr5           # 異なるリスト List、辞書 Dictionary またはBlob のインスタンス
expr6 << expr6              # ビット単位の左シフト
expr6 >> expr6              # ビット単位の右シフト
expr7 +  expr7 ...          # 足し算、リストまたはBlobの連結
expr7 -  expr7 ...          # 引き算
expr7 .  expr7 ...          # 文字列の連結
expr7 .. expr7 ...          # 文字列の連結
expr8 *  expr8 ...          # 掛け算
expr8 /  expr8 ...          # 割り算
expr8 %  expr8 ...          # 剰余(割った余り)
<type>expr9                 # 型のチェックと変換 (Vim9 のみ)
! expr9                     # 論理否定
- expr9                     # 単項のマイナス
+ expr9                     # 単項のプラス
expr10[expr1]               # 文字列のバイト、またはリストの要素
expr10[expr1 : expr1]       # 文字列の部分文字列、またはリストの部分リスト
expr10.name                 # 辞書 Dictionary の要素
expr10(expr1, ...)          # Funcref 変数による関数呼び出し
expr10->name(expr1, ...)    # method 呼び出し
number                      # 数定数
"string"                    # 文字列定数。バックスラッシュは特別な意味を持つ
$"Hello, {name}!"           # 文字列補間
'string'                    # リテラル文字列定数。'を含めるには2重にする
[expr1, ...]                # リスト List
{expr1: expr1, ...}         # 辞書 Dictionary
&option                     # オプション変数
(expr1)                     # 式の入れ子
variable                    # 内部変数
$VAR                        # 環境変数
@r                          # レジスタ 'r' の値
function(expr1, ...)        # 関数呼出し
(args) => expr1             # Vim9 のラムダ式
```

# 代入

```
{var-name} = {expr1}            # 内部変数{var-name}に式{expr1}の結果をセット
{var-name}[{idx}] = {expr1}     # リストの要素に式{expr1}の結果をセット
{var-name}[{idx1}:{idx2}] = {expr1}
                                # リストListの一部を式{expr}の値で置き換え
{var} += {expr1}                # {var} = {var} + {expr1}
{var} -= {expr1}                # {var} = {var} - {expr1}
{var} *= {expr1}                # {var} = {var} * {expr1}
{var} /= {expr1}                # {var} = {var} / {expr1}
{var} %= {expr1}                # {var} = {var} % {expr1}
{var} .= {expr1}                # {var} = {var} . {expr1}
{var} ..= {expr1}               # {var} = {var} .. {expr1}
${env-name} = {expr1}           # 環境変数{env-name}に式{expr1}の結果をセット
@{reg-name} = {expr1}           # 式{expr1}の結果をレジスタ{reg-name}に書きこむ
&{option-name} = {expr1}        # オプション{option-name}に式{expr}の値をセット
```

# リスト

```
var mylist = [1, two, 3, "four"]
var emptylist = []
var item = mylist[2]                    # 3番目の要素(3)を取得
var nestlist = [[11, 12], [21, 22], [31, 32]]
var longlist = [1, 2, 3] + [4, 5, 6]    # リストの連結
var otherlist = longlist[:]             # リストのコピー
var endlist = longlist[2:]              # 2番目から最後まで
var shortlist = longlist[2:2]           # 1個の要素からなるリスト
```

リストのアンパック

```
var [var1, var2] = mylist
```

変数の個数とリストの要素数が一致しないときはエラーになる。リストにある余分な要
素をまとめて受け取るには、";" と受け取る変数名を書いておく:

```
# 要素が 2 つしかないときでもエラーにはならない。"rest" は空リストになる。
var [var1, var2; rest] = mylist
```

リスト操作

```
var r = call(funcname, list)      # 引数リストをつけて関数を呼び出す
if empty(list)                    # リストが空かどうか判定する
var l = len(list)                 # リストの要素数
var big = max(list)               # リスト中の最大値
var small = min(list)             # リスト中の最小値
var xs = count(list, 'x')         # 'x' の出現回数を数える
var i = index(list, 'x')          # 最初に 'x' が現れる位置のインデックス
var lines = getline(1, 10)        # バッファから10行を取得
call append('$', lines)           # バッファに行を追加する
var list = split("a b c")         # 文字列を分割してリストにする
var string = join(list, ', ')     # リストの要素を連結して文字列にする
var s = string(list)              # リストの文字列表現
call map(list, '">> " .. v:val')  # 各要素の前に ">> " をつける
```

# 辞書

```
var mydict = {1: 'one', 2: 'two', 3: 'three'}
var emptydict = {}
var nestdict = {1: {11: 'a', 12: 'b'}, 2: {21: 'c'}}
```

キーに対してループ

```
for key in keys(mydict)
   echo key .. ': ' .. mydict[key]
endfor
```

キーでソートした順にループ

```
for key in sort(keys(mydict))
   echo key .. ': ' .. mydict[key]
endfor
```

値に対してループ

```
for v in values(mydict)
   echo "value: " .. v
endfor
```

キーと値の両方でループ

```
for [key, value] in items(mydict)
   echo key .. ': ' .. value
endfor
```

辞書から要素を取り除く

```
var i = remove(dict, 'aaa')
unlet dict.aaa
unlet dict['aaa']
```

辞書操作関数

```
if has_key(dict, 'foo')           # 辞書がキー "foo" の要素を持つなら真
if empty(dict)                    # 辞書が空なら真
var l = len(dict)                 # 辞書の要素数
var big = max(dict)               # 辞書中の最大値
var small = min(dict)             # 辞書中の最小値
var xs = count(dict, 'x')         # 'x' の出現回数を数える
var s = string(dict)              # 辞書の文字列表現
call map(dict, '">> " .. v:val')  # 各要素の前に ">> " をつける
```

# Blob

```
var b = 0zFF00ED015DAF
var b = 0zFF00.ED01.5DAF        # 読みやすくするために、ドットをバイト間(16進文字のペア)に挿入
var b = readfile('image.png', 'B')
```

Blobのインデックス

```
var myblob = 0z00112233
var byte = myblob[0]           # 1番目のバイトを取得: 0x00
var byte = myblob[2]           # 3番目のバイトを取得: 0x22
var last = myblob[-1]          # 最後のバイトを取得: 0x33
var byte = get(myblob, idx)    # 無効なインデックスに対するエラーを回避
```

Blobの繰り返し

```
for byte in 0z112233
   call Doit(byte)
endfor
```

Blobの連結

```
var longblob = myblob + 0z4455
var myblob += 0z6677
```

# 関数

`def[!] {name}([arguments])[: {return-type}]`

- {name} : 100バイト未満
- {reeturn-type} :  省略、あるいは `void` である場合は、何も返さないと想定される
- {arguments} : 書式は以下の3つ
  * `{name}: {type}`
  * `{name} = {value}`
  * `{name}: {type} = {value}`
- `def` : 最大 50 階層のネストが可能
- `[!]` : `!` を付けると既存の関数を上書きする

`enddef`

- 関数の終了

関数定義

```
def Add(x: number, y: number): number
  return x + y
enddef
```

# メソッド呼び出し

```
expr10->name([args]) 
```

これは次と同じである:

```
name(expr10 [, args])
```

あるメソッドが返す値を次のメソッドに渡して連鎖させることができる

```
mylist->filter(filterexpr)->map(mapexpr)->sort()->join()
```

ラムダの使用例:

```
GetPercentage()->{x -> x * 100}()->printf('%d%%')
```

# ラムダ式 （クロージャ）

```
def Foo(arg: number): func
  var i = 3
  return (x) => x + i - arg
enddef

var Bar = Foo(4)
echo Bar(6)       # 5
```

ラムダ式には {} に複数のステートメントを含むことができる:

```
var Lambda = (arg) => {
  g:was_called = 'yes'
  return expression
}
```

- いかなるコマンドも `{` の後ろに続けることはできない
- 閉じの `}` は行の先頭

ラムダとクロージャのサポートは以下のように判定できる:

```
if has('lambda')
```

# if - elseif - else - endif

```
var a = 5
if a == 5
  echo "5"
elseif a == 4
  echo "4"
else
  echo "others"
endif
```

# while - endwhile

```
let lnum = 1
while lnum <= line("$")
  let lnum = lnum + 1
  echo lnum
endwhile
```

# for - endfor

```
for item in [1, 2, 3, 4, 5]
  echo item
endfor
```

# ループ制御(break, continue)

```
var n = 0
while (true)
  n += 1
  if n == 2
    continue
  endif
  if n == 8
    break
  endif
  echo n
endwhile
```

# try - catch - finally - endtry

```
def Foo(value: any)
  try
    throw value
  catch /^\d\+$/
    echo "Number thrown"
  catch /.*/
    echo "String thrown"
  finally
    echo "finally"
  endtry
enddef

call Foo(0x1267)       # Number thrown
                       # finally
call Foo('string')     # String thrown
                       # finally
```