「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







