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

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

C++ の調査(6)

ChatGPT で全部作ってもらうのは難しいので、いったん以下のようなプログラムを ChatGPT で作ってもらって、動くように修正しました。

構文

構文を以下のどれかとします。

  • var 変数名 = 式
  • print 変数名 # 番号
  • eval 変数名 # 番号

ここで

  • 番号は自然数とします。
  • 式は数値、変数、加減乗除、「- 式」(単項のマイナス)、かっこが使えるとします。
  • 変数名(式の中ではない)は省略可能とします。
  • 「# 番号」は省略可能とします。

動作

構文解析の結果に以下の処理を行います。

var 変数名 = 式

変数を定義します。変数名が省略されたときは「_」という名前の変数を定義します。

変数に定義された式を表示します。変数名が省略されたときは「_」という名前の変数に定義された式を表示します。式の各「部分式」には番号が付けられていて、番号が指定されたときはその番号の部分式を表示します。番号を省略すると式全体を表示します。

eval 変数名 # 番号

変数に定義された式を計算します。変数名が省略されたときは「_」という名前の変数に定義された式を計算します。式の各「部分式」には番号が付けられていて、番号が指定されたときはその番号の部分式を計算して、その部分式を計算結果で置き換えます。番号を省略すると式全体を計算します。

「計算する」とは

  • 式が二項演算または単項演算で各演算数が数値のとき、その式を演算の結果で置き換えます。
  • 式が変数のとき、その式を変数に定義された式で置き換えます。

プログラムのコード

動くように修正したものは以下のようになります。

#include <iostream>
#include <sstream>
#include <string>
#include <map>
#include <memory>
#include <cctype>
#include <vector>
#include <stdexcept>
#include <optional>

using namespace std;

// 式の型定義
struct Expr {
    virtual ~Expr() = default;
    virtual double eval(const map<string, shared_ptr<Expr>>& env) const = 0;
    virtual string to_string(int& pos) const = 0;
    string pos_string(int& pos) const {
        return "#" + std::to_string(pos++);
    }
};

struct Number : Expr {
    double value;
    Number(double v) : value(v) {}
    double eval(const map<string, shared_ptr<Expr>>& env) const override { return value; }
    string to_string(int& pos) const override {
        return std::to_string(value) + pos_string(pos);
    }
};

struct Variable : Expr {
    string name;
    Variable(string n) : name(std::move(n)) {}
    double eval(const map<string, shared_ptr<Expr>>& env) const override {
        if (env.count(name)) return env.at(name)->eval(env);
        throw runtime_error("Undefined variable: " + name);
    }
    string to_string(int& pos) const override {
        return name + pos_string(pos);
    }
};

struct UnaryMinus : Expr {
    shared_ptr<Expr> expr;
    UnaryMinus(shared_ptr<Expr> e) : expr(std::move(e)) {}
    double eval(const map<string, shared_ptr<Expr>>& env) const override {
        return -expr->eval(env);
    }
    string to_string(int& pos) const override {
        string pos_str = pos_string(pos);
        return "-(" + expr->to_string(pos) + ")" + pos_str;
    }
};

struct BinaryOp : Expr {
    char op;
    shared_ptr<Expr> lhs, rhs;
    BinaryOp(char o, shared_ptr<Expr> l, shared_ptr<Expr> r) : op(o), lhs(std::move(l)), rhs(std::move(r)) {}
    double eval(const map<string, shared_ptr<Expr>>& env) const override {
        double a = lhs->eval(env), b = rhs->eval(env);
        switch (op) {
        case '+': return a + b;
        case '-': return a - b;
        case '*': return a * b;
        case '/': return a / b;
        }
        throw runtime_error("Unknown operator");
    }
    string to_string(int& pos) const override {
        string pos_str = pos_string(pos);
        string left_str = lhs->to_string(pos);
        string right_str = rhs->to_string(pos);
        return "(" + left_str + " " + op + " " + right_str + ")" + pos_str;
    }
};

// トークンの型
enum TokenType { NUMBER, IDENT, OP, LPAREN, RPAREN, END, INVALID, HASH, EQUAL, KEYWORD };

struct Token {
    TokenType type;
    string text;
};

