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

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

C++ の調査(9)

プログラム電卓」は TypeScript で書かれているので、これを C++ に変換して TypeScript から C++ に変換する方法を調べようと思ったのですが、C# で書かれたものもありました。これは C# から C++ に書き換えることにします。

この件はいったん保留して、Z80 アセンブラーを作ってもらうことにしました。これは昔 BASIC の中間コードを Z80 のコードに変換するものを作ったことがあるので、どんなものだったか、今 Z80 について思い出してもしかたがないのですが思い出しておこうと考えたためです。

C++Z80 アセンブラーを作ってください

と ChatGPT で入力すると以下の結果となりました。

了解です!Z80アセンブラーをC++で作るには、以下のようなコンポーネントが必要になります:

基本構成

1. 字句解析 (Lexer):

2. 構文解析 (Parser):

  • トークン列を命令やラベルなどの構造に変換する。

3. コード生成 (Code Generator):

  • パースされた命令をバイナリ(Z80機械語)に変換する。

4. ラベルの解決:

  • ジャンプ先などに使われるラベルを、アドレスに変換する。

最小構成のZ80アセンブラー(C++

以下はシンプルなZ80命令(例:`LD A, 0x42`)のみを扱う最小構成の例です。

ファイル: `z80_assembler.cpp`

#include <iostream>
#include <fstream>
#include <sstream>
#include <unordered_map>
#include <vector>
#include <iomanip>

struct Instruction {
    std::string mnemonic;
    std::vector<std::string> operands;
};

std::vector<std::string> tokenize(const std::string& line) {
    std::stringstream ss(line);
    std::string token;
    std::vector<std::string> tokens;
    while (ss >> token) {
        // カンマを削除
        if (!token.empty() && token.back() == ',') {
            token.pop_back();
        }
        tokens.push_back(token);
    }
    return tokens;
}

// 単純なLD命令のみ対応: LD A, n
std::vector<uint8_t> assemble(const Instruction& inst) {
    if (inst.mnemonic == "LD" && inst.operands.size() == 2) {
        if (inst.operands[0] == "A") {
            uint8_t value = std::stoi(inst.operands[1], nullptr, 0);
            return {0x3E, value}; // LD A, n の機械語
        }
    }
    std::cerr << "未対応の命令: " << inst.mnemonic << "\n";
    return {};
}

int main(int argc, char* argv[]) {
    if (argc != 3) {
        std::cerr << "使い方: " << argv[0] << " input.asm output.bin\n";
        return 1;
    }

    std::ifstream infile(argv[1]);
    if (!infile) {
        std::cerr << "入力ファイルが開けません: " << argv[1] << "\n";
        return 1;
    }

    std::vector<uint8_t> output;
    std::string line;
    while (std::getline(infile, line)) {
        auto tokens = tokenize(line);
        if (tokens.empty()) continue;

        Instruction inst;
        inst.mnemonic = tokens[0];
        inst.operands.assign(tokens.begin() + 1, tokens.end());

        auto code = assemble(inst);
        output.insert(output.end(), code.begin(), code.end());
    }

    std::ofstream outfile(argv[2], std::ios::binary);
    if (!outfile) {
        std::cerr << "出力ファイルが開けません: " << argv[2] << "\n";
        return 1;
    }
    outfile.write(reinterpret_cast<char*>(output.data()), output.size());

    std::cout << "アセンブル完了: " << output.size() << " バイト生成されました。\n";
    return 0;
}

使い方

アセンブリファイル `input.asm`
LD A, 0x42
LD A, 255
コンパイル & 実行
g++ -o z80asm z80_assembler.cpp
./z80asm input.asm output.bin

これで、最小限のZ80アセンブラーが完成です。

拡張案

  • `LD B, n` などの他のレジスタ対応
  • ラベルのサポート
  • `JP`, `CALL`, `RET` などの制御命令
  • `.org` ディレクティブなどの対応
  • エラー処理強化

もっと本格的な機能を入れたいですか?あるいはGUIやデバッガとの統合も視野にありますか?