Mery 電卓 (bc コマンド使用版)

2025年11月29日 (土) 14:02時点におけるYuko (トーク | 投稿記録)による版 (更新履歴)
(差分) ← 古い版 | 最新版 (差分) | 新しい版 → (差分)

概要 編集

「Mery 電卓 (bc コマンド使用版)」は、 Mery 電卓 の派生版で、Meryエディタ上で数式を計算するためのマクロです。オリジナルの「Mery 電卓」よりも対応する数学関数が少ない代わりに起動が早いため、日々の利用の中で軽快に使用することができます。

Linux でお馴染みの bc コマンドを計算エンジンとしており、標準的な数式と一部の数学関数をサポートしています。カーソル位置の行または選択したテキストを数式として解釈し、結果を表示します。計算結果は自動的にクリップボードにコピーされ、カンマ区切りの数値として表示されます。

本マクロをショートカットキーに設定し、いつでも起動できるようにしておくと日常シーンで何かと便利です。作者は「Ctrl + Alt + Enter」キーに割り当てて利用しています。

注意事項 編集

  • Mery Ver 3.7.19 で動作確認しています。

使い方 編集

0. 事前準備

  • 本マクロでは計算コマンドの「bc」を内部で使っています。当該コマンドを使うために「BusyBox for Windows」を利用します
    • ダウンロード先: https://frippery.org/busybox/
    • ダウンロードするファイル: busybox64u.exe ※ダウンロード後、マクロファイルと同じフォルダーに配置してください
      • Windows 10 (1903) 以降の環境では上記を推奨。それ以前の環境の場合は動作しない可能性があるため、適切な実行ファイルを選択してください。

1. 行全体を計算する場合:

  • 計算したい数式が書かれた行にカーソルを置きます。
  • マクロを実行すると、次の行に「= 計算結果」が表示されます。
  • 既に結果行がある場合は、その行が更新されます。

2. 選択範囲を計算する場合:

  • 計算したい数式部分を選択します。
  • マクロを実行すると、選択範囲の後ろに「計算式 = 計算結果」が挿入されます。

3. サポートされている演算子:

  • 四則演算(+, -, *, /)
  • べき乗(^)
  • 剰余(%) ※整数の剰余にのみ対応
  • 比較演算子(<, >, <=, >=, ==, !=)

4. サポートされている関数:

  • s(x): サイン(引数はラジアン)
  • c(x): コサイン(引数はラジアン)
  • a(x): アークタンジェント(返り値はラジアン)
  • l(x): 自然対数
  • e(x): 指数関数
  • sqrt(x): 平方根
  • j(n,x): n次ベッセル関数

5. 変数の利用:

  • 変数への代入: $<変数名> = <値> (例: $a = 5, $pi = 4*a(1))
  • 変数の参照: 変数名を数式に記述 (例: $a + 3, $b * 2)
  • 複数の式を実行: セミコロン ; で区切る (例: $a = 3; $b = 2; $a + $b)
  • インクリメント・デクリメント: ++, -- (例: $a++, ++$a)

