コマンドプロンプト・プログラミング 三次会

# 目次

- [コマンドプロンプト・プログラミング 序](https://qiita.com/feelinguy/items/ce75348470b583b3ae42)
- [コマンドプロンプト・プログラミング 々](https://qiita.com/feelinguy/items/0b88db4aeb41bee44f19)
- [コマンドプロンプト・プログラミング 宴](https://qiita.com/feelinguy/items/ed91ed23e7baa63471a5)
- [コマンドプロンプト・プログラミング 二次会](https://qiita.com/feelinguy/items/02c70d56b2ed039db0ee)
- [コマンドプロンプト・プログラミング 三次会](https://qiita.com/feelinguy/items/f519549b0f9e8d2a714b) <- ★
- [コマンドプロンプト・プログラミング 壮行会](https://qiita.com/feelinguy/items/76dcc87020edbeb7dc14)

Lua と nkf をつなぐ会。

# CP932/UTF-8 相互変換

## パイプによる変換

nkf に `-w` を与えると UTF-8 を吐き出してくれる。
`-L` で改行コード変換。

- `-Lw`：Windows（CRLF）
- `-Lm`：Mac（CR）※ 最新 Mac は LF
- `-Lu`：Unix（LF）

`--guess` で文字コードを推測してくれる。

```cmd.exe
> echo あいうえお | nkf --guess
Shift_JIS (CRLF)

> echo あいうえお | nkf -w
縺ゅ＞縺・∴縺・

> echo あいうえお | nkf -w | nkf --guess
UTF-8 (CRLF)

> echo あいうえお | nkf -wLu | nkf --guess
UTF-8 (LF)

> echo あいうえお | nkf -wLu | nkf
あいうえお

> echo あいうえお | nkf -wLu | nkf | nkf --guess
Shift_JIS (LF)

> echo あいうえお | nkf -wLu | nkf -Lw | nkf --guess
Shift_JIS (CRLF)
```

### バッチファイルでラップする

`-w` と `--oc=UTF-8` は等価。

```sh:win2unix.cmd
@echo off
nkf.exe --ic=CP932 --oc=UTF-8 -Lu
exit /b
```
```sh:unix2win.cmd
@echo off
nkf.exe --ic=UTF-8 --oc=CP932 -Lw
exit /b
```
```cmd.exe
> echo あいうえお | win2unix | unix2win
あいうえお
```

## Lua から nkf を呼ぶ

### `os.execute([command])`

Lua で外部コマンドを呼ぶときにこれを使うが、
返り値は多値にて `boolean, 'exit'/'signal', errorCode` が返る。
本当に欲しいであろう値は標準出力として表示される。

```lua:lua.exe
> os.execute()
true  -- 外部コマンド実行可能

> os.execute("echo あいうえお| nkf --guess")
Shift_JIS (CRLF)   -- 標準出力
true    exit    0  -- 返り値（成功、exit、エラーコード）
```

この標準出力を Lua 内部で拾いたい。

### `io.popen(prog[, mode])`

これは別プロセスを作る関数で、返り値はファイルハンドルである。
パイプを作る関数と言った方が正しいか。

```lua:lua.exe
> file = io.popen("nkf.exe --guess", "w")  -- 書き込みモード
> file:write("あいうえお")
file (000007fefe412b10)
> file:close()
Shif_JIS
true    exit    0
>
> file = io.popen("nkf.exe --guess", "w"):write("あいうえお\n")
> file:close()
Shift_JIS (CRLF)
true    exit    0
```

（注意：Lua はメソッドチェーンを `:` で書く）
`os.execute` は `io.popen` で実装されていると予想できる。

ここでふと気づく。
結局どうやって nkf の出力を拾えばいいのかと。

というわけで、今度は読み込みモードを試してみる。
読み込みモードは `"r"` を指定するが、デフォなので無指定でもよい。

```lua:lua.exe
> file = io.popen("echo あいうえお")  -- 読み込みモード
> reply = file:read("a")             -- 全読み込み
> reply
あいうえお

> file:close()
true    exit    0
>
> file = io.popen("echo あいうえお")
> reply = file:read("l")             -- 行読み込み
> reply
あいうえお
> file:close()
true    exit    0
```

これでパイプからの出力を拾うことができた。
だが、両モード混在はできないようだ。

```lua:lua.exe
> io.popen("nkf.exe --guess", "rw")
nil     nkf.exe --guess: Invalid argument       22
```

というわけで、
「nkf に文字列をパイプで渡すコマンドライン」を
読み込みモードで呼び出すこととなる。

```lua:lua.exe
> file = io.popen("echo あいうえお| nkf.exe -wLu")
> reply = file:read("a")
> reply
縺ゅ＞縺・∴縺・

> file:close()
>
> file = io.popen("echo あいうえお| nkf -wLu | nkf --guess")
> reply = file:read("l")
> reply
UTF-8 (LF)
> file:close()
```

### 関数でラップする

```lua:cp932.lua
cp932 = {}  -- 名前空間

function cp932.utf8(s)
  -- 決して echo に空文字を与えてはいけない
  if s == "" then return s end
  -- echo が末尾の空白を拾うので '|' を左に詰めて書く
  local form = "echo %s| nkf.exe --ic=CP932 --oc=UTF-8 -Lu"
  local file = io.popen(string.format(form, s))
  local reply = file:read("a")
  file:close()
  return reply
end
```

`lua.exe -i cp932.lua` で REPL 起動時に読み込める。
もしくは、`dofile "cp932.lua"` と内部で読み込む。

```lua:lua.exe
> dofile "cp932.lua"
> cp932.utf8 "Hello Lua!"
Hello Lua!

> cp932.utf8 "あいうえお"
縺ゅ＞縺・∴縺・

> s = [[  -- ヒアドキュメント
0123456789
abcdefghij
]]
> cp932.utf8(s)
0123456789    -- oops!

```

複数行文字列を与えると一行目しか返ってこない。
`cp932.utf8` の内部では `echo %s` があり、
`string.format` によって `%s` を文字列に置換している。
この置換後のコマンドライン文字列を解釈するのは `cmd.exe` である。
こいつが改行以降を捨てているのだ。

ここで Lua の外側で新たなコマンドを作ることは考えない。
バッチファイルが面倒なので Lua を導入しているのだから。
`echo` は一行の文字列なら問題ないと割り切る。

### 文字列を配列に変換する

Lua は CP932 を認識しないが、
文字列が単なるバイト列なので、どれが改行コードなのかは判定できる。
まずは、文字列を「バイトの配列」に変換してみる。

```lua:cp932.lua
-- 標準ライブラリ string にメソッドを追加
function string:bytes() -- タイプ１
  local bytes = {}
  for i = 1, #self do
    table.insert(bytes, self:byte(i))
  end
  return bytes
end

function string:bytes() -- タイプ２
  return {self:byte(1, -1)}
end

-- 標準ライブラリ table に関数を追加
function table.each(t, f)
  for k, v in pairs(t) do f(k, v) end
end
```
```lua:lua.exe
> dofile "cp932.lua"
> s = [[
あ
い
]]
> table.each(s:bytes(), print)
1       130  -- あ
2       160  -- あ
3       10   -- LF
4       130  -- い
5       162  -- い
6       10   -- LF
> s = "あ\r\nい\r\n"
> table.each(s:bytes(), print)
1       130  -- 一行目
2       160  -- 一行目
3       13   -- CR
4       10   -- LF
5       130  -- 二行目
6       162  -- 二行目
7       13   -- CR
8       10   -- LF
```

さぁ、どうする？とは考えない。
欲しいものは何か？と考える。
欲しいものは「行ごとの文字列の配列」である。

### 関数型パラダイム

「行ごとの文字列」とは何か？
そもそも「行」とは何か？

行：改行コード/ヌル文字で終わる文字列
改行コード：LF(0x0A), CR(0x0D), CRLF(0x0D0A)

１バイトずつ見ていって、改行コード/ヌル文字があったならば、
「それまで見てきたバイトを結合したもの」が欲しいのである。

もっと抽象的に言うと、畳み込まれた文字列が欲しいのである。

```lua:cp932.lua
function string:reduce(acc, f)
  for i = 1, #self do
    acc = f(acc, self:byte(i))
  end
  return acc
end
```

`string.bytes` のタイプ１にそっくりである。
これにて別タイプの `string.bytes` を書ける。

```lua:cp932.lua
function string:bytes() -- タイプ３
  return self:reduce({}, function(bytes, byte)
    table.insert(bytes, byte)
    return bytes
  end)
end
```

本題に戻って、
「欲しいものは何か？」と考えるので、述語を作るのは重要だ。
これは静的型付け言語で型（クラス）を作ることと同じだ。

```lua:cp932.lua
local function is_newline(byte)
  return byte == 0x0A or byte == 0x0D
end

local function is_crlf(prev, current)
  return prev == 0x0D and current == 0x0A
end

local function is_eol(byte)
  return byte == 0x00
end

function string:lines()
  if self == "" then return {} end

  local buf, prev = "", nil
  local s = self .. string.char(0x00)
  return s:reduce({}, function(lines, byte)
    if is_eol(byte) then
      table.insert(lines, buf)
      return lines
    end
    -- ヌル文字以外の場合
    if is_newline(byte) then
      if not is_crlf(prev, byte) then
        table.insert(lines, buf)
      end
      buf = ""
    else -- 改行コード以外
      buf = buf .. string.char(byte)
    end
    prev = byte
    return lines
  end)
end -- string.lines
```
```lua:lua.exe
> dofile "cp932.lua"                               
> table.each(string.lines "", print)
> table.each(string.lines "abc", print)
1       abc
> table.each(string.lines "abc\n", print)
1       abc
2
> mixed = "あ\r\nい\n\rう"
> mixed
あ
い
う
> table.each(mixed:lines(), print)
1       あ
2       い
3
4       う
```

Lua は `\n\r` (LFCR) を１つの改行として扱っているようだ。
`string.lines` では２つの改行として扱うことにした。
改行コードの混在は不要な混乱を招くので気をつけよう。

### `cp932.utf8(string)`

やっとこ、相互変換の片道が完成。

```lua:cp932.lua
cp932 = {}

local function win2unix(s)
  if s == "" then return s end
  local form = "echo %s| nkf.exe --ic=CP932 --oc=UTF-8 -Lu"
  local file = io.popen(string.format(form, s))
  local reply = file:read("l")
  file:close()
  return reply
end

function cp932.utf8(s)
  if s == "" then return s end
  local utf8s = {}
  for _, line in ipairs(s:lines()) do
    table.insert(utf8s, win2unix(line))
  end
  return table.concat(utf8s, "\n")
end
```
```lua:lua.exe
> dofile "cp932.lua"
> #(cp932.utf8 "")
0
> #(cp932.utf8 "\n")
1
> #(cp932.utf8 "\r\n")
1
> #(cp932.utf8 "\n\r")
2
> #(cp932.utf8 "あいうえお")
15
> cp932.utf8 "Hello 世界！"
Hello 荳也阜・・
> s = [[
0123456789
abcdefghij
]]
> cp932.utf8(s)
0123456789
abcdefghij

```

# 次回

残すは Lua 内部での UTF-8 -> CP932 変換。
`echo` で nkf にどうやって UTF-8 を渡すか？
またも面倒そうである。

- [コマンドプロンプト・プログラミング 壮行会](https://qiita.com/feelinguy/items/76dcc87020edbeb7dc14)