【Rust】TUI電卓自作,電卓が「言語」になった日:if文の実装と再帰関数によるフィボナッチ計算 #9

前回までで、私たちは「計算の木(AST)」を作り、過去の履歴を参照する仕組みを整えてきました。しかし、まだ最後のピースが欠けていました。それが 「条件分岐」「関数の動的展開」 です。

今回は、この電卓に「知能」を与える最後の評価ロジックを実装し、ついに 再帰関数(フィボナッチ数列など) の実行に成功しました。

前回 履歴参照機能の実装: 【Rust】計算機:過去の計算を再利用!履歴参照($n)の実装とファイルの永続化 #8

計算機のコア部分、評価機を実装していきます

今回は評価(計算部分)のうち関数の評価の実装です。

1. 設計と実装

関数(文字列)としてパースされたものを受け取り、if文は特殊なので先に処理し、その後、自作関数や定数等に該当するか判定してそれぞれ処理するように実装しました。

1.1 if文の処理

if文は自由記述に近い拡張性を持たせてもいいですが、設計が大変なのと計算機にそこまでの機能がいるか?という疑問もあるので、3引数の基本的な機能を実装することにしました。

evaluateにif文の判定文などを渡しつつ、スタックオーバフロー対策の深さ(depth)を渡して再帰的に処理させます。

1.2 定義した関数と定数の処理

tomlから読み込みあらかじめ構造木(AST)にして置いた文字列辞書に今回来た関数らしき文字列が存在するかを判定しています。

# あらかじめ展開した関数 defs.parsed_functions を参照して判定
Some(func) = defs.parsed_functions.get(name)

1.3 ソースコード抜粋

evaluateの関数の処理部分です。

# 関数呼び出しとif文の処理
Expr::FunctionCall { name, args } => {
    if name == "if" {
        if args.len() != 3 {
            return Err("if() requires 3 arguments: (cond, then, else)".into());
        }
        let cond = evaluate(&args[0], defs, history, local_env, depth + 1)?;
        if cond != 0.0 {
            evaluate(&args[1], defs, history, local_env, depth + 1)
        } else {
            evaluate(&args[2], defs, history, local_env, depth + 1)
        }
    } else if let Some(func) = defs.parsed_functions.get(name) {
        if args.len() != func.args.len() {
            return Err(format!("{}() expected {} args, got {}", name, func.args.len(), args.len()));
        }

        // 型推論エラー(E0282)を避けるため、型を明示的に指定しました。(もっといい方法があるかも?)
        let mut new_env: HashMap<String, f64> = HashMap::new();
        for (arg_name, arg_expr) in func.args.iter().zip(args) {
            let val = evaluate(arg_expr, defs, history, local_env, depth + 1)?;
            new_env.insert(arg_name.clone(), val);
        }

        evaluate(&func.ast, defs, history, &new_env, depth + 1)
    } else {
        Err(format!("Unknown function: {}", name))
    }
}

2. 実行テスト

自分で定義した(conv()とfib())が動くかテストしてみました。 convはDeeplearningの畳み込み層のノード数計算をカーネルサイズと入力サイズから計算するような関数で、fibはフィボナッチ数列を返す関数です。

#convの定義
conv = { args = ["W", "K"], expr = "W - K + 1" }

# fib(フィボナッチ)の定義
fib = { 
    args = ["n"], expr = "if(n < 2, n, fib(n-1) + fib(n-2))",
    memoize = true, 
    max_depth = 20 
}
関数機能を実装し動作チェックしたときの計算結果の履歴

計算結果の履歴

実行結果を見れば、convの計算、conv(20,4) → 20 – 4 +1 = 17ができていることが確認できます。

また、再帰計算のテスト用に作ったfib関数に10を入れると、フィボナッチ数列の10番目(55)が帰ってきていることも確認できました。

さいごに

ここまでで、電卓としての基本機能の概形はすべて完成しました。 文字列が「意味」を持ち、条件によって「判断」し、自分自身を呼び出して「成長」する。たった数百行のRustコードですが、そこにはコンピュータサイエンスの凝縮された面白さが詰まっています。

次回はいったんここまでの実装を振り返りつつ、さらなる使い勝手の向上や、プログラマ向け機能の追加に向けた 「ロードマップ」 を更新していこうと思います。

次回ひと段落ついたので今後の予定関連記事は、2026年5月9日に公開予定 (あと9時間)

ここまで読んでいただきありがとうございます。

では、次の記事で。 lumenHero

関連記事

第1回 構想編【Rustで作る】ターミナル関数電卓を設計する(アーキテクチャ編) #1
第2回 Lexer実装:【Rust】自作計算機エンジンへの道:Lexer(字句解析)で数式をトークンに分解する #2
第3回 tomlの読み込み:【Rust】TOMLで関数を自由に追加!自作電卓に設定読み込みとホットリロードを実装する #3
第4回 Parserの実装(基礎):【Rust】数式を「計算の木」へ:ASTと再帰下降構文解析で電卓エンジンの心臓部を作る #4
第5回 Parserの実装(関数):【Rust】関数の「事前パース」で電卓を高速化!AST管理とデバッグ出力の実装 #5
第6回 比較演算子の実装 :【Rust】電卓に「論理」を。比較演算子の実装と優先順位の階層設計 #6
第7回 Evaluatorの実装 :【Rust】計算機自作:演算と比較を評価するEvaluatorの実装(式指向の設計) #7
第8回 履歴参照($n)の実装:【Rust】計算機:過去の計算を再利用!履歴参照($n)の実装とファイルの永続化 #8