非専門的シンギュラリティー研究所

無限に動き続けるシステムを表す方法を AI なども使って考えていきます。

関数電卓コマンド版(7)

関数電卓」のコマンド版(Windows のデスクトップアプリケーション版)にグラフ描画機能を追加して「関数のグラフ」と同等のことができるようにしました。まだ変数の計算で「読み取りアクセス違反」が発生するので動作はできていません。

描画領域を追加したのでその部分を掲載します。

#include <windows.h>
#include <string>
#include <tchar.h>
#include <map>
#include "ProgInpStr.h"
#include "ProgExp.h"
#include "ClassGraphEnv.h"

// 使い方
// exit で終了します。
// list で変数の一覧を表示します。
// 変数の定義は、
//     変数名 = 式
// で行います。
// 変数名は、英字またはアンダースコアで始まる必要があります。
// 変数の値を表示するには、
//     変数名
// と入力します。
// 式の評価は、
//     式
// と入力します。
// 式は、四則演算や関数呼び出しなどが可能です。
// グラフの描画は、
//     graph (t, x, y) (t, x, y) ...
// と入力します。
// ここで、t はパラメーター名、x は x 座標の変数名、y は y 座標の変数名です。
// 色の設定は、
//     color (r, g, b) (r, g, b) ...
// と入力します。
// ここで、r, g, b は RGB の値です。
// グラフの範囲は、
//     range (t_from, t_to, t_step)
// と入力します。
// ここで、t_from は開始値、t_to は終了値、t_step はステップ幅です。

#define ID_INPUT 1001
#define ID_OUTPUT 1002
#define ID_DRAWWINDOW 1003

// https://qiita.com/tsuchinokoman/items/869a30e02e6ddb5786b3 からコードを引用
std::wstring StringToWstring(const std::string& str);
std::string WstringToString(const std::wstring& wstr);

class ConOutput {
	// コンソール風出力クラス
private:
    HWND hOutput; // 出力先のウィンドウハンドル
    void print(const std::string& str) {
        // 出力先のウィンドウに文字列を追加
        std::wstring wstr = StringToWstring(str);
        int len = GetWindowTextLength(hOutput);
        SendMessage(hOutput, EM_SETSEL, len, len);
        SendMessage(hOutput, EM_REPLACESEL, 0, (LPARAM)wstr.c_str());
	}
public:
    ConOutput(HWND hOutput) {
        // コンストラクタで出力先のウィンドウハンドルを設定
		this->hOutput = hOutput;
	}
    ConOutput& operator<<(const std::string& str) {
        // 出力演算子オーバーロード
        print(str);
        return *this;
	}
};

HWND hInput, hOutput;
HWND hDrawWindow;
WNDPROC oldEditProc = nullptr;
WNDPROC oldDrawProc = nullptr;

using Env = std::map<std::string, ProgExp>;
Env variables;

ClassGraphEnv graphEnv; // グラフ環境のインスタンス

bool all_number_string(std::vector<std::vector<ProgExp>>& exp_list_list) {
    // 各要素が数値かどうかを調べる
    for (std::vector<ProgExp> exp_list : exp_list_list) {
        for (ProgExp exp : exp_list) {
            if (exp.getType() != "var" || !exp.getName().is_number_string()) {
                return false; // 数値でない要素があれば false
            }
        }
    }
    return true; // 全ての要素が数値であれば true
}

bool all_varname_string(std::vector<std::vector<ProgExp>>& exp_list_list) {
    // 各要素が変数名かどうかを調べる
    for (std::vector<ProgExp> exp_list : exp_list_list) {
        for (ProgExp exp : exp_list) {
            if (exp.getType() != "var" || !exp.getName().is_varname_string()) {
                return false; // 変数名でない要素があれば false
            }
        }
    }
    return true; // 全ての要素が変数名であれば true
}

ControlGraph* pControl = nullptr; // グラフ描画用のコントロール

