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

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

C++ の調査(10)

C++ の調査(8) - 非専門的シンギュラリティー研究所」で簡単な BASIC インタープリターを ChatGPT で作ってもらいました。簡単な BASIC は比較的簡単にコンパイラーを作れると考えられるので、以下のように ChatGPT で作ってもらいました。昔やっていたことを思い出すだけでは発展性がありませんが、68000 用のコンパイラーを作ることができれば意味があります。

以下のように ChatGPT で入力しました。
(1) オリジナルの BASIC の IF 文の仕様を教えてください
(2) オリジナルの BASIC のサブルーチンの仕様を教えてください
(3) BASIC インタープリターに

  • IF
  • GOSUB / RETURN
  • INPUT

を追加してください
(4) 四則演算を追加してください
(5)

  • 式で括弧と「-」(反数)が使えるようにしてください。
  • 条件式も式の中に入れてください。
  • 「LIST」コマンド(プログラムを表示)、「RUN」コマンド(プログラムを実行)が使えるようにしてください。

(6) これらの機能をすべて盛り込んだ完全なC++ソースコード を書き出してください
(7) コンパイル機能を追加してください:
「COMPILE」コマンドで、その時点のプログラムを 68000 の機械語(またはアセンブリ言語)に変換できるようにしてください。(または(他の機械語に簡単に変換できる)仮想的な機械語に変換できるようにしてください)

(8) 仮想命令を出力する機能を組み込んだ C++ コードを提示してください

返されたコードはそのままでは動かなかったので動くように修正しました。また、以下の件を修正しました。

  • 行番号がないときコマンドを直接実行
  • EXIT コマンドで BASIC インタープリターを終了(END ステートメントで終了するようになっていた)
  • STOP と END ステートメントでプログラムを中断
  • CONT コマンド(プログラムを継続)を追加
  • REM(コメント)を追加
  • RENUM コマンド(リナンバー)を追加
  • コマンド・ステートメントは大文字と小文字を区別しない

これらを修正したものは以下のようになります。

#include <iostream>
#include <sstream>
#include <stack>
#include <map>
#include <string>
#include <vector>
#include <cctype>
#include <algorithm>  // std::transform

using namespace std;

map<int, string> program;
map<string, int> variables;
stack<map<int, string>::iterator> returnStack;

// --- Tokenizer and Expression Parser ---
vector<string> tokenize(const string& expr) {
    vector<string> tokens;
    string token;
    for (size_t i = 0; i < expr.size(); ++i) {
        char c = expr[i];
        if (isspace(c)) continue;
        if (isdigit(c) || isalpha(c)) {
            token += c;
        }
        else {
            if (!token.empty()) {
                tokens.push_back(token);
                token.clear();
            }
            if (c == '-' && (tokens.empty() || tokens.back() == "(" || tokens.back() == "+" || tokens.back() == "-" || tokens.back() == "*" || tokens.back() == "/")) {
                tokens.push_back("u-"); // unary minus
            }
            else {
                tokens.push_back(string(1, c));
            }
        }
    }
    if (!token.empty()) tokens.push_back(token);
    return tokens;
}

vector<string> toPostfix(const string& expr) {
    vector<string> tokens = tokenize(expr);
    vector<string> output;
    stack<string> ops;
    map<string, int> prec = { {"+", 1}, {"-", 1}, {"*", 2}, {"/", 2}, {"u-", 3} };

    for (const string& tok : tokens) {
        if (isdigit(tok[0]) || (tok[0] == '-' && tok.size() > 1) || isalpha(tok[0])) {
            output.push_back(tok);
        }
        else if (tok == "(") {
            ops.push(tok);
        }
        else if (tok == ")") {
            while (!ops.empty() && ops.top() != "(") {
                output.push_back(ops.top());
                ops.pop();
            }
            ops.pop();
        }
        else {
            while (!ops.empty() && prec[ops.top()] >= prec[tok]) {
                output.push_back(ops.top());
                ops.pop();
            }
            ops.push(tok);
        }
    }
    while (!ops.empty()) {
        output.push_back(ops.top());
        ops.pop();
    }
    return output;
}

int evaluateExpression(const string& expr) {
    vector<string> postfix = toPostfix(expr);
    stack<int> eval;
    for (const string& tok : postfix) {
        if (tok == "+" || tok == "-" || tok == "*" || tok == "/") {
            int b = eval.top(); eval.pop();
            int a = eval.top(); eval.pop();
            if (tok == "+") eval.push(a + b);
            else if (tok == "-") eval.push(a - b);
            else if (tok == "*") eval.push(a * b);
            else if (tok == "/") eval.push(a / b);
        }
        else if (tok == "u-") {
            int a = eval.top(); eval.pop();
            eval.push(-a);
        }
        else {
            if (isdigit(tok[0]) || (tok[0] == '-' && tok.size() > 1)) {
                eval.push(stoi(tok));
            }
            else {
                eval.push(variables[tok]);
            }
        }
    }
    return eval.top();
}