6. 特別な機能:

  • カンマ区切り表示: 計算結果は読みやすいようにカンマ区切りで表示されます。変数「COMMA」を調整することで制御できます。
  • 自動コピー: 計算結果は自動的にクリップボードにコピーされます。変数「COPY」を調整することで制御できます。
  • 計算結果の再利用: 行頭の「= 」は計算実行時に無視されるため、計算結果をスムーズに次の式として利用できます。
  • 変数の利用: 複数行を選択して実行、または一行内で「;」で数式を区切ることで、変数を使った数式が利用できます。変数形式は「$[a-zA-Z][a-zA-Z0-9]*」に対応しています。例えば「$a = 2; $a * 3」として実行すると、結果は「6」と出力されます。
  • メモ書きから計算: 数字や関数以外の文字はできるだけ除外した上で計算をします。例えば「月100円 * 12ヶ月」のようなメモ書きに対して実行すると「1,200」と出力されます。また「` (バッククォート)」で囲んだ文字は確実に無視されるため、計算メモ用として使うことができます (使用例: 『1500円 `金額未定のため仮置き`』)。

ソースコード 編集

#title = "Mery 電卓 (bc コマンド使用版)"
#include "Common.js"
BeginUndoGroup();

// 事前準備:
//   以下のサイトから busybox64u.exe を入手し、本マクロと同じフォルダーに配置することで動作します。
//   https://frippery.org/busybox/

// 結果をクリップボードにコピーするか
var COPY = true;
// 出力結果をカンマ区切りにするするか
var COMMA = true;

// busybox のパスを取得
const scriptDir = ScriptFullName.replace(/\\[^\\]+$/g, "")
const busyboxPath = scriptDir + "\\busybox64u.exe";
if (!shell.FileExists(busyboxPath)) {
    alert("busybox64u.exe が見つかりません。\nこのマクロを動作させるには、busybox64u.exe を本マクロと同じフォルダーに配置する必要があります。");
    Quit();
}

var sel = document.selection;

if (sel.Count > 1) {
    // マルチカーソル実行時は計算中表示を出してから実行
    ShowTip("計算中... (全 " + sel.Count + " 件)", meShowTipPosCaret);
    doMultiEdit(main);
    ShowTip("", meShowTipHide);
} else {
    main();
}

function main() {
    var isEmpty = sel.IsEmpty;
    var activePointY = sel.GetActivePointY(mePosLogical);
    if (isEmpty) {
        var exp = document.GetLine(activePointY);
    } else {
        var exp = sel.Text;
    }
    // 空白行の場合は何もしない
    if (exp.replace(/[\s ]+/g, "") === "") {
        return;
    }

    // 計算実行
    try {
        var exp_replaced = replaceExpression(exp)
            .replace(/([^;]*%[^;]*)/g, "scale=0; $1; scale=15; "); // 剰余計算を整数にする
        const exec = shell.Exec(busyboxPath + " bc -l", "scale=15; " + exp_replaced);
        if (exec.ExitCode !== 0) {
            throw new Error("計算エラー: " + exec.StdErr);
        }
        // bc の出力のクセに合わせて整形する
        var result_str = exec.StdOut
            .trim()
            .replace(/^(-)?\./gm, "$10.")
            .replace(/(\.[0-9]*?)0+$/gm, "$1")
            .replace(/\.$/gm, "");
    } catch (e) {
        if (exp_replaced === undefined) {
            alert("エラー発生" + "\n------\n" + e)
        } else {
            alert("整形後の数式で計算エラーが発生" + "\n------\n" + e + "\n元の数式: " + exp + "\n整形後の数式: " + exp_replaced);
        }
        var c = Confirm("数式ヘルプを表示しますか?");
        if (c) {
            editor.NewFile();
            var newDoc = editor.ActiveDocument;
            newDoc.Write(help());
            newDoc.selection.SetActivePos(0);
        }
        return;
    }

    // 小数点以上のみカンマ区切り
    if (COMMA && result_str.match(/^[0-9]+(\.[0-9]+)?$/m)) {
        var result_parts = result_str.split(".");
        result_parts[0] = result_parts[0].replace(/(\d)(?=(\d{3})+$)/gm, "$1,");
        if (result_parts.length > 1) {
            result_str = result_parts.join(".");
        } else {
            result_str = result_parts[0];
        }
    }

    // bc コマンドでは複数個の計算出力があると計算結果が複数行になるので1行にまとめる
    result_str = result_str.replace(/\r?\n/g, " ; ");

    // 計算行が = 始まりの場合、= を削除する
    if (isEmpty && exp.match(/^= /)) {
        sel.SetActivePoint(mePosLogical, 1, activePointY, false);
        sel.EndOfLine(true, mePosLogical);
        sel.Text = sel.Text.replace(/^= /, "");
    }

    // 既存の計算結果を上書きするか判定
    var pattern = /^=\s*(-?[0-9.,]+(\s*;\s*)?)*\s*$/;
    var nextLine = document.GetLine(activePointY + 1);
    if (isEmpty && nextLine.match(pattern)) {
        sel.SetActivePoint(mePosLogical, 1, activePointY + 1, false);
        sel.EndOfLine(true, mePosLogical);
        sel.Text = '= ' + result_str
    } else if (isEmpty) {
        sel.LineOpen();
        sel.Text = '= ' + result_str
    } else {
        sel.CharRight();
        const inputExp = exp.replace(/((?!;)\n)/mg, "; ").replace(/;\s*;/g, ";").replace(/ /g, " ").replace(/;\s*$/, "").trim();
        var newLine = "";
        if (exp.slice(-1) !== "\n") {
            newLine = '\n';
        }
        sel.Text = newLine + inputExp + '\n= ' + result_str + '\n';
        sel.CharLeft();
    }

    if (COPY) {
        ClipboardData.SetData(result_str);
        Status = "計算結果 " + result_str + " をコピーしました"
    }
}

/**
 * 数式を置換する
 */
function replaceExpression(exp) {
    // コメント扱いの記載を削除
    let exp_replaced = exp.replace(/`[^`]*`/g, " ")

    // 他の言語で対応する演算子などはあらかじめエラーにする
    const invalidOperators = ["\\*\\*", "//"];
    for (var i = 0; i < invalidOperators.length; i++) {
        var operator = invalidOperators[i];
        if (exp.match(new RegExp(operator))) {
            throw new Error("演算子「" + operator + "」はサポートされていません");
        }
    }

    exp_replaced = exp_replaced.trim()
        // 先頭の = を削除
        .replace(/^= /, "")
        // 改行を ; に置換
        .replace(/\n/g, ";")
        // 非ASCII文字を削除
        .replace(/[^\x20-\x7e]/g, " ")
        // 全角スペースを半角スペースに変換
        .replace(/ /g, " ")
        // 数字内のカンマを取り除く。j(1, 2) では削除しない
        .replace(/j *\(\d+, *\d+\)|,/g, function (match) {
            return match.match(/j/) ? match : "";
        })
        // 変数形式を退避してからドル記号削除
        .replace(/(\$[a-zA-Z][a-zA-Z0-9]*)/g, " %%%$1%%% ")
        .replace(/\$/g, "")
        // 空の括弧を削除
        .replace(/\(\s*\)|\[\s*\]|\{\s*\}/g, " ")
        // 対応関数以外の文字列は削除
        .replace(/[a-zA-Z]+/g, function (match) {
            const functions = ["s", "c", "a", "l", "e", "sqrt", "j"];
            for (var i = 0; i < functions.length; i++) {
                var f = functions[i];
                if (match === f) {
                    return match;
                }
            }
            return " ";
        })
        // `/hour` などの文字列があるとスラッシュだけ残るためスラッシュを削除
        .replace(/\/\s*([,+\-*\/%^&~|<>?/)\];\n]|$)/g, "$1")
        // 退避文字を復元
        .replace(/ %%%\$([a-zA-Z][a-zA-Z0-9]*)%%% /g, " $1 ")
        .replace(/ %%%([^%]+)%%% /g, " $1 ")

    return exp_replaced;
}