bool process_line(const std::wstring& wline) {
    ConOutput co_cout(hOutput); // 出力オブジェクト
    ConOutput co_cerr(hOutput); // エラー出力オブジェクト
    std::string s_endl = "\r\n"; // 改行文字列

    std::string line = WstringToString(wline);
    if (line.find("exit") == 0)
    {
        // "exit" コマンドが入力された場合、アプリケーションを終了
        return false;
    }
    if (line.find("list") == 0)
    {
        // 変数リストの表示
        if (variables.empty()) {
            co_cout << "No variables defined." << s_endl;
        }
        else {
            co_cout << "Defined variables:" << s_endl;
            for (const auto& var : variables) {
                co_cout << var.first << " = " << var.second.mprint(0).GetString() << s_endl;
            }
        }
        return true;
    }
    if (line.find("graph ") == 0)
    {
        // グラフ描画の処理
        // graph (t1, x1, y1) (t2, x2, y2) ...
        // tn: パラメーター名
        // xn: x座標の変数名
        // yn: y座標の変数名
        line = line.substr(6); // "graph " を除去
        ProgInpStr inputStr(line);
        std::vector<std::vector<ProgExp>> graph_list;
        for (;;) {
            std::vector<ProgExp> exp_list = inputStr.get_exp_list();
            if(exp_list.empty()) {
                break; // 入力が終了したらループを抜ける
            }
            graph_list.push_back(exp_list);
        }
        if (!all_varname_string(graph_list)) {
            co_cerr << "Error: Each graph element must be a variable name." << s_endl;
            return true;
        }
        for (std::vector<ProgExp> exp_list : graph_list) {
            if (exp_list.size() != 3) {
                co_cerr << "Error: Invalid graph format. Expected: graph (t, x, y)...." << s_endl;
                return true;
            }
        }
        graphEnv.set_graph_list(graph_list);
        pControl->setEnv(graphEnv); // グラフ環境をコントロールに設定
        graphEnv.setDrawAll(pControl); // グラフ描画ウィンドウを設定
        return true;
    }
    if (line.find("color ") == 0)
    {
        // 色の設定
        // color (r1, g1, b1) (r2, g2, b2) ...
        line = line.substr(6); // "color " を除去
        ProgInpStr inputStr(line);
        std::vector<std::vector<ProgExp>> color_list;
        for (;;) {
            std::vector<ProgExp> exp_list = inputStr.get_exp_list();
            if (exp_list.empty()) {
                break; // 入力が終了したらループを抜ける
            }
            color_list.push_back(exp_list);
        }
        if (!all_number_string(color_list)) {
            co_cerr << "Error: Each color element must be a number." << s_endl;
            return true;
        }
        for (std::vector<ProgExp> exp_list : color_list) {
            if (exp_list.size() != 3) {
                co_cerr << "Error: Invalid color format. Expected: range (r, g, b)...." << s_endl;
                return true;
            }
        }
        graphEnv.set_color_list(color_list);
        return true;
    }
    if (line.find("view ") == 0)
    {
        // 表示範囲の設定
        // view (x1, y1) (x2, y2)
        line = line.substr(5); // "view " を除去
        ProgInpStr inputStr(line);
        std::vector<std::vector<ProgExp>> view_list;
        //view_list.clear(); // 既存のリストをクリア
        for (;;) {
            std::vector<ProgExp> exp_list = inputStr.get_exp_list();
            if (exp_list.empty()) {
                break; // 入力が終了したらループを抜ける
            }
            view_list.push_back(exp_list);
        }
        if(view_list.size() != 2 || view_list[0].size() != 2 || view_list[1].size() != 2) {
            co_cerr << "Error: Invalid view format. Expected: view (x1, y1) (x2, y2)." << s_endl;
            return true;
        }
        // 各要素が数値かどうかを調べる
        if (!all_number_string(view_list)) {
            co_cerr << "Error: All coordinates must be numbers." << s_endl;
            return true;
        }
        double x1 = view_list[0][0].getName().ToDouble();
        double y1 = view_list[0][1].getName().ToDouble();
        double x2 = view_list[1][0].getName().ToDouble();
        double y2 = view_list[1][1].getName().ToDouble();
        if (x1 >= x2 || y1 >= y2) {
            co_cerr << "Error: Invalid view coordinates. Ensure x1 < x2 and y1 < y2." << s_endl;
            return true;
        }
        // グラフ環境の表示範囲を設定
        graphEnv.set_graph_range(x1, y1, x2, y2);
        return true;
    }
    if (line.find("range ") == 0)
    {
        // パラメーターの範囲と増分の設定
        // range name = (from, to, step)
        // name: パラメーター名
        // from: 開始値
        // to: 終了値
        // step: 増分
        // range (from1, to1, step1) (from1, to1, step1) ...
        line = line.substr(6); // "range " を除去
        ProgInpStr inputStr(line);
        std::vector<std::vector<ProgExp>> range_list;
        for (;;) {
            std::vector<ProgExp> exp_list = inputStr.get_exp_list();
            if (exp_list.empty()) {
                break; // 入力が終了したらループを抜ける
            }
            range_list.push_back(exp_list);
        }
        // 各要素が数値かどうかを調べる
        if (!all_number_string(range_list)) {
            co_cerr << "Error: Each range element must be a number." << s_endl;
            return true;
        }
        for (std::vector<ProgExp> exp_list : range_list) {
            if (exp_list.size() != 3) {
                co_cerr << "Error: Invalid range format. Expected: range (from, to, step)...." << s_endl;
                return true;
            }
        }
        if (range_list.size() != 1) {
            co_cerr << "Error: Invalid range format. Expected: range (from, to, step)." << s_endl;
            return true;
        }
        // 今のところは1つの範囲のみをサポート
        double from = range_list[0][0].getName().ToDouble();
        double to = range_list[0][1].getName().ToDouble();
        double step = range_list[0][2].getName().ToDouble();
        if (step <= 0) {
            co_cerr << "Error: Step must be greater than zero." << s_endl;
            return true;
        }
        // パラメーターの範囲と増分を設定
        graphEnv.set_param_interval(from, to, step);
        graphEnv.set_range_list(range_list);
        return true;
    }
    ProgInpStr inputStr(line);
    ProgExp exp = inputStr.get_asn();
    if (exp.is_error()) {
        co_cerr << "Error: " << exp.get_error_message().GetString() << s_endl;
        return true;
    }
    if (exp.getType() == "var") {
        // 変数の定義を表示
        std::string varName = exp.getName().GetString();
        if (isalpha(varName[0]) || varName[0] == '_') {
            if (variables.find(varName) == variables.end()) {
                co_cerr << "Error: Variable '" << varName << "' is not defined." << s_endl;
                return true;
            }
            // 変数の値を表示
            ProgExp pointExp = variables[varName];
            co_cout << varName << " = " << pointExp.mprint(0).GetString() << s_endl;
            // グラフ描画用の環境に変数定義を追加
            graphEnv.add_exp(line);
            return true;
        }
    }
    else if (exp.getType() == "=") {
        // 変数を定義
        std::string varName = exp.get_left().getName().GetString();
        ProgExp right_exp = exp.get_right();
        variables[varName] = right_exp;
        return true;
    }
    // 式の評価
    ProgDefMap vardefs;
    for (const auto& var : variables) {
        vardefs.Add(var.first, var.second);
    }
    ExpNum result = exp.value(vardefs);
    if (result.is_error()) {
        co_cerr << "Error: " << result.get_error_message().GetString() << s_endl;
    }
    else {
        co_cout << "Result: " << result.Print().GetString() << s_endl;
    }
    return true;
}

