COINS で Brainfuck のコンパイラ (2)

とりあえず、コンパイラプログラムのドライバ部分を作ります。コマンドライン引数を読んでファイルを開いたり、オプションを設定したりほげほげほげ……というのは面倒なので、COINS で用意されている coins.driver.Driver クラスを使います。こいつを継承することで、めんどくさいところはだいたいうまいことやってくれます。
Brainfuck のフロントエンドは bffront パッケージとしてまとめて、BFDriver, BFtoHIR という二つのクラスから作ることにします。 BFDriver は coins.driver.Driver を継承した Brainfuck のドライバです。あと一つ、拡張子を設定するファイルも作る必要があります。拡張子指定ファイルは bfsuffix としました。
http://konbu.s13.xrea.com/lib/coins/bffront-070618.jar

% jar xf bffront-070618.jar
% cd bffront
% ls
BFDriver.java  BFtoHIR.java  bfsuffix  build.xml  classes/  hello.bf
% java -cp $CLASSPATH:classes bffront.BFDriver -coins:suffix=bfsuffix -coins:assembler=as -b x86 hello.bf
% ./a.out
% java -cp $CLASSPATH:classes bffront.BFDriver -coins:suffix=bfsuffix -coins:assembler=as -b x86 hello.bf -S
% cat hello.s
 .ident "Coins Compiler version: coins-1.4.2.2 + BackEnd-0.8.1"
/* JavaCG for target:x86 convention:standard */

        .section .text
        .align  4
        .global main
main:
        pushl   %ebp
        movl    %esp,%ebp
.L2:
        leave
        ret

%

見てわかりますが、まだ BFtoHIR が空っぽなので、中身が空っぽのプログラムを吐くだけです。コンパイラの体裁だけ整えたところ。
以下コード。
BFDriver.java

package bffront;

import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;

import coins.HirRoot;
import coins.IoRoot;
import coins.PassException;
import coins.driver.Driver;
import coins.driver.Suffix;
import coins.ir.hir.Program;

/**
 * A Brainfuck driver implementation using the COINS Compiler Driver API.
 **/

public class BFDriver extends Driver{
    /**
     * A main function to invoke driver instance.
     *
     * @param args a command line.
     **/
    public static void main(String[] args){
        new BFDriver().go(args);
    }
    /**
     * HIR tree creation from source code.
     *
     * @param hirRoot an HirRoot object
     * @param suffix suffix rule of the source file.
     * @param in an input stream from which the C source program can be read
     * @param io an IoRoot object
     * @throws IOException any IO error
     * @throws PassException unrecoverable error(s) found in processing
     **/    
    protected void makeHirFromSource(File sourceFile, HirRoot hirRoot, Suffix suffix, InputStream in, IoRoot io)
        throws IOException, PassException {
        if (suffix.getLanguageName().equals("Brainfuck")) {
            try{
                BFtoHIR front = new BFtoHIR(io.symRoot, hirRoot, io);
                try {
                    front.makeHirFromBF(sourceFile, hirRoot);
                } catch (Exception e) { 
                    io.msgError.put("in BF parser: "+e);
                    e.printStackTrace();
                    throw new PassException("BFDriver", "Error(s) in parsing "+sourceFile.getName());
                }
            }
            catch(Exception e){
                io.msgError.put("in BFParser: "+e);
                e.printStackTrace();
            }
            ((Program)hirRoot.programRoot).finishHir();
        }
        else {
            io.msgError.put(sourceFile + ": Unknown programming language: " + suffix.getLanguageName());
            throw new PassException(myName, "Unknown language " + suffix.getLanguageName());
        }
    }
}

coins.driver.Driver の main と makeHirFromSource をオーバーライドしています。あとは BFtoHIR.makeHirFromBF を呼んでるだけです。他はエラー処理とか拡張子チェックとか細々したこと。

BFtoHIR.java

package bffront;

import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.FileInputStream;

import coins.HirRoot;
import coins.IoRoot;
import coins.SymRoot;
import coins.ir.hir.HIR0;
import coins.ir.hir.Program;
import coins.ir.hir.BlockStmt;
import coins.ir.hir.SubpDefinition;
import coins.sym.Subp;
import coins.sym.Sym0;
import coins.sym.SymTable;

public class BFtoHIR {
    private SymRoot symRoot; // Root of symbol information.
    private Sym0 sym;        // Instanceo to make Sym objects.
    private HirRoot hirRoot; // Root of HIR information.
    private HIR0 hir;        // Instance to make HIR0 objects.
    private IoRoot ioRoot;   // Root of input/output-related information.

    private SubpDefinition lMainDef;   // コンパイル結果のmainプログラムのノード

    private BlockStmt lMainBlock = null;

    // Constructor of BFtoHIR
    public BFtoHIR(SymRoot sRoot, HirRoot hRoot, IoRoot iRoot) {
        symRoot = sRoot;
        sym = (Sym0)symRoot.sym;
        hirRoot = hRoot;
        hir = (HIR0)hirRoot.hir;
        ioRoot = iRoot;
        hirRoot.programRoot = hir.program(null, symRoot.symTableRoot, null, null);

        // プログラムのルートのノードとしてprogramノードを作る
        Subp lMain = sym.defineSubp("main".intern(), symRoot.typeVoid);
        lMain.setVisibility(Sym0.SYM_PUBLIC);
        lMain.closeSubpHeader();
        // "main"プログラムのシンボルを作る
        SymTable lSymTable = symRoot.symTableRoot.pushSymTable(lMain);
        // "main"プログラムの記号表として新しいSymTableを作る
        lMainDef = hir.subpDefinition(lMain, lSymTable);
        // "main"プログラムの定義のノードを作る
        ((Program)hirRoot.programRoot).addSubpDefinition(lMainDef);
        lMainBlock = hir.blockStmt(null);
        lMainDef.setHirBody(lMainBlock);
    }
    public void makeHirFromBF(File sourceFile, HirRoot hirRoot)
        throws IOException{
        FileInputStream fis = new FileInputStream(sourceFile);
        int exp;
        while((exp = fis.read()) != -1){
            // exp に対応した HIR コードに変換する。
        }
    }
}

コンストラクタでは、HIR の最低限のヘッダとかだけ用意してます。BF から HIR への変換をするはずのメソッド makeHirFromBF はまだ空っぽですから、どんな BF をコンパイルしたところで出力するファイルは同じです。
http://www.coins-project.org/international/COINSdoc.en/infra/SimpleMain.java なんかは HIR の生成方法がよくわかると思います。

bfsuffix

#SRD, 2, Suffix rule DB file, format version 2
S,              Assembler,      assembly source (need preprocess),      s,-,o
s,              Assembler,      assembly source,                        -,-,o
bf,      Brainfuck,  Brainfuck source,       -,s,o

コンパイルは ant で以下のように。設定ファイルは Makefile じゃなくて build.xml です。

% ant
Buildfile: build.xml

compile:
    [javac] Compiling 2 source files to /home/sun/docs/bffront/classes

buildall:

BUILD SUCCESSFUL
Total time: 4 seconds

test