// 字句解析器
class Tokenizer {
    stringstream ss;
    char curr;
public:
    explicit Tokenizer(const string& input) : ss(input) { next(); }

    void next() { curr = ss.get(); }

    Token getToken() {
        while (isspace(curr)) next();
        if (isdigit(curr)) {
            string num;
            while (isdigit(curr) || curr == '.') {
                num += curr;
                next();
            }
            return { NUMBER, num };
        }
        if (isalpha(curr)) {
            string ident;
            while (isalnum(curr)) {
                ident += curr;
                next();
            }
            if (ident == "var" || ident == "print" || ident == "eval" || ident == "exit")
                return { KEYWORD, ident };
            return { IDENT, ident };
        }
        if (curr == '+' || curr == '-' || curr == '*' || curr == '/') {
            char c = curr;
            next();
            return { OP, string(1, c) };
        }
        if (curr == '(') { next(); return { LPAREN, "(" }; }
        if (curr == ')') { next(); return { RPAREN, ")" }; }
        if (curr == '=') { next(); return { EQUAL, "=" }; }
        if (curr == '#') {
            next();
            string num;
            while (isdigit(curr)) {
                num += curr;
                next();
            }
            return { HASH, num };
        }
        if (curr == EOF) return { END, "" };
        next();
        return { INVALID, string(1, curr) };
    }
};

// 再帰下降構文解析器
class Parser {
    Tokenizer tokenizer;
    Token curr;

    void eat(TokenType type) {
        if (curr.type != type)
            throw runtime_error("Unexpected token: " + curr.text);
        curr = tokenizer.getToken();
    }

    shared_ptr<Expr> parsePrimary() {
        if (curr.type == NUMBER) {
            double val = stod(curr.text);
            eat(NUMBER);
            return make_shared<Number>(val);
        }
        if (curr.type == IDENT) {
            string name = curr.text;
            eat(IDENT);
            return make_shared<Variable>(name);
        }
        if (curr.type == OP && curr.text == "-") {
            eat(OP);
            return make_shared<UnaryMinus>(parsePrimary());
        }
        if (curr.type == LPAREN) {
            eat(LPAREN);
            auto expr = parseExpr();
            eat(RPAREN);
            return expr;
        }
        throw runtime_error("Invalid primary expression");
    }

    shared_ptr<Expr> parseTerm() {
        auto left = parsePrimary();
        while (curr.type == OP && (curr.text == "*" || curr.text == "/")) {
            char op = curr.text[0];
            eat(OP);
            auto right = parsePrimary();
            left = make_shared<BinaryOp>(op, left, right);
        }
        return left;
    }

    shared_ptr<Expr> parseExpr() {
        auto left = parseTerm();
        while (curr.type == OP && (curr.text == "+" || curr.text == "-")) {
            char op = curr.text[0];
            eat(OP);
            auto right = parseTerm();
            left = make_shared<BinaryOp>(op, left, right);
        }
        return left;
    }

public:
    explicit Parser(const string& input) : tokenizer(input) {
        curr = tokenizer.getToken();
    }

    shared_ptr<Expr> parseExpression() {
        return parseExpr();
    }

    // 番号付きの部分式取得
    void collect_subexpressions(const shared_ptr<Expr>& expr,
        vector<shared_ptr<Expr>>& out) {
        if (!expr) return;
        out.push_back(expr);
        if (auto bin = dynamic_pointer_cast<BinaryOp>(expr)) {
            collect_subexpressions(bin->lhs, out);
            collect_subexpressions(bin->rhs, out);
        }
        else if (auto un = dynamic_pointer_cast<UnaryMinus>(expr)) {
            collect_subexpressions(un->expr, out);
        }
    }

