miniC.py テクニカルドキュメント

プログラムの動作

miniC.py は、独自のスクリプト言語である「miniC」のインタプリタです。このプログラムは、miniC言語で記述されたソースコードを読み込み、以下の3つの主要な段階を経て実行します。

  1. 字句解析 (Lexing): ソースコードを意味のある最小単位であるトークンに分割します。

  2. 構文解析 (Parsing): トークンのストリームから、プログラムの構造を表現する抽象構文木 (AST: Abstract Syntax Tree) を構築します。

  3. 実行 (Interpretation): 構築されたASTをトラバースし、各ノードに対応する処理を実行することで、プログラムのロジックを実行します。

本プログラムは、変数、数値、文字列、ブーリアン、リスト、辞書といったデータ型をサポートし、条件分岐 (if/elif/else)、繰り返し (for)、関数定義と呼び出し、そして組み込み関数 (print, exit, len, time, append, insert, open, read, write, close, add) を提供します。特に、ファイルI/Oの機能が強化されており、スクリプト内でファイルの読み書きを行うことができます。

原理

miniC.py の動作は、一般的なインタプリタの設計原理に基づいています。

1. 字句解析 (Lexical Analysis)

字句解析は、正規表現 (re モジュール) を用いて行われます。定義されたトークンルール (TOKENS) は正規表現パターンとトークンタイプ名のペアのリストとして与えられ、これらを結合して一つの巨大な正規表現 TOK_RE がコンパイルされます。 入力コードはこの正規表現によってスキャンされ、マッチしたパターンに基づいて Token オブジェクトが生成されます。NEWLINE, SKIP, COMMENT タイプのトークンは実行処理には関与しないため、この段階で無視されます。

2. 構文解析 (Syntax Analysis)

構文解析は、再帰下降パーサ (Recursive Descent Parser) として実装されています。Parser クラスはトークンのストリームを消費しながら、miniC言語の文法規則に対応するメソッド(例: statement(), expr(), term(), factor() など)を再帰的に呼び出します。これらのメソッドは、文法構造に応じて AST クラスのサブクラスのインスタンスを生成し、それらを組み合わせて抽象構文木を構築します。 演算子の優先順位は、より高位の優先順位を持つ演算子を解析するメソッドが、より低位の優先順位を持つ演算子を解析するメソッドを呼び出すようにすることで実現されます。例えば、factor()term() を呼び出し、term()expr() を呼び出すといった階層構造です。

3. 抽象構文木 (Abstract Syntax Tree, AST)

ASTは、プログラムの構造を表現するツリー状のデータ構造です。AST クラスを基底クラスとし、Number (数値リテラル)、String (文字列リテラル)、Var (変数)、BinOp (二項演算)、If (if文)、For (forループ)、FuncDef (関数定義)、FuncCall (関数呼び出し) などの具体的な言語要素に対応するサブクラスが定義されています。各ASTノードは、その言語要素に関する情報を属性として持ちます。

4. 実行 (Interpretation)

インタプリタ (Interpreter クラス) は、構築されたASTのルートノード (Block オブジェクト) から開始し、ツリーを深さ優先でトラバースします。各ノードに到達すると、そのノードの型に応じて定義された実行ロジック (_exec() メソッドや _eval() メソッド) が呼び出されます。

  • 環境管理: 変数は、スコープのスタック (self.scopes) を用いて管理されます。各スコープは辞書であり、変数を名前で値にマップします。関数呼び出し時や for ループの実行時に新しいスコープがプッシュされ、終了時にポップされることで、変数スコープが適切に管理されます。

  • 組み込み関数: time, len, append, insert などの基本的な組み込み関数に加えて、ファイルI/Oのための open, read, write, close が用意されています。ファイルハンドルはインタプリタ内部で数値IDにマッピングされ、Pythonのファイルオブジェクトを管理します。

  • 制御フロー: if 文や for ループのような制御フローは、条件の評価結果に基づいてASTの特定の部分を実行することで実装されます。return, break, continue は、カスタム例外 (ReturnValue, BreakLoop, ContinueLoop) を発生させることで、通常の実行フローを中断し、適切な処理へとジャンプします。

  • 型の変換と真偽値: miniC言語の型システムは動的であり、Pythonの型にマッピングされます。条件式などで真偽値が必要な場合、_to_bool() メソッドがPythonの真偽値規則に従って値を変換します。

例えば、BinOp ノードの評価 (\(l \ op \ r\)) は以下のように処理されます。

  1. 左側のサブツリー node.l を評価して値 \(l\) を得る。

  2. 右側のサブツリー node.r を評価して値 \(r\) を得る。

  3. 演算子 node.op に応じて、Pythonの対応する演算子 (\(+, -, *, /, \&\&, ||, ==, !=, <, >, <=, >=\)) を \(l\)\(r\) に適用し、結果を返す。

文字列連結演算子 & は、str(l) + str(r) として実装されます。 $\( \text{Result} = \text{str}(l) + \text{str}(r) \)$ これは、miniC言語における任意の型の値を文字列に変換して連結する動作をエミュレートします。

必要な非標準ライブラリとインストール方法

miniC.py は、Pythonの標準ライブラリである sys, re, time のみを使用しており、追加の非標準ライブラリは必要ありません

したがって、特別なインストール手順は不要です。Pythonがインストールされていれば、そのまま実行できます。

必要な入力ファイル

miniC.py が実行するために必要とする入力ファイルは、miniC言語のソースコードが記述されたテキストファイルです。

  • ファイル形式: プレーンテキスト

  • データ構造: miniC言語の文法に従って記述されたプログラムコード。

  • デフォルト名: コマンドライン引数でファイルが指定されなかった場合、デフォルトのファイル名は miniC.mnc です。ただし、現状のコードでは引数が1つ必須のため、デフォルト名が使用されることはありません。

  • エンコーディング: open() 関数で encoding='utf-8' が指定されているため、UTF-8 エンコーディングで保存されている必要があります。

