バッチファイルで複雑な数値計算をする - eval.js

# はじめに

この記事では、Windows のコマンドプロンプト（バッチファイル）で関数や算術演算子を使った計算ができるようになる eval.js を紹介します。

ソースは [github に公開](https://github.com/asterisk9101/wincmd/blob/master/scripts/eval.js)しています。

eval.js をコマンドとして実行する方法については、[別の記事](http://qiita.com/asterisk9101/items/7da60f8c8859d6e4dffe)を参照してください。

# eval の機能について

## 文法
eval [option]... EXPR...
eval [/?] [/help] [/v] [/version]
eval [/syntax] [/sample] [/function]

## 説明
eval は EXPR を評価した結果を出力します。
結果が true(または数値、文字列など) の場合は、%ERRORLEVEL% に 0 を設定します。
結果が false (または null, NaN) の場合は、%ERRORLEVEL% に 1 を設定します。
式の評価自体に失敗した場合は、%ERRORLEVEL% に 2 以上の値を設定します。

## オプション
<dl>
<dt>/i, /stdin</dt>
    <dd>標準入力を eval の変数 $ にセットします。</dd>

<dt>/f, /filter</dt>
    <dd>/i オプションと組み合わせて使用します。
    結果が TRUE になった場合だけ $ を出力します。</dd>

<dt>/s, /silent</dt>
    <dd>結果を出力しません。</dd>

<dt>/F</dt>
    <dd>eval をフィルタモードで実行します。/ifs を指定する場合と等価です。</dd>

<dt>/syntax</dt>
    <dd>eval の文法を表示して正常終了します。</dd>

<dt>/sample</dt>
    <dd>EXPR のサンプルを表示して正常終了します。</dd>

<dt>/function</dt>
    <dd>eval で使用できる関数の一覧を表示して正常終了します。</dd>

<dt>/?, /help</dt>
    <dd>ヘルプを表示して正常終了します。</dd>

<dt>/v, /version</dt>
    <dd>バージョン情報を出力して正常終了します。</dd>
</dl>

## 一般的な eval との差異
一般的な eval は、引数を1つにまとめてコマンドとして実行しますが、
この eval は Linux における expr , test または find として利用できます。

# eval で使用できる値

## 数値
小数を表現できます。

```bat
eval 1.5
=> 1.5
```

負数を表現できます。

```bat
eval -1
=> -1
```

単位(k, m, g)を付けることができます。

```bat
eval 1k
=> 1000
```

## 文字列
文字列はシングルクォートで括ります。

```bat
eval 'sample string'
=> sample string
```

文字列の結合ができます。

```bat
eval 'abc' + 'def'
=> abcdef
```

文字列が \ 記号を含む場合はエスケープが必要です。

```bat
eval 'C:\\Windows\\'
=> C:\Windows\
```

先頭に @ を付与することでエスケープ文字を ` 記号に変更することができます。

```bat
eval @'C:\Windows\'
=> C:\Windows\
```

エスケープで表現できる文字は以下の通り。

```
n     改行
r     復帰
t     水平タブ
'     シングルクォート
\     エスケープ文字自身(@ している場合は `)
uFFFF 16進数2バイト文字
```

`\u0022` のダブルクォートは頻繁に使うことになるでしょう。

## 真偽値
`true` と `false` の2種類です。大文字・小文字は区別されます。

## NULL
`null` の 1 種類です。今のところ想定される使い方はありません。

## 日付
日付は # で括ります。計算に使用できますが、
文字列の生成には向きません(mkdateコマンドを使ってください)。

```bat
eval "#2016/5/8#"
=> Sun May 8 00:00:00 UTC+0900 2016
```

## 演算
演算子の優先順位を正しく評価できます。

```bat
eval 1 + 2 * 3
=> 7
```

```bat
eval (1 + 2) * 3
=> 9
```

多くの演算子はコマンドプロンプトに解釈されてしまうので
EXPR は基本的にダブルクォートで括ると良いでしょう。

除算演算子は、コマンドへのオプションとなってしまうためクォートが必要です。

```bat
eval "10 / 7"
=> 1.4285714285714286
```

論理演算子は、コマンドプロンプトの式になってしまうためクォートが必要です。

```bat
eval "true && false"
=> false

eval "false || true"
=> true
```

比較演算子は、出力のリダイレクトになってしまうためクォートが必要です。

```bat
eval "100 > 10"
=> true
```

論理演算子は2種類(&, && または |, ||)ありますが、区別はありません。
また、両方ともショートサーキットです。

```bat
eval "file('abc.txt') & size('abc.txt') > 1k"
eval "file('abc.txt') && size('abc.txt') > 1k"
=> true(or false) 同じ結果になる。
                  また、'abc.txt' が存在しない場合 file() が false となるので、
                  size() は評価されない。よって size() でエラーは発生しない。
```

関数の詳細については /function オプションを参照してください。


# eval の使用例

## 条件分岐の可読性を高める
eval の関数を使用することで可読性の高い条件分岐を記述することができます。
eval は式が true となるとき環境変数 %ERRORLEVEL% に 0 をセットします。

```bat
:: 二つの条件を IF を入れ子にせず記述できる
eval "file(@'file.txt') && size(@'file.txt') > 1k"
IF %ERRORLEVEL% EQU 0 goto hogehoge

:: OR 条件の複数条件にも対応できる
eval "file(@'file.txt') || dir(@'folder')"
IF %ERRORLEVEL% EQU 0 goto hogehoge

:: 複雑な条件を複数行に分けて記述する場合(実行コストは高くなるでしょう)
eval "%ERRORLEVEL% == 0 && file(@'file.txt')"
eval "%ERRORLEVEL% == 0 && size(@'file.txt') > 1k"
eval "%ERRORLEVEL% == 0 && adate(@'file.txt') > #2016/05/10#"
eval "%ERRORLEVEL% == 0 && length(fullpath(@'file.txt')) <= 255"
IF %ERRORLEVEL% EQU 0 goto hogehoge
```

## 複雑な数値計算
eval は複雑な数値計算にも対応できます(JScriptの計算精度に依存します)。

```bat
eval "1 + 2 * 3 + (4 % 5) / pow(6, 7)"
=> 7.0000142889803385
```

計算結果の取得は、これまで通り FOR を使ってください。

## フィルタ
eval は標準出力のフィルタとして利用できます。

以下の例は、標準出力から読み込んだファイルとフォルダの一覧から
1000バイト以上のファイルだけを出力します。
$ は標準入力が代入される eval コマンド唯一の変数です。

```bat
dir /s /b | eval "file($) && size($) > 1k" /ifs
```

# 関数
## ファイルシステム
<dl>
<dt>file(STRING)</dt>
<dd>STRING で指定されたファイルが存在する場合は true を返します。</dd>

<dt>dir(STRING)</dt>
<dd>STRING で指定されたフォルダが存在する場合は true を返します。</dd>

<dt>empty(STRING)</dt>
<dd>STRING で指定されたファイルまたはフォルダが空の場合に true を返します。</dd>

<dt>size(STRING)</dt>
<dd>STRING で指定されたファイルのサイズを返します。</dd>

<dt>cdate(STRING)</dt>
<dd>STRING で指定されたファイルの作成日時を返します。</dd>

<dt>mdate(STRING)</dt>
<dd>STRING で指定されたファイルの更新日時を返します。</dd>

<dt>adate(STRING)</dt>
<dd>STRING で指定されたファイルのアクセス日時を返します。</dd>
</dl>

## 文字列
<dl>
<dt>length(STRING)</dt>
<dd>STRING で指定された文字列の長さを返します。</dd>

<dt>to_n(STRING)</dt>
<dd>STRING を数値に変換します。</dd>

<dt>slice(STRING, NUMBER1[, NUMBER2])</dt>
<dd>STRING を NUMBER1 文字目以降を切り出した STRING を返します。</dd>
<dd>NUMBER2 を指定すると、NUBMER1 から NUMBER2 文字目を切り出した STRING を返します。</dd>

<dt>indexof(STRING1, STRING2)</dt>
<dd>STRING1 の中に STRING2 があるか検索します。発見した位置(Number)を返します。</dd>
<dd>発見できなかった場合は -1 を返します。</dd>

<dt>upper(STRING)</dt>
<dd>STRING を大文字にして返します。</dd>

<dt>lower(STRING)</dt>
<dd>STRING を小文字にして返します。</dd>
</dl>

## 数学
<dl>
<dt>sqrt(NUMBER)</dt>
<dd>NUMBER の平方根を返します。</dd>

<dt>pow(NUMBER1, NUMBER2)</dt>
<dd>NUMBER1 を NUMBER2 のべき乗で返します。</dd>

<dt>round(NUMBER)</dt>
<dd>NUMBER を四捨五入した値を返します。</dd>

<dt>floor(NUMBER)</dt>
<dd>NUMBER の小数部を切り捨てた値を返します。</dd>

<dt>ceil(NUMBER)</dt>
<dd>NUMBER の小数部を切り上げた値を返します。</dd>

<dt>sin(NUMBER)</dt>
<dd>NUMBER の sin 値を返します。NUMBER はラジアンです。</dd>

<dt>cos(NUMBER)</dt>
<dd>NUMBER の cos 値を返します。NUMBER はラジアンです。</dd>

<dt>tan(NUMBER)</dt>
<dd>NUMBER の tan 値を返します。NUMBER はラジアンです。</dd>
</dl>

## 日付
<dl>
<dt>today()</dt>
<dd>システム日付を返します。</dd>
</dl>

# おわりに

日付計算できるようにしたい。