    // 部分式評価(定数なら簡約、変数なら展開)
    shared_ptr<Expr> evaluate_once(shared_ptr<Expr> expr,
        map<string, shared_ptr<Expr>>& env) {
        if (auto v = dynamic_pointer_cast<Variable>(expr)) {
            if (env.count(v->name)) return env[v->name];
        }
        else if (auto bin = dynamic_pointer_cast<BinaryOp>(expr)) {
            auto lhs = bin->lhs, rhs = bin->rhs;
            auto left = evaluate_once(lhs, env);
            auto right = evaluate_once(rhs, env);
            auto lnum = dynamic_pointer_cast<Number>(left);
            auto rnum = dynamic_pointer_cast<Number>(right);
            if (lnum && rnum) {
                double result = BinaryOp(bin->op, left, right).eval(env);
                return make_shared<Number>(result);
            }
        }
        else if (auto un = dynamic_pointer_cast<UnaryMinus>(expr)) {
            auto sub = evaluate_once(un->expr, env);
            if (auto subnum = dynamic_pointer_cast<Number>(sub)) {
                return make_shared<Number>(-subnum->value);
            }
        }
        return expr;
    }

    shared_ptr<Expr> replace_subexpression(const shared_ptr<Expr>& expr,
        int index_to_replace,
        const shared_ptr<Expr>& new_subexpr,
        int& current_index) {
        if (current_index == index_to_replace) {
            current_index++;
            return new_subexpr;
        }

        int this_index = current_index;
        current_index++;

        if (auto bin = dynamic_pointer_cast<BinaryOp>(expr)) {
            auto new_lhs = replace_subexpression(bin->lhs, index_to_replace, new_subexpr, current_index);
            auto new_rhs = replace_subexpression(bin->rhs, index_to_replace, new_subexpr, current_index);
            return make_shared<BinaryOp>(bin->op, new_lhs, new_rhs);
        }

        if (auto un = dynamic_pointer_cast<UnaryMinus>(expr)) {
            auto new_expr = replace_subexpression(un->expr, index_to_replace, new_subexpr, current_index);
            return make_shared<UnaryMinus>(new_expr);
        }

        // Number や Variable は子を持たないのでそのまま返す
        return expr;
    }

    // 外から使いやすいようにラップする関数
    shared_ptr<Expr> replace_subexpression(const shared_ptr<Expr>& expr,
        int index_to_replace,
        const shared_ptr<Expr>& new_subexpr) {
        int current_index = 0;
        return replace_subexpression(expr, index_to_replace, new_subexpr, current_index);
    }

    optional<string> parseCommand(map<string, shared_ptr<Expr>>& env) {
        if (curr.type == KEYWORD) {
            string cmd = curr.text;
            eat(KEYWORD);

            if (cmd == "print" || cmd == "eval") {
                string varname = "_";
                int index = 0;
                if (curr.type == IDENT) {
                    varname = curr.text;
                    eat(IDENT);
                }
                if (curr.type == HASH) {
                    index = stoi(curr.text);
                    eat(HASH);
                }
                if (!env.count(varname)) throw runtime_error("Undefined variable: " + varname);
                auto root = env[varname];
                vector<shared_ptr<Expr>> subs;
                collect_subexpressions(root, subs);
                if (index >= 0 && index < subs.size()) {
                    if (cmd == "print") {
                        int pos = index;
                        return subs[index]->to_string(pos);
                    }
                    else {
                        auto evaluated = evaluate_once(subs[index], env);
                        env[varname] = replace_subexpression(env[varname], index, evaluated);
                        int pos = 0;
                        return env[varname]->to_string(pos);
                    }
                }
                else {
                    throw runtime_error("Invalid subexpression index");
                }
            }

            if (cmd == "var") {
                string varname = "_";
                if (curr.type == IDENT) {
                    varname = curr.text;
                    eat(IDENT);
                }
                if (curr.type == EQUAL) eat(EQUAL);
                auto expr = parseExpression();
                env[varname] = expr;
                return "Defined";
            }

            if (cmd == "exit") {
                return nullopt;
            }

            throw runtime_error("Unknown command: " + cmd);
        }

        throw runtime_error("Expected command");
    }
};

// メインループ
int main() {
    map<string, shared_ptr<Expr>> variables;

    string line;
    cout << "sub-eval-print> ";
    while (getline(cin, line)) {
        try {
            Parser parser(line);
            optional<string> result = parser.parseCommand(variables);
            if (!result) {
                break;
            }
            cout << *result << endl;
        }
        catch (exception& e) {
            cerr << "Error: " << e.what() << endl;
        }
        cout << "sub-eval-print> ";
    }

    return 0;
}