例:

// miniC言語のコメント
int factorial(int n) {
    if (n == 0) {
        return 1;
    } else {
        return n * factorial(n - 1);
    }
}

print("Factorial of 5:", factorial(5));

// ファイル操作の例
str myFile = "output.txt";
double fileHandle = open(myFile, "w");
if (fileHandle != null) {
    write(fileHandle, "Hello from miniC to a file!\n");
    close(fileHandle);
} else {
    print("Error: Could not open file for writing.");
}

生成される出力ファイル

miniC.py は、それ自身が特定の出力ファイルを直接生成することはありません。 プログラムの実行結果は、主に標準出力 (コンソール) に表示されます。

ただし、miniC言語の組み込み関数 open(), write(), close() を使用して、スクリプト内で明示的にファイルを作成し、内容を書き込むことができます。 例えば、上記の「必要な入力ファイル」の例では、output.txt というファイルが生成され、そのファイルには以下の内容が書き込まれます。

Hello from miniC to a file!

生成されるファイルのファイル名や内容は、実行される miniC スクリプトの内容に完全に依存します。

コマンドラインでの使用例 (Usage)

miniC.py をコマンドラインから実行する際の基本的な形式は以下の通りです。

python miniC.py <source_file>
  • python miniC.py: miniC.py スクリプトを実行するためのコマンドです。

  • <source_file>: 実行したい miniC ソースコードが記述されたファイルのパスを指定します。この引数は必須です。

コマンドラインでの具体的な使用例

例 1: 基本的な数値計算と出力

calc.mnc というファイルを作成します。

// calc.mnc
int main() {
    int a = 10;
    int b = 5;
    double result_add = a + b;
    double result_sub = a - b;
    double result_mul = a * b;
    double result_div = a / b;

    print("Addition:", result_add);
    print("Subtraction:", result_sub);
    print("Multiplication:", result_mul);
    print("Division:", result_div);

    // 論理演算
    bool cond1 = (a > b); // true
    bool cond2 = (a == b); // false
    print("a > b:", cond1);
    print("a == b:", cond2);
    print("cond1 && cond2:", cond1 && cond2);
    print("cond1 || cond2:", cond1 || cond2);

    return 0;
}

main();

実行コマンド:

python miniC.py calc.mnc

実行結果の説明:

まず、プログラムは calc.mnc の内容を読み込み、字句解析と構文解析を行い、抽象構文木 (AST) を構築します。その後、そのASTを整形して標準出力に表示します(これはデバッグ情報として表示されます)。 最後に、インタプリタがASTを実行し、main 関数内で定義された変数 ab を使った四則演算および論理演算の結果が print 文によってコンソールに出力されます。

<--- ASTの出力 (省略) --->

run:
Addition: 15.0
Subtraction: 5.0
Multiplication: 50.0
Division: 2.0
a > b: true
a == b: false
cond1 && cond2: false
cond1 || cond2: true

例 2: ファイル操作とリストの利用

file_list_example.mnc というファイルを作成します。

// file_list_example.mnc
int main() {
    str filename = "data.txt";
    double fh_write = open(filename, "w");

    if (fh_write != null) {
        write(fh_write, "apple\n");
        write(fh_write, "banana\n");
        write(fh_write, "cherry\n");
        close(fh_write);
        print("Successfully wrote data to", filename);
    } else {
        print("Error: Could not open", filename, "for writing.");
        return 1;
    }

    double fh_read = open(filename, "r");
    if (fh_read != null) {
        str content = read(fh_read);
        print("\nContent of", filename, ":");
        print(content);

        close(fh_read);

        // コンテンツを行に分割し、リストに格納
        list lines = [];
        // NEW: split関数がminiCにはないので、手動で分割をシミュレート
        // このインタプリタでは単純な文字列操作しかできないため、readした内容をそのまま表示する
        // 実際のsplitのような機能は組み込み関数として追加が必要
        
        print("Length of content string:", len(content));
        
    } else {
        print("Error: Could not open", filename, "for reading.");
        return 1;
    }

    // リストの例
    list my_list = [10, 20, 30];
    print("\nInitial list:", my_list);
    append(my_list, 40);
    print("List after append:", my_list);
    insert(my_list, 1, 15);
    print("List after insert:", my_list);
    print("Element at index 2:", my_list[2]);

    delete my_list[0];
    print("List after deleting first element:", my_list);

    return 0;
}

main();

実行コマンド:

python miniC.py file_list_example.mnc

実行結果の説明:

この例では、ファイルI/O機能とリスト操作の組み込み関数を組み合わせています。 まず、data.txt というファイルを作成し、いくつかの文字列を書き込みます。その後、同じファイルを読み取りモードで開き、内容をコンソールに出力します。

続いて、リストを初期化し、append()insert() 組み込み関数を使って要素を追加・挿入し、その結果を表示します。delete ステートメントを使ってリストの要素を削除する操作も示されています。

また、実行が成功すると、現在のディレクトリに data.txt というファイルが作成され、以下の内容が含まれます。

apple
banana
cherry

コンソール出力は以下のようになります。

<--- ASTの出力 (省略) --->

run:
Successfully wrote data to data.txt

Content of data.txt :
apple
banana
cherry

Length of content string: 21

Initial list: [10.0, 20.0, 30.0]
List after append: [10.0, 20.0, 30.0, 40.0]
List after insert: [10.0, 15.0, 20.0, 30.0, 40.0]
Element at index 2: 20.0
List after deleting first element: [15.0, 20.0, 30.0, 40.0]