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

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

C++ の調査(2)

X68000エミュレーター向けに、C#JavaScript で書かれたものを C++ に書き直してみることの一環として、『円の面積』、『式の計算電卓』、『関数のグラフ』を C++ に書き直す作業をやっています。これらは Vector に登録したものがあって、それらは C# または Java で書かれているので、そちらを C++ に書き直しています。

C++ の書き方を調べるため ChatGPT で以下のように入力しました。

  • 再帰下降で式の構文解析を行うプログラムを C++ で書いてください
  • いったん式の構造のまま保持しておき、ユーザーが指定した部分だけ計算できるようにしてください
  • 途中まで計算したとき、途中まで計算した状態を表示するようにしてください
  • 変数を定義できるようにして、ユーザーが変数の位置を指定すると変数の定義の内容で置き換えられるようにしてください
  • 完全なコードを見せてください

以下のようなコードを得ることができました。これは『式の計算電卓』のコマンドライン版のようなものになっています。『関数のグラフ』と同様に変数も使えるようになっています。

#include <iostream>
#include <string>
#include <memory>
#include <cctype>
#include <stdexcept>
#include <optional>
#include <unordered_map>

int global_id = 0;

struct Expr {
    int id;
    bool evaluated = false;
    virtual ~Expr() = default;
    virtual void print() const = 0;
    virtual double eval() = 0;
};

using ExprPtr = std::shared_ptr<Expr>;

struct Number : public Expr {
    double value;
    Number(double value) : value(value) {
        id = global_id++;
        evaluated = true;
    }
    void print() const override {
        std::cout << value << "[" << id << "]";
    }
    double eval() override {
        return value;
    }
};

struct BinaryOp : public Expr {
    char op;
    ExprPtr left, right;
    std::optional<double> value;

    BinaryOp(char op, ExprPtr left, ExprPtr right)
        : op(op), left(std::move(left)), right(std::move(right)) {
        id = global_id++;
    }

    void print() const override {
        if (evaluated && value.has_value()) {
            std::cout << value.value() << "[" << id << "]";
        }
        else {
            std::cout << "(";
            left->print();
            std::cout << " " << op << " ";
            right->print();
            std::cout << ")[" << id << "]";
        }
    }

    double eval() override {
        double l = left->eval();
        double r = right->eval();
        switch (op) {
        case '+': value = l + r; break;
        case '-': value = l - r; break;
        case '*': value = l * r; break;
        case '/': value = l / r; break;
        default: throw std::runtime_error("Unknown operator");
        }
        evaluated = true;
        return value.value();
    }
};

struct VariableRef : public Expr {
    std::string name;
    VariableRef(const std::string& name) : name(name) {
        id = global_id++;
    }

    void print() const override {
        std::cout << name << "[" << id << "]";
    }

    double eval() override {
        throw std::runtime_error("Unexpanded variable '" + name + "' cannot be evaluated");
    }
};

class Parser {
public:
    Parser(const std::string& input) : input(input), pos(0) {}

    ExprPtr parse() {
        auto result = parse_expr();
        if (pos < input.size()) {
            throw std::runtime_error("Unexpected characters at end");
        }
        return result;
    }

private:
    std::string input;
    size_t pos;

    void skip_whitespace() {
        while (pos < input.size() && std::isspace(input[pos])) ++pos;
    }

    char peek() {
        skip_whitespace();
        return pos < input.size() ? input[pos] : '\0';
    }

    char get() {
        skip_whitespace();
        return pos < input.size() ? input[pos++] : '\0';
    }

    ExprPtr parse_expr() {
        auto left = parse_term();
        while (true) {
            char op = peek();
            if (op == '+' || op == '-') {
                get();
                auto right = parse_term();
                left = std::make_shared<BinaryOp>(op, left, right);
            }
            else {
                break;
            }
        }
        return left;
    }

    ExprPtr parse_term() {
        auto left = parse_factor();
        while (true) {
            char op = peek();
            if (op == '*' || op == '/') {
                get();
                auto right = parse_factor();
                left = std::make_shared<BinaryOp>(op, left, right);
            }
            else {
                break;
            }
        }
        return left;
    }

    ExprPtr parse_factor() {
        char ch = peek();
        if (ch == '(') {
            get();
            auto expr = parse_expr();
            if (get() != ')') throw std::runtime_error("Expected ')'");
            return expr;
        }
        else {
            return parse_variable_or_number();
        }
    }