// --- Condition Evaluation ---
bool evaluateCondition(const string& cond) {
    string ops[] = { "<=", ">=", "<>", "=", "<", ">" };
    for (const string& op : ops) {
        size_t pos = cond.find(op);
        if (pos != string::npos) {
            string left = cond.substr(0, pos);
            string right = cond.substr(pos + op.size());
            int lhs = evaluateExpression(left);
            int rhs = evaluateExpression(right);
            if (op == "=") return lhs == rhs;
            if (op == "<") return lhs < rhs;
            if (op == ">") return lhs > rhs;
            if (op == "<=") return lhs <= rhs;
            if (op == ">=") return lhs >= rhs;
            if (op == "<>") return lhs != rhs;
        }
    }
    return false;
}

// --- COMPILE: Convert expression to pseudo machine code ---
void compileExpression(const string& expr) {
    vector<string> postfix = toPostfix(expr);
    for (const string& tok : postfix) {
        if (tok == "+" || tok == "-" || tok == "*" || tok == "/") {
            if (tok == "+") cout << "ADD\n";
            if (tok == "-") cout << "SUB\n";
            if (tok == "*") cout << "MUL\n";
            if (tok == "/") cout << "DIV\n";
        }
        else if (tok == "u-") {
            cout << "NEG\n";
        }
        else {
            if (isdigit(tok[0]) || (tok[0] == '-' && tok.size() > 1))
                cout << "PUSH " << tok << "\n";
            else
                cout << "LOAD " << tok << "\n";
        }
    }
}

void compileProgram() {
    cout << "=== COMPILED OUTPUT ===\n";
    for (auto& [ln, code] : program) {
        stringstream ss(code);
        string cmd;
        ss >> cmd;
        cout << ln << ": ";
        if (cmd == "LET") {
            string var, eq;
            ss >> var >> eq;
            string expr;
            getline(ss, expr);
            compileExpression(expr);
            cout << "STORE " << var << "\n";
        }
        else if (cmd == "PRINT") {
            string expr;
            getline(ss, expr);
            compileExpression(expr);
            cout << "OUT\n";
        }
        else if (cmd == "INPUT") {
            string var;
            ss >> var;
            cout << "IN " << var << "\n";
        }
        else if (cmd == "IF") {
            string cond;
            getline(ss, cond);
            size_t thenPos = cond.find("THEN");
            if (thenPos != string::npos) {
                string condition = cond.substr(0, thenPos);
                string action = cond.substr(thenPos + 4);
                string op;
                string cmpOps[] = { "<=", ">=", "<>", "=", "<", ">" };
                for (const string& co : cmpOps) {
                    size_t pos = condition.find(co);
                    if (pos != string::npos) {
                        string lhs = condition.substr(0, pos);
                        string rhs = condition.substr(pos + co.size());
                        compileExpression(lhs);
                        compileExpression(rhs);
                        if (co == "=") op = "EQ";
                        if (co == "<") op = "LT";
                        if (co == ">") op = "GT";
                        if (co == "<=") op = "LE";
                        if (co == ">=") op = "GE";
                        if (co == "<>") op = "NE";
                        cout << op << "\n";
                        cout << "JNZ " << action << "\n";
                        break;
                    }
                }
            }
        }
        else if (cmd == "GOTO") {
            int target;
            ss >> target;
            cout << "JMP " << target << "\n";
        }
        else if (cmd == "GOSUB") {
            int target;
            ss >> target;
            cout << "CALL " << target << "\n";
        }
        else if (cmd == "RETURN") {
            cout << "RET\n";
        }
        else if (cmd == "END") {
            cout << "HALT\n";
        }
    }
    cout << "=== END COMPILE ===\n";
}

string to_upper(const std::string& str) {
    string result = str;
    transform(result.begin(), result.end(), result.begin(),
        [](unsigned char c) { return toupper(c); });
    return result;
}

// 行番号を揃える
string renumLine(const string& line, map<int, int> num_map) {
    stringstream ss(line);
    string cmd;
    ss >> cmd;

    cmd = to_upper(cmd);

    if (cmd == "IF") {
        string cond;
        getline(ss, cond);
        size_t thenPos = cond.find("THEN");
        if (thenPos != string::npos) {
            string condition = cond.substr(0, thenPos);
            string action = cond.substr(thenPos + 4);
            stringstream act(action);
            string next;
            act >> next;
            if (isdigit(next[0])) {
                int target = stoi(next);
                return cmd + condition + "THEN " + to_string(num_map[target]);
            }
        }
    }
    else if (cmd == "GOTO") {
        int target;
        ss >> target;
        return cmd + " " + to_string(num_map[target]);
    }
    else if (cmd == "GOSUB") {
        int target;
        ss >> target;
        return cmd + " " + to_string(num_map[target]);
    }
    return line;
}

