スクラッチで作るnlコマンド


#きっかけ

V言語でcoreutilsをつくるプロジェクトを見つけました。

https://github.com/vlang/coreutils

nlは未実装だったので作ることにしました。

![スクリーンショット 2021-08-20 20.54.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/542542/32bfdcec-3a72-0cc0-a857-35422d98d3e8.png)

# V言語の特徴

- シンプルな構文（goっぽい）
- 自動メモリ安全を目指してるらしい（Rustっぽい）
- バイナリが小さい（Rustっぽい）
- 中間言語がC言語らしい

なのでC言語でできることは基本的にできるっぽいですね。

# vlang/coreutilsの要件

> Complete set of coreutils, written as closely as possible to the POSIX spec, with as many GNU extensions as feasible.

> We are looking for solid, working implementations of the commands, not 100% 1-to-1 parity, especially not quirks and unintended side-effects.

「POSIXで、できる限りGNU拡張で、余計なことはしない」ということですね。


# nlの実装方針

## とりあえず

- Vのライブラリは積極的に使う
- 原則POSIX、GNU拡張フルスペックにする
- 細かな実装差異はGNU版に合わせる

## 個人的にやりたいこと

- コンパイルオプションでBSD互換動作も網羅
 - これは **quirksな（癖のある）実装** ではない…はず

----

# 使い方

こちらを読んでください。

https://qiita.com/advent-calendar/2020/nl

# コンパイル

## GNU互換

````bash
$ v nl.v
````

## BSD互換

````bash
$ v -d all_line_separator=1 -d remove_delimiter_line=1 -d one_delimiter_posix=1 -d fixed_width=1 nl.v
````

# 実装の差異

`nlg`がGNU互換バーションで、`nlb`がBSD互換バージョンです。

## all_line_separator

````bash
$ echo '' | ./nlg -s @
       
$ echo '' | ./nlb -s @
      @
````

https://qiita.com/Carol_815/items/0e7de6dd1fb3d821ec6e

## remove_delimiter_line

````bash
$ echo -e 'a\n\\:\nb' | ./nlg
     1	a

       b
$ echo -e 'a\n\\:\nb' | ./nlb
     1	a
      	b
````

https://qiita.com/Carol_815/items/d1f52df36b0d39b374b0

## one_delimiter_posix

````bash
$ echo -e 'a\n@:\nb\n@\nc' | ./nlg -d '@'
     1	a
     2	@:
     3	b

       c
$ echo -e 'a\n@:\nb\n@\nc' | ./nlb -d '@'
     1	a
      	b
      	@
      	c
````

https://qiita.com/Carol_815/items/401c327a1dbc7c63b7bb

## fixed_width

````bash
$ seq 2 | ./nlg -v 99 -w 2
99	1
100	2
$ seq 2 | ./nlb -v 99 -w 2
99	1
00	2
````

https://qiita.com/Carol_815/items/fa16e4875ba7bc19de86

# 既知の問題

## ヘルプの引数

````bash
  -i, --line-increment <string>
````

64ビット整数まで受け付けるオプション（-vとか-i）の引数の説明が`<string>`になっています。これはオプションを解析するvlibの`flag`ライブラリが、整数は`<int>`（=32ビット）しか対応していないためです。

（2021/8/30追記）
ヘルプに関しては、`val_desc`を設定することで引数の説明を変更できるようです。今度直そう。

## ヘルプのオプション

````
  -h, --help                display this help and exit
````
ヘルプによれば、`-h`と`--help`でヘルプが表示されるのですが、nlの`-h`はヘッダーのスタイル指定のオプションと重複します。これも`flag`の仕様によるもです。`-h a`など引数を渡せば、スタイル指定になりますので実害はないです。

## 正規表現

`-b pBRE`はPOSIXでは基本正規表現としていますが、今回はvlibの`regex`をそのまま利用しています。何正規表現をサポートするのかは調べてないです。

https://qiita.com/Carol_815/items/dc7d3a23d1ebc33bf1ba

## オプション処理

`nl -nln`と書くとエラーになります。`nl -n ln`とスペースをちゃんと入れましょう。これも`flag`の仕様です。

## 整数オーバーフロー

現状では行番号の符号反転で検出する環境依存の実装になっています。`INT64_MAX`みたいなリテラルがほしい…

## 性能

GNU版より10倍くらい遅い…遅い…
(`-prod`で2倍くらい速くなります。後述)

- 付番なし