const int MAX_INPUT_LENGTH = 1000;
std::wstring prompt = L"> ";

// サブクラス用プロシージャ
LRESULT CALLBACK EditProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
    if (msg == WM_KEYDOWN && wParam == VK_RETURN) {
        wchar_t buffer[MAX_INPUT_LENGTH];

        DWORD start, end;
        SendMessage(hInput, EM_GETSEL, (WPARAM)&start, (LPARAM)&end); // カーソル位置取得
        int currentLine = (int)SendMessage(hInput, EM_LINEFROMCHAR, (WPARAM)start, 0);
        // 最初の WORD に、バッファのサイズを入れる(重要!)
        *((WORD*)buffer) = sizeof(buffer) / sizeof(TCHAR);
        // EM_GETLINE を使って指定行を取得
        int copied = (int)SendMessage(hInput, EM_GETLINE, (WPARAM)currentLine, (LPARAM)buffer);
        // buffer に copied 文字分のテキストが格納される(終端の \0 は自動で付かないので注意)
        buffer[copied] = _T('\0'); // 明示的に終端すること

        int lineCount = (int)SendMessage(hInput, EM_GETLINECOUNT, 0, 0);
        if (currentLine < lineCount - 1) {
            // 次の行がある場合は、最後に移動する
            int len = GetWindowTextLength(hInput);
            SendMessage(hInput, EM_SETSEL, len, len);
        }
        else {
            // 最後の行の場合は、プロンプトを表示する
            int len = GetWindowTextLength(hInput);
            SendMessage(hInput, EM_SETSEL, len, len);
            SendMessage(hInput, EM_REPLACESEL, 0, (LPARAM)(L"\r\n" + prompt).c_str());
        }

        // 行の処理
        std::wstring line = buffer;
        line = line.substr(prompt.size()); // プロンプト部分を除去
        if(!process_line(line)) {
            // "exit" コマンドが入力された場合、アプリケーションを終了
            PostQuitMessage(0);
        }

        return 0; // エンターキーの既定動作(ビープ音など)を抑制
    }
    if (msg == WM_CHAR && wParam == VK_RETURN) {
        return 0; // エンターキーの既定動作(ビープ音など)を抑制
    }
    return CallWindowProc(oldEditProc, hwnd, msg, wParam, lParam);
}