map<int, string> renum() {
    map<int, int> num_map;
    auto pit = program.begin();
    int num = 0;
    while (pit != program.end()) {
        num += 10;
        num_map[pit->first] = num;
        pit++;
    }
    map<int, string> renum_program;
    auto nit = num_map.begin();
    pit = program.begin();
    while (pit != program.end()) {
        renum_program[nit->second] = renumLine(pit->second, num_map);
        nit++;
        pit++;
    }
    return renum_program;
}

// --- Interpreter Core ---
bool executeLine(const string& line, map<int, string>::iterator& it) {
    stringstream ss(line);
    string cmd;
    ss >> cmd;

    cmd = to_upper(cmd);

    if (cmd == "LET") {
        string var, eq;
        ss >> var >> eq;
        string expr;
        getline(ss, expr);
        variables[var] = evaluateExpression(expr);
    }
    else if (cmd == "PRINT") {
        string expr;
        getline(ss, expr);
        cout << evaluateExpression(expr) << endl;
    }
    else if (cmd == "INPUT") {
        string var;
        ss >> var;
        cout << "? ";
        cin >> variables[var];
    }
    else if (cmd == "IF") {
        string cond;
        getline(ss, cond);
        size_t thenPos = cond.find("THEN");
        if (thenPos != string::npos) {
            string condition = cond.substr(0, thenPos);
            string action = cond.substr(thenPos + 4);
            if (evaluateCondition(condition)) {
                stringstream act(action);
                string next;
                act >> next;
                if (isdigit(next[0])) {
                    int target = stoi(next);
                    it = program.find(target);
                    return true;
                }
            }
        }
    }
    else if (cmd == "GOTO") {
        int target;
        ss >> target;
        it = program.find(target);
        return true;
    }
    else if (cmd == "GOSUB") {
        int target;
        ss >> target;
        returnStack.push(next(it));
        it = program.find(target);
        return true;
    }
    else if (cmd == "RETURN") {
        it = returnStack.top();
        returnStack.pop();
        return true;
    }
    else if (cmd == "LIST") {
        // プログラムを表示
        for (auto& [ln, code] : program) {
            cout << ln << " " << code << endl;
        }
    }
    else if (cmd == "RUN") {
        // プログラムを実行
        it = program.begin();
        while (it != program.end()) {
            if (!executeLine(it->second, it)) {
                return true;
            }
        }
        return true;
    }
    else if (cmd == "CONT") {
        // プログラムを継続
        while (it != program.end()) {
            if (!executeLine(it->second, it)) {
                return true;
            }
        }
        return true;
    }
    else if (cmd == "COMPILE") {
        // コンパイル
        compileProgram();
    }
    else if (cmd == "STOP" || cmd == "END") {
        // プログラムを中断
        if (it != program.end()) {
            ++it;
        }
        return false;
    }
    else if (cmd == "EXIT") {
        // BASIC インタープリターを終了
        exit(0);
    }
    else if (cmd == "RENUM") {
        // 行番号を揃える
        program = renum();
        // プログラムが変更されたときは実行位置をリセット
        it = program.begin();
        return true;
    }
    else if (cmd == "REM") {
        // コメント
    }
    else {
        cout << "コマンドが誤っています:" << cmd;
    }
    if (it != program.end()) {
        ++it;
        return true;
    }
    return false;
}

// --- Main Loop ---
int main() {
    string line;
    cout << "basic> ";
    auto it = program.begin();
    while (true) {
        getline(cin, line);
        if (line.empty()) {
            cout << "basic> ";
            continue;
        }
        stringstream ss(line);
        int lineNumber;
        if (ss >> lineNumber) {
            string rest;
            getline(ss, rest);
            if (!rest.empty() && rest[0] == ' ') rest.erase(0, 1);
            if (rest == "") {
                // 空文字列のときは削除
                program.erase(lineNumber);
            }
            else {
                program[lineNumber] = rest;
            }
            // プログラムが変更されたときは実行位置をリセット
            it = program.begin();
        }
        else {
            // 行番号がない(変換に失敗した)とき直接実行
            executeLine(line, it);
        }
        cout << "basic> ";
    }

    return 0;
}

以下の例を実行することができます。

10 REM フィボナッチ数列
20 LET N = 10
30 LET I = 1
40 LET A = 1
50 LET B = 1
60 PRINT A
70 LET T = B
80 LET B = A + B
90 LET A = T
100 LET I = I + 1
110 IF I <= N THEN 60