function help() {
    return "【Mery 電卓 (bc コマンド使用版) で使用可能な演算子と関数】\n" +
        "\n■基本演算子:\n" +
        "`+` (加算), `-` (減算), `*` (乗算), `/` (除算), `%` (剰余), `^` (べき乗)\n" +
        "\n■比較演算子:\n" +
        "`==` (等しい), `!=` (等しくない), `<` (より小さい), `<=` (以下), `>` (より大きい), `>=` (以上)\n" +
        "\n■代入演算子:\n" +
        "`=`, `+=`, `-=`, `*=`, `/=`, `%=`, `^=`\n" +
        "\n■インクリメント・デクリメント:\n" +
        "`++`, `--`\n" +
        "\n■数学関数 (要 `-l` オプション):\n" +
        "- `s(x)`: サイン(引数はラジアン)\n" +
        "- `c(x)`: コサイン(引数はラジアン)\n" +
        "- `a(x)`: アークタンジェント(返り値はラジアン)\n" +
        "- `l(x)`: 自然対数\n" +
        "- `e(x)`: 指数関数\n" +
        "- `sqrt(x)`: 平方根\n" +
        "- `j(n,x)`: n次ベッセル関数\n" +
        "\n■変数の使い方:\n" +
        "- 変数への代入: $<変数名> = <値> (例: $a = 5, $pi = 4*a(1))\n" +
        "- 変数の参照: 変数名を数式に記述 (例: $a + 3, $b * 2)\n" +
        "- 複数の式を実行: セミコロン ; で区切る (例: $a = 3; $b = 2; $a + $b)";
}

サードパーティライブラリ 編集

busybox64u.exe

更新履歴 編集

  • 2025-11-29 計算結果が「-0.1」のような1未満の負の小数になる場合に「-.1」のような表示になってしまう不具合を修正。小数計算精度を scale=10 から scale=15 へ調整
  • 2025-10-26 「5 - 3.0」のような「小数以下が『.0』になる計算」で「.」だけが出力に残ってしまう不具合を修正
  • 2025-10-25 公開
スポンサーリンク