多言語対応&著作権フリーの式計算ライブラリ「 Exevalator(エグゼバレータ)」をリリース

RINEARNではこの度、Java®/C#/C++/Rust製のソフトウェア開発で利用できる、オープンソース&著作権フリーの式計算ライブラリ 「 Exevalator(エグゼバレータ、式の計算/評価器を意味する Expression Evaluator の略)」をリリースしました。

今回のお知らせでは、この Exevalator の詳細をお知らせします。また、記事後半では、実際に少し使ってみるための導入ガイドも行っています。

- 目次 -

Exevalator とは?

まずはじめに、Exevalator は単体で直接使用するソフトウェアではなく、他のソフトウェア/アプリを開発する際に部品として用いる、「 ライブラリ 」というものの一つです。 そのため、直接の想定ユーザーは、仕事や趣味などでプログラミングを行い、何らかのソフトウェア/アプリを開発されているような方々となります。

Exevalator の用途

それでは本題に入りましょう。唐突ですが、ソフトウェアを開発している際に、「 文字列変数などに格納された式の値を計算(評価)したい 」 といった場面に出会った事はありませんか?

ここでの「 式 」とは、例えば以下のようなものなどです:

1 + 2
(1.2 + 3.4) * 5.6
x + f(y)

こういった式の計算処理は、電卓のような計算系のアプリはもちろん、式のグラフを描きたい場面、データに対してユーザー定義の変換処理を行いたい場面などで必要となります。

このような式をユーザーに入力(またはファイルなどに記述)してもらうような処理を実装すると、通常、ソフトウェア/アプリ側からは、それを文字列として受け取る事になります。 従って、「 文字列に式が入っていて、その値を計算したい 」といった場面が生じるわけです。

一方で、大抵のコンパイラ型の(スクリプト言語ではない)言語では、そのような機能は、標準ではサポートされていません。 そのため、式の構文解析/計算処理を自力で実装するか、そのような機能を提供するライブラリを使用する必要があります。

Exevalator は、 実際にそのような機能を提供するためのライブラリです。

多言語対応

