Zenn
👻

pythonっぽい計算式を実行できるRustのパッケージをnomで作ってみた

2024/12/25に公開

この記事はRust Advent Calendar 2024の25日目の記事です。

はじめに

Pythonっぽい文法で書かれた数式や簡易なラムダ式を、Rustで評価することができる軽量なパッケージを作りました。"Pythonっぽいeval"ということでpyishevalという名前で公開しています。

https://github.com/neka-nat/pyisheval

なぜ作ったか?

主に設定ファイルなどで、ユーザーに動的な数式や簡易スクリプトを記述させたい場面があります。たとえば、

  • CI/CDツール内で簡易な条件式をユーザーに書かせたい
  • DSL(ドメイン固有言語)のような軽量スクリプトを埋め込みたい
  • テンプレートファイルに算術式やフィルタ式をそのまま書いて評価したい

このようなケースで新たな文法の式評価器を作ってもよいのですが、私がPythonに慣れてるのもあって「もういっそPythonみたいな式を書かせて評価できたらな...」とよく思ってしまいます。
しかし、本物のPythonインタプリタを埋め込むと依存関係やビルド環境が複雑になるし、PyO3などを使うのも大げさです。
もっと軽量で、スッと組み込み可能な、「Pythonっぽい記法で書ける式評価器」 が今回作ったpyishevalです。

類似パッケージ

evalexpr

おそらくrustで一番人気のevalパッケージです。
AGPLなのでライセンスに注意です。
https://github.com/ISibboI/evalexpr

exmex

evalexprより速いっぽい。
https://github.com/bertiqwerty/exmex/

何ができるか

  • Pythonっぽい文法のサブセットをサポート
    • 算術演算子: +, -, *, /, //, %, **
    • 比較演算子: >, <, >=, <=, ==, !=
    • 三項演算子: x if x > 0 else y
    • 変数代入: x = 10
    • ラムダ式: inc = lambda a: a + 1
    • 組み込み関数: abs, max, min, int, float, len, sum, round
    • リスト、タプル、セット、辞書リテラル、そしてリスト内包表記: [y*2 for y in x]
    • 文字列、リスト、辞書のメソッド: x.lower(), x.append(y), x.items(), ...
  • 制御構文やクラス、関数定義(def)、importなどは未サポート
  • シンプルさ重視。あくまで「式」ベースです。
  • パーサーはRust製のパーサコンビネータnomを利用し、ASTを構築して評価

実際のサンプルコードとしては以下のようになります。

use pyisheval::Interpreter;

fn main() {
    let mut interp = Interpreter::new();

    // Assign variables
    interp.eval("x = 10").unwrap();
    interp.eval("y = 20").unwrap();

    // Arithmetic
    let val = interp.eval("x + y * 2").unwrap();
    println!("{}", val); // 50

    // Lambda
    interp.eval("inc = lambda a: a + 1").unwrap();
    let val = interp.eval("inc(x)").unwrap();
    println!("{}", val); // 11

    // Conditional expression
    let val = interp.eval("x if x > y else y").unwrap();
    println!("{}", val); // 10

    // List comprehension
    let val = interp.eval("[y * 2 for y in x]").unwrap();
    println!("{}", val); // [2, 4, 6, 8, 10]

    // Dict comprehension
    let val = interp.eval("{y: y * 2 for y in x}").unwrap();
    println!("{}", val); // {1: 2, 2: 4, 3: 6, 4: 8, 5: 10}

    // String method
    let val = interp.eval("'hello'.upper()").unwrap();
    println!("{}", val); // HELLO

    // List method
    interp.eval("x = [1, 2, 3]").unwrap();
    let val = interp.eval("x.append(4)").unwrap();
    println!("{}", val); // [1, 2, 3, 4]
}

Todo

  • builtin関数を増やす。(sort, byte, print,...まだまだいっぱいある)
  • importできないとはいえ、math系の関数はサポートしたいかも。(sin, cosとか)

ほぼほぼAIで作りました

このパッケージですが、ほぼほぼAIに作ってもらいました。
どれくらいAIなのかと言うと、ベースの構成や枠組みをまずはAIに考えてもらって、機能追加はuithubを使って、o1に現状のコードと追加したい機能についての説明を与えて出力してもらうというのを繰り返して作成しています。
細かいコンパイルエラーなどは手動で修正していますが、それもCursorを使って半自動的にやってます。
多分ですが、Devinとか使えば、イシューを書くだけでほぼほぼ自動で拡張されていくのではないかと思っています。
今回の「Pythonっぽい記法で書ける式評価器」というのは機能が明確でかなりAI向きなお題ではあるかなと思いますが、これまでいろいろOSSを作って公開してきて、このレベルのパッケージがほぼAIでできてしまうことにはかなり驚きです。

まとめ

ちょっと前まではこれくらいのパッケージを作るのに、それなりの手間を書ける必要が合ったと思うのですが、こういったちょっとした機能のパッケージをAIで作れるようになったのはすごいことですね。
今回の応用で、簡単なプログラミング言語であれば、仕様とか使い方を明確に定義すればほぼ自動でAIで作れるのではと思いました。
AIがこなせるタスクがどんどん広がってますね、これからもAIの力を借りていろいろと作っていきたいと思います。

Discussion

ログインするとコメントできます