JavaScriptで数式解析パーサーを20行で作る方法

JavaScriptで数式解析パーサー作りたい。

  • 数式を解析評価できるパーサー
  • 例えば 5*(4/(2-1))+3  だったら
  • その評価値 23  を返すみたいな…

ググると色々出てきます。

逆ポーランド記法とか抽象構文木とか…

自力で実装するのも面白いですが、
より汎用的なパーサーを作る方法があります。

それが Rena.js というライブラリ

少し癖があるけど汎用性が高いので、
これで +-*/^()  に対応したパーサーを作りました。

Rena.jsをインストールまたは読込

下記で公開されているライブラリです。

▼ GitHub : y-moriguchi/rena2

▼ Node.jsならnpmからインストール

▼ ブラウザで読み込む場合

MITライセンスで配布されてます。

名前から分かるように旧バージョン Rena.js( https://github.com/y-moriguchi/rena-js )もあったのですが、そちらは廃止(obsolete)になってます。今はRena2.jsを使うのが推奨されてます。

▼ これを使えば色々なパーサーが作成可能

  • CSV・JSONパーサーとか、
  • 簡易的なコンパイラーとか、
  • 数式解析評価パーサーとか、

そういうのが手軽に作れるのが利点です。

Rena2.jsで数式パーサーを作ってみた

次の仕様で数式パーサーを作ります。

  • 数式には少数含む数値を含められる
  • 演算子は +-*/^()  に対応する
  • 数式を解析評価して1つの値で返す

一見すると複雑そうに見えますが
Rena2.jsなら十数行もあれば簡単に書けます。

▼ 数式パーサーのコードがこちら

▼ 数式パーサーに色々な式を渡してみた

初見だと意味不明だと思います(笑)
自分自身も初めて見た時そう思いました。

でも1つ1つかみ砕いていけば簡単です。

Rena2.jsで重要となるメソッド一覧

Rena.jsで構文解析が簡単な理由…

それは豊富な解析メソッドが用意されてるからです。

上記コードで使った主要メソッドをまとめます。

▼ 数式パーサーでは以下メソッドを多用

  • Rena2.letrec()

    引数に1つ以上の関数を渡し、その関数を再帰的に実行できるメソッド。最初に渡した関数の戻り値をreturnして返す。例えば4つの引数を渡したなら、引数の関数には (f1,f2,f3,f4)  のように自分自身を含む関数が渡される。例えば上記コードの次の部分

    ここでは4つの引数で+-*/^()を優先順位の高い順に処理している。もし優先度の異なる演算子を増やしたいなら、その分だけ関数引数が必要になる。

  • Rena2.then()
    複数の文字列(literal)や正規表現(regular expression)を結合するための関数。引数はいくつでも取れる。最も多用されるメソッドの1つ
  • Rena2.action()
    第1引数に正規表現または文字列、第2引数にその表現に対して実行する関数(ここなら四則演算+累乗+括弧展開)を定義する。第2引数の関数引数は (x, a, b)  のようになっているが、x はマッチした文字列、a は左辺のオペランド、b は右辺のオペランドを表す

主要なメソッドの役割はこういう感じ

基本的には上記メソッドの組み合わせです。

数式パーサーをBigIntに対応させる

自分的にはここからが本題です。

今作った数式パーサーをBigInt対応させます。

▼ BigIntとはなにか

BigInt 値は、単に BigInt と呼ばれることもありますが、bigint プリミティブです。整数リテラルの末尾に n を追加するか、BigInt() コンストラクターを呼び出し、整数値または文字列値を与えることで生成することができます(ただし new 演算子なしで)。

引用元 : https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/BigInt

▼ BigInt対応させた数式パーサー例

単純に Rena2.action()  で演算を実行する時、 BigInt(b) + BigInt(a) のようにBigIntでラップしてあげればいいだけです。

これでBigInt対応も完了

数式解析ライブラリを使うのもアリ

もちろんライブラリを使うという手もあります。

▼ 特に使いやすそうなライブラリ

上記ライブラリは変数にも対応してます。

こういうのも便利なんですが、たとえば「数式をBigIntとして解析してBigIntで評価したい」「独自演算子を定義したい」などの特殊なケースでは使えません。

そういう時はRena.jsが便利です。

Rena.jsは数式以外のパーサー作成に使える

このRena.jsは本当に汎用性が高いです。

ただ慣れるまでは少しクセがあるかも。
でも数式に限らず色々なパーサーが定義できます。

以上、JavaScriptで数式パーサーを作るでした。