LRESULT CALLBACK DrawProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
    switch (msg) {
    case WM_PAINT:
        pControl->OnPaint();
		return 0; // Paint message handled
    }
    return CallWindowProc(oldDrawProc, hwnd, msg, wParam, lParam);
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
    switch (msg) {
    case WM_CREATE:
        // 出力用(読み取り専用、複数行)
        hOutput = CreateWindow(L"EDIT", NULL,
            WS_CHILD | WS_VISIBLE | WS_BORDER | ES_MULTILINE | ES_READONLY | WS_VSCROLL,
            10, 10, 300, 300,
            hwnd, (HMENU)ID_OUTPUT, NULL, NULL);

        // 入力用
        hInput = CreateWindow(L"EDIT", NULL,
            WS_CHILD | WS_VISIBLE | WS_BORDER | ES_MULTILINE | WS_VSCROLL,
            10, 320, 300, 210,
            hwnd, (HMENU)ID_INPUT, NULL, NULL);

        // 描画用ウィンドウを作成
        hDrawWindow = CreateWindow(L"STATIC", NULL,
            WS_CHILD | WS_VISIBLE | WS_BORDER,
            320, 10, 520, 520,
            hwnd, (HMENU)ID_DRAWWINDOW, NULL, NULL);

		pControl = new ControlGraph(hDrawWindow); // グラフ描画用のコントロールを初期化
		//pControl = std::make_shared<ControlGraph>(hDrawWindow); // グラフ描画用のコントロールを初期化

        SetWindowText(hInput, prompt.c_str());
        {
            int len = GetWindowTextLength(hInput);
            SendMessage(hInput, EM_SETSEL, len, len);
        }
        SetFocus(hInput);

        // サブクラス化
        oldEditProc = (WNDPROC)SetWindowLongPtr(hInput, GWLP_WNDPROC, (LONG_PTR)EditProc);
        oldDrawProc = (WNDPROC)SetWindowLongPtr(hDrawWindow, GWLP_WNDPROC, (LONG_PTR)DrawProc);
        break;

    case WM_COMMAND:
        if (LOWORD(wParam) == ID_INPUT && HIWORD(wParam) == EN_CHANGE) {
            // Enterキーを検出するためにEN_CHANGEではなくWM_KEYDOWNを使う
        }
        break;

    case WM_KEYDOWN:
        break;

    case WM_DESTROY:
        PostQuitMessage(0);
        break;

    default:
        return DefWindowProc(hwnd, msg, wParam, lParam);
    }

    return 0;
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int nCmdShow) {
    const wchar_t CLASS_NAME[] = L"MyConsoleWindow";

    WNDCLASS wc = {};
    wc.lpfnWndProc = WndProc;
    wc.hInstance = hInstance;
    wc.lpszClassName = CLASS_NAME;
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);

    RegisterClass(&wc);

    HWND hwnd = CreateWindowEx(
        0, CLASS_NAME, L"GUI Console",
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT, 868, 578,
        NULL, NULL, hInstance, NULL
    );

    ShowWindow(hwnd, nCmdShow);

    MSG msg = {};
    while (GetMessage(&msg, NULL, 0, 0)) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return 0;
}

// https://qiita.com/tsuchinokoman/items/869a30e02e6ddb5786b3 からコードを引用
std::wstring StringToWstring(const std::string& str)
{
    std::wstring ret;
    //一度目の呼び出しは文字列数を知るため
    auto result = MultiByteToWideChar(CP_UTF8,
        0,
        str.c_str(),//入力文字列
        str.length(),
        nullptr,
        0);
    ret.resize(result);//確保する
    //二度目の呼び出しは変換
    result = MultiByteToWideChar(CP_UTF8,
        0,
        str.c_str(),//入力文字列
        str.length(),
        ret.data(),
        ret.size());
    return ret;
}

// https://qiita.com/tsuchinokoman/items/869a30e02e6ddb5786b3 からコードを引用
std::string WstringToString(const std::wstring& wstr)
{
    std::string ret;
    //一度目の呼び出しは文字列数を知るため
    auto result = WideCharToMultiByte(
        CP_ACP,
        0,
        wstr.c_str(),//入力文字列
        wstr.length(),
        nullptr,
        0,
        nullptr,
        nullptr);
    ret.resize(result);//確保する
    //二度目の呼び出しは変換
    result = WideCharToMultiByte(
        CP_ACP,
        0,
        wstr.c_str(),//入力文字列
        wstr.length(),
        ret.data(),
        ret.size(),
        nullptr,
        nullptr);
    return ret;
}