````bash
#GNU
$ yes '' | head -n 10000000 | time gnl >/dev/null
gnl > /dev/null  0.52s user 0.01s system 98% cpu 0.534 total
$ yes '' | head -n 10000000 | time gnl >/dev/null
gnl > /dev/null  0.53s user 0.01s system 99% cpu 0.535 total
$ yes '' | head -n 10000000 | time gnl >/dev/null
gnl > /dev/null  0.53s user 0.01s system 99% cpu 0.536 total
#V
$ yes '' | head -n 10000000 | time ./nl >/dev/null
./nl > /dev/null  6.09s user 1.61s system 97% cpu 7.905 total
$ yes '' | head -n 10000000 | time ./nl >/dev/null
./nl > /dev/null  6.15s user 1.60s system 97% cpu 7.943 total
$ yes '' | head -n 10000000 | time ./nl >/dev/null
./nl > /dev/null  6.11s user 1.60s system 96% cpu 8.023 total
````

- 付番あり

````bash
#GNU
$ yes '' | head -n 10000000 | time gnl -b a >/dev/null
gnl -b a > /dev/null  0.96s user 0.01s system 99% cpu 0.976 total
$ yes '' | head -n 10000000 | time gnl -b a >/dev/null
gnl -b a > /dev/null  0.97s user 0.01s system 99% cpu 0.982 total
$ yes '' | head -n 10000000 | time gnl -b a >/dev/null
gnl -b a > /dev/null  0.96s user 0.01s system 99% cpu 0.981 total
#V
$ yes '' | head -n 10000000 | time ./nl -b a >/dev/null
./nl -b a > /dev/null  9.08s user 1.75s system 97% cpu 11.057 total
$ yes '' | head -n 10000000 | time ./nl -b a >/dev/null
./nl -b a > /dev/null  9.08s user 1.75s system 97% cpu 11.067 total
$ yes '' | head -n 10000000 | time ./nl -b a >/dev/null
./nl -b a > /dev/null  9.10s user 1.75s system 97% cpu 11.076 total
````

# 悩みどころ

## one_delimiter_posix

https://qiita.com/Carol_815/items/401c327a1dbc7c63b7bb#%E4%B8%8D%E5%85%B7%E5%90%88%E4%BF%AE%E6%AD%A3%E3%81%AE%E4%BD%99%E8%AB%87

現時点（2021/08/20）ではGNU coreutils v8.32が最新ですが、次版ではPOSIX互換に修正され1文字デリミタが使えなくなる見込みです。今回のVの実装ではPOSIX非互換のGNUの動作にあわせています。このPOSIXの仕様どう思いますか？

----

# 性能と `-prod` コンパイルオプション

（2021/08/29追記）コメント頂いて試してみました。
vは-prodオプションを利用してコンパイルすると、コンパイル時間は長くなりますが、速いバイナリが出てきます。
以下の結果は、現在実装されている23コマンドについてのコンパイル時間になります。あくまでも手元の環境（Macmini M1）ではこのくらいの時間かかりました。というご参考です。

## `-prod`あり

### コンパイル

````bash
$ time make prod
make prod  39.05s user 1.06s system 99% cpu 40.258 total
````

### 付番なし

````bash
$ yes '' | head -n 10000000 | time ./nl >/dev/null
./nl > /dev/null  2.38s user 1.47s system 95% cpu 4.035 total
$ yes '' | head -n 10000000 | time ./nl >/dev/null
./nl > /dev/null  2.37s user 1.47s system 95% cpu 3.999 total
$ yes '' | head -n 10000000 | time ./nl >/dev/null
./nl > /dev/null  2.37s user 1.47s system 95% cpu 4.013 total
````

### 付番あり

````bash
$ yes '' | head -n 10000000 | time ./nl -b a >/dev/null
./nl -b a > /dev/null  4.08s user 1.61s system 97% cpu 5.867 total
$ yes '' | head -n 10000000 | time ./nl -b a >/dev/null
./nl -b a > /dev/null  4.09s user 1.62s system 96% cpu 5.887 total
$ yes '' | head -n 10000000 | time ./nl -b a >/dev/null
./nl -b a > /dev/null  4.09s user 1.62s system 97% cpu 5.875 total
````

## `-prod`なし（ご参考）

````bash
$ time make 
make  7.78s user 0.69s system 98% cpu 8.620 total
$ yes '' | head -n 10000000 | time ./nl >/dev/null     
./nl > /dev/null  6.10s user 1.42s system 97% cpu 7.724 total
$ yes '' | head -n 10000000 | time ./nl -b a >/dev/null
./nl -b a > /dev/null  9.07s user 1.56s system 96% cpu 10.972 total
````

`-prod`コンパイルで時間は5倍弱かかりますが、処理速度は2倍程度に改善したようです。なかなかいいですね。

----