    ExprPtr parse_variable_or_number() {
        skip_whitespace();
        if (std::isalpha(peek())) {
            std::string name;
            while (std::isalnum(peek()) || peek() == '_') {
                name += get();
            }
            return std::make_shared<VariableRef>(name);
        }
        else {
            return parse_number();
        }
    }

    ExprPtr parse_number() {
        skip_whitespace();
        size_t start = pos;
        while (pos < input.size() && (std::isdigit(input[pos]) || input[pos] == '.')) {
            ++pos;
        }
        double value = std::stod(input.substr(start, pos - start));
        return std::make_shared<Number>(value);
    }
};

ExprPtr find_by_id(const ExprPtr& expr, int target_id) {
    if (!expr) return nullptr;
    if (expr->id == target_id) return expr;
    if (auto bin = std::dynamic_pointer_cast<BinaryOp>(expr)) {
        auto l = find_by_id(bin->left, target_id);
        if (l) return l;
        return find_by_id(bin->right, target_id);
    }
    else if (auto var = std::dynamic_pointer_cast<VariableRef>(expr)) {
        return (var->id == target_id) ? expr : nullptr;
    }
    return nullptr;
}

bool expand_variable(ExprPtr& node, int target_id, const std::unordered_map<std::string, ExprPtr>& vars) {
    if (!node) return false;

    if (auto var = std::dynamic_pointer_cast<VariableRef>(node)) {
        if (var->id == target_id) {
            auto it = vars.find(var->name);
            if (it != vars.end()) {
                node = it->second;
                return true;
            }
            else {
                std::cerr << "Variable '" << var->name << "' is undefined.\n";
                return false;
            }
        }
    }

    if (auto bin = std::dynamic_pointer_cast<BinaryOp>(node)) {
        if (expand_variable(bin->left, target_id, vars)) return true;
        if (expand_variable(bin->right, target_id, vars)) return true;
    }

    return false;
}

int main() {
    std::unordered_map<std::string, ExprPtr> variables;
    ExprPtr ast;

    std::string command;
    std::cout << "Commands:\n  define x = <expr>\n  expr = <expr>\n  expand <id>\n  eval <id>\n  print\n  quit\n";

    while (true) {
        std::cout << "\n> ";
        std::getline(std::cin, command);

        if (command.starts_with("define ")) {
            size_t eq_pos = command.find('=');
            if (eq_pos == std::string::npos) {
                std::cerr << "Invalid define syntax.\n";
                continue;
            }
            std::string name = command.substr(7, eq_pos - 7);
            std::string expr_str = command.substr(eq_pos + 1);
            try {
                Parser parser(expr_str);
                variables[name] = parser.parse();
                std::cout << "Defined variable '" << name << "'.\n";
            }
            catch (const std::exception& e) {
                std::cerr << "Parse error: " << e.what() << "\n";
            }

        }
        else if (command.starts_with("expr =")) {
            std::string expr_str = command.substr(6);
            try {
                Parser parser(expr_str);
                ast = parser.parse();
                std::cout << "Expression parsed.\n";
            }
            catch (const std::exception& e) {
                std::cerr << "Parse error: " << e.what() << "\n";
            }

        }
        else if (command.starts_with("expand ")) {
            int id = std::stoi(command.substr(7));
            if (!expand_variable(ast, id, variables)) {
                std::cerr << "Could not expand variable.\n";
            }
            else {
                std::cout << "Variable expanded.\n";
            }

        }
        else if (command.starts_with("eval ")) {
            int id = std::stoi(command.substr(5));
            ExprPtr target = (id == -1) ? ast : find_by_id(ast, id);
            if (!target) {
                std::cerr << "ID not found.\n";
            }
            else {
                try {
                    double result = target->eval();
                    std::cout << "Result: " << result << "\n";
                }
                catch (const std::exception& e) {
                    std::cerr << "Evaluation error: " << e.what() << "\n";
                }
            }

        }
        else if (command == "print") {
            if (ast) {
                ast->print();
                std::cout << "\n";
            }
            else {
                std::cout << "No expression.\n";
            }

        }
        else if (command == "quit") {
            break;

        }
        else {
            std::cout << "Unknown command.\n";
        }
    }

    return 0;
}