Exevalator は複数の言語に対応していて、現在は以下の言語でのソフトウェア開発に利用可能です。

  • Java (Java 8 以降が必須条件、本記事時点で最新の 18 も対応済み)
  • C#   (C# 8.0 以降推奨)
  • C++ (C++17 以降必須)
  • Rust  (1.57.0 以降推奨)
  • 規模や構造的に、移植が比較的容易なため、将来的にはさらに他の言語に対応する可能性もあります。 実際に、開発の際は最初に Java 版が完成し、C# → Rust → C++ へと移植したという経緯を辿っています。

    コンパクトで簡単に導入できる

    Exevalator は、各言語版ごとに、それぞれたった一枚のファイル(C++のみヘッダと実装の二枚)として実装されている、コンパクトなライブラリです。

    開発しているソフトウェアのソースコードフォルダに、単に Exevalator のソースファイル一枚を放り込むだけで、すぐに使う事ができます。ややこしい導入の手続きはほぼ必要ありません (さすがにコンパイル/ビルド対象に含める指定は必要ですが、それは普通にソースファイルを一枚追加する場合と全く同じです)。

    ライセンスは実質的な著作権フリーである「Unlisense」と「CC0」から選択可能

    ライセンスについても、色々な条件に注意を払ったりする必要はありません。というのも Exevalator は、実質的な著作権フリー(パブリックドメイン)宣言である「 Unlisense 」に基づいて配布されているからです。

    また、ユーザーの希望に応じて、Unlicense の代わりに CC0 を選ぶ事もできます。 こちらも実質的なパブリックドメイン宣言ですが、細部が微妙に異なります。好きな方を選んでください。

    ソフトウェアを公開/販売する際などに「 Exevalator を使った 」と明記する必要はありませんし、Exevalator 自体のソースコードの改造や流用なども、基本的に何も気にせず自由に行えます。

    リリース当初は CC0 のみのライセンスでしたが、CC0 がオープンソースライセンスとしては難があるとの情報を(Twitter 上で)頂いたため、Ver.1.0.4 より Unlisense と CC0 のデュアルライセンスに改めました。

    変数や関数も定義可能

    用途によっては、「 x + 2 」などのように変数を用いた式を、変数 x の値を設定したり変更したりしながら計算したい場合もあると思います。 Exevalator では、何個でも自由な名前の変数を作成できます。

    また、「 x + f(y) 」などのように、式の中で使える関数を作成したい場合もあると思います。 こちらも、何個でも自由な名前と処理内容の関数を作成できます。

    手軽な割に処理が高速

    処理速度の面でも、Exevalator は比較的高速に動作します。

    具体的には、同じ計算式を、変数の値を変えながら繰り返し実行するベンチマーク(リポジトリ内に同梱)において、一般的なPC上において最大で数億回演算/秒(数百MFLOPS)程度のパフォーマンスを発揮します。

    これは、それなりに大量のデータに変換をかけたり、式の値を密な地点でサンプリングしたりする場面においても、十分現実的に使える水準です。 コードがファイル 1 枚のみという点からすると、手軽さと性能のバランスが良好なライブラリに仕上がっていると思います。

    Vnano との立ち位置の違い

    ところで RINEARN では、同じようにソフトウェア/アプリ内に組み込んで使う処理系として、スクリプトエンジンの「 Vnano(ブイナノ) 」も開発/公開しています。

    この Vnano を使用しても、先述のような式の計算は可能です。 従って Exevalator は、既にある Vnano と、ある程度は用途が重複するライブラリになります。 しかしこれは、どちらかが全面的に優れているというわけではなく、「 Exevalator がコンパクト&組み込みやすさを最優先した方向、Vnano はそれよりもやや高機能/高速に振った方向 」に軸足を置いていて、立ち位置が微妙に違います。

    ※ 元々は Vnano のコンセプト自体が、上記の全てを満たす事を目指していました。 しかし、やはり機能量・速度とコード規模の間には、どうしても大きなトレードオフがあり、2つに分派させて使い分けた方が良いという判断から、今回の Exevalator の開発に繋がっています。 具体的には、「 たまにしか使わない機能や処理速度域のためのコードほど、全体の中で大きな割合を占め、移植やメンテナンスの障壁になる 」という、よくあるジレンマに対応するための分派です。

    具体的な両者の違いとしては、以下の表のようにまとめられます(青/赤の着色は、相対的な有利/不利を表しています):

    特徴\ライブラリ Exevalator Vnano
    式の計算
    搭載アプリ開発側での変数/関数定義
    アプリユーザー側での変数/関数定義 ×
    分岐や繰り返しを含む複雑な処理
    (スクリプト実行)
    ×
    扱えるデータ型 64bit浮動小数点数のみ 64bit浮動小数点数,
    64bit符号付き整数,
    文字列,
    真偽値
    配列の使用 ×
    一般的なPCにおける処理速度目安
    (理想条件下のピーク値)
    約 4億演算/秒
    ※ 非配列、Java 版での値
    (CPU: Core i7-1165G7
    非配列演算: 約 7億演算/秒,
    配列の演算: 約 150億演算/秒
    (CPU: Core i7-1165G7
    対応するアプリの開発言語 Java, C#, C++, Rust 本記事時点では Java のみ
    (将来的には移植の可能性あり)
    ライブラリの構成物 単一のソースファイル 多数のソースファイル
    ライブラリの総コード規模 各言語版それぞれ千数百行ほど 数万行
    アプリへの組み込み方法 アプリのソースコードフォルダ内に1枚を放り込むだけでほぼ完了 ビルドしてJARファイル化し、アプリ側からクラスパスを通して接続
    処理系の内部構造 ASTインタープリタ
    » 詳細構造
    中間コードコンパイラ+VM
    » 詳細構造
    ライセンス Unlicense または CC0 から選択 MITライセンス

    以上の通りです。

    上の表をラフにまとめると、ユーザーによる自由度やカスタマイズ性の高いアプリを作りたい場合は Vnano が有利で、単に式を計算できればいいという場合は Exevalator が有利、といった方向性になります。 (ただ、Vnano はまだ Java 言語にしか対応していないめ、他の言語で使う場合は、現状では Exevalator しか選べません。)

    実際に使用してみる

    Exevalator の紹介については以上です。 ここからは、実際に各言語で Exevalator を使用してみましょう。

    コードの入手

    まずは、Exevalator のコードを入手します。これは GitHub 上の、以下のページから入手できます:

    Exevalator ソースコードリポジトリ(GitHub)、リリース版配布ページ
    https://github.com/RINEARN/exevalator/releases

    上記ページから、各バージョンの(特に理由が無ければ最新版の)項目にある、"Source code (zip)" のリンクを選択します。

    すると「 exevalator-1.0.0 」などの名前のZIPファイルをダウンロードできるため、これを右クリックして「 すべて展開 」などを選んで展開(解凍)してください。

    中には、README やサンプルコード等が色々と入っていますが、ソフトウェア/アプリ開発の際に使用する Exevalator の本体は、以下の通りです:

    Java言語で使用する場合
    「java」フォルダ内にある Exevalator.java が本体です。
    C#で使用する場合
    「csharp」フォルダ内にある Exevalator.cs が本体です。
    C++で使用する場合
    「cpp」フォルダ内にある exevalator.cpp が本体の実装、exevalator.hpp がヘッダです。
    Rustで使用する場合
    「rust」フォルダ内にある exevalator.rs が本体です。

    以下では、実際に各言語で使用してみます。

    Java言語で使用する

    Java で使う場合は、まず先述の「 Exevalator.java 」を、計算を行いたいクラスのソースファイルと同じフォルダ内に配置してください。必要に応じてビルド対象に登録なども行います(使用しているIDEやビルド環境依存で、普通に新しいソースファイルを追加する場合と同様です)。

    続いて、Exevalator.java 内の先頭で、配置場所に応じたパッケージ宣言を記述します。その内容は、計算を行いたいクラス(同フォルダ内)のソースファイル内の先頭付近にある「 pakage 〜 」の行と同じでOKなはずです。 例えばここでは、以下のようなパッケージ宣言になったとします:

    (Exevalator.java内の先頭)
    package myapp;
    ...

    あとは、計算を行いたいクラスのソースファイル内から、import して以下のように計算を行えます:

    (計算を行いたいクラスのソースファイル内)
    ...
    import myapp.Exevalator;
    ...

    ...
    Exevalator exevalator = new Exevalator();   // Exevalatorのインタープリタを生成
    double result = exevalator.eval("1.2 + 3.4");   // 式の値を計算
    System.out.println("result: " + result);   // 結果を表示
    ...

    なお、式の内容がおかしい場合など、エラーが発生した際には Exevalator.Exception 例外がスローされるため、必要に応じて catch してハンドルしてください。

    以上です。より詳しい説明や、サンプルコードのビルド/実行例などは、リポジトリ内のJava用READMEで解説しています。

    C#で使用する

    C# で使う場合は、まず先述の「 Exevalator.cs 」を、プロジェクト内のソースコードフォルダ内に配置してください。必要に応じてビルド対象に登録なども行います(使用しているIDEやビルド環境依存で、普通に新しいソースファイルを追加する場合と同様です)。

    あとは、計算を行いたいソースファイル内から、using して以下のように計算を行えます:

    (計算を行いたいソースファイル内)
    ...
    using Rinearn.ExevalatorCS;
    ...

    ...
    Exevalator exevalator = new Exevalator();   // Exevalatorのインタープリタを生成
    double result = exevalator.Eval("1.2 + 3.4");   // 式の値を計算
    Console.WriteLine("result: " + result);   // 結果を表示
    ...

    なお、式の内容がおかしい場合など、エラーが発生した際には ExevalatorException 例外がスローされるため、必要に応じて catch してハンドルしてください。

    以上です。より詳しい説明や、サンプルコードのビルド/実行例などは、リポジトリ内のC#用READMEで解説しています。

    C++で使用する

    C++ で使う場合は、特に行儀作法などに厳格でないプロジェクトでは、ヘッダ「 exevalator.hpp 」と実装「 exevalator.cpp 」の両方を、計算を行いたいソースファイルと同じフォルダ内に放り込んでください。

    そして、計算を行いたいソースファイルから、include して以下のように計算を行えます:

    (計算を行いたいソースファイル内)
    ...
    #include "exevalator.hpp"
    #include "exevalator.cpp"
    ...

    ...
    exevalator::Exevalator exevalator; // Exevalator のインタープリタを生成
    try {
        double result = exevalator.eval("1.2 + 3.4"); // 式の値を計算
        std::cout << "result: " << result << std::endl; // 結果を表示

    // エラーが発生した場合
    } catch (exevalator::ExevalatorException &e) {
        std::cout << "Error occurred: " << e.what() << std::endl;
    }
    ...

    以上です。より詳しい説明や、サンプルコードのビルド/実行例などは、リポジトリ内のC++用READMEで解説しています。

    Rustで使用する

    最後に、Rust で使う場合です。

    ※ ここまでの言語と比べると Rust は新しい言語で、「 Rust って何?」って思われる方が多いかもしれないため、少しだけ Rust について触れておきましょう。

    Rust は、Firefox ブラウザで有名な Mozilla が深く関わっている言語で、VM等を介さず直接走るコードを、CやC++よりもかなり安全に記述できる言語です。 そのための新概念や制約も多く、記述がやや難しめです(雰囲気的には、C++で unique_ptr オンリー縛りで書くのが近い気がします)。

    しかし、近年の脆弱性に振り回され続ける世界の実情からすると、少なくともセキュリティが最重要なカテゴリーの開発においては、やがてこのような言語が中心になっていくのかもしれません。 Mozilla が開発しているブラウザは、まさにそういったカテゴリーなので、このような言語を必要とするのもうなずけます。

    まず、「 exevalator.rs 」を、ソースコードフォルダ内の直下(ルート)に放り込んでください。 もし別の場所に配置する場合は、mod 宣言などを適切な形/場所で行う必要があります。

    そして、ルートファイル( main.rs など)から mod 宣言を行います:

    ...
    mod exevalator;
    ...

    あとは、計算を行いたいソースファイル内で、以下のように使用できます:

    (計算を行いたいソースファイル内)
    ...
    use exevalator::Exevalator;
    ...

    ...
    // Exevalator のインタープリタを生成
    let mut exevalator = Exevalator::new();

    // 式の値を計算
    let result: f64 = match exevalator.eval("1.2 + 3.4") {
        Ok(eval_value) => eval_value,
        Err(eval_error) => panic!("{}", eval_error),
    };

    // 結果を表示
    println!("result: {}", result);

    以上です。より詳しい説明や、サンプルコードのビルド/実行例などは、リポジトリ内のRust用READMEで解説しています。

    今回のお知らせは以上です。Exevalator 関連の続報については、今後もこのコーナーで随時お知らせしていきます。 オープンソースのライブラリであるため、内部のソフトウェアアーキテクチャに関する解説なども行う予定です。