課題 (10:スプレッドシート)

作業について

今回の課題は以下のパッケージに作成してください。

パッケージの名前
j1.lesson10

作成するクラスの名前は問題ごとに指示があります。下記を参照してください

課題の提出方法については下記を参照してください。

また、別のコンピューター上に移動する際には、下記を参考にプログラムを持ち帰ってください。

新しい内容

今回、スプレッドシートに関する新しい内容はありません。

問題

作成するクラスの名前
VirtualMachineS1

注意

この課題はコンピューターに関する高度な内容を取り扱います。今回の授業の内容について不安のある人は先に別の教材に挑戦してみてください。また、前回のスプレッドシートの問題(j1.lesson09のスクリプトエンジン)を作成していない場合は先にそちらを作成してください。

仮想機械

仮想機械 (Virtual Machine) とは、コンピューターのプログラムとして実行される、仮想的なコンピューターのことです。この課題では、Javaを使って非常に単純な仮想機械を作成します。

この仮想機械は100個の整数を記憶する領域(メモリー)をつかって、11種類の命令を処理します、スクリプトエンジンのときはたった3つの命令だけを処理してもらいましたが、今回は簡単な電卓を作成できるような命令を一通り使えるようにします。

package j1.lesson10;

import gpjava.Spreadsheet;

import javax.swing.JOptionPane;

public class VirtualMachineS1 {

    public static void main(String[] args) {
        new VirtualMachineS1().start();
    }

    void start() {
        Spreadsheet.show();
        Spreadsheet.load();
        State state = createState();
        while (true) {
            if (Spreadsheet.isSet(state.row - 1, 0, "HLT")) {
                JOptionPane.showMessageDialog(null, "終了します");
                break;
            }
            else if (Spreadsheet.isSet(state.row - 1, 0, "SET")) {
                doSet(state);
            }
            else if (Spreadsheet.isSet(state.row - 1, 0, "WRT")) {
                doWrite(state);
            }
            else if (Spreadsheet.isSet(state.row - 1, 0, "ADD")) {
                doAdd(state);
            }
            else if (Spreadsheet.isSet(state.row - 1, 0, "SUB")) {
                doSubtract(state);
            }
            else if (Spreadsheet.isSet(state.row - 1, 0, "MUL")) {
                doMultiply(state);
            }
            else if (Spreadsheet.isSet(state.row - 1, 0, "DIV")) {
                doDivide(state);
            }
            else if (Spreadsheet.isSet(state.row - 1, 0, "BEQ")) {
                doBranchIfEqual(state);
            }
            else if (Spreadsheet.isSet(state.row - 1, 0, "BNE")) {
                doBranchIfNotEqual(state);
            }
            else if (Spreadsheet.isSet(state.row - 1, 0, "BLT")) {
                doBranchIfLessThan(state);
            }
            else if (Spreadsheet.isSet(state.row - 1, 0, "BGT")) {
                doBranchIfGreaterThan(state);
            }
            else {
                break;
            }
        }
    }

    State createState() {
        State state = new State();
        state.row = 1;
        state.memory = createMemory();
        return state;
    }

    int[] createMemory() {
        int[] memory = new int[100];
        for (int i = 0; i < memory.length; i++) {
            memory[i] = -1;
        }
        return memory;
    }

    void doSet(State state) {
        int[] memory = state.memory;

        state.row = state.row + 1;
    }

    void doWrite(State state) {
        int[] memory = state.memory;

        state.row = state.row + 1;
    }

    void doAdd(State state) {

        state.row = state.row + 1;
    }

    void doSubtract(State state) {

        state.row = state.row + 1;
    }

    void doMultiply(State state) {

        state.row = state.row + 1;
    }

    void doDivide(State state) {

        state.row = state.row + 1;
    }

    void doBranchIfEqual(State state) {

    }

    void doBranchIfNotEqual(State state) {

    }

    void doBranchIfLessThan(State state) {

    }

    void doBranchIfGreaterThan(State state) {

    }
}

class State {
    int row;
    int[] memory;
}

命令の種類

この仮想機械には次のような種類の命令があります。

  • 四則演算

    • ADD – メモリー上の2つの整数を加算して、結果をメモリー上に記憶させる
    • SUB – メモリー上の2つの整数を減算して、結果をメモリー上に記憶させる
    • MUL – メモリー上の2つの整数を乗算して、結果をメモリー上に記憶させる
    • DIV – メモリー上の2つの整数を除算して、結果をメモリー上に記憶させる
  • 条件分岐

    • BEQ – メモリー上の2つの整数を比較して、2つの値が同じならば別の命令にジャンプする
    • BNE – メモリー上の2つの整数を比較して、2つの値が違うならば別の命令にジャンプする
    • BLT – メモリー上の2つの整数を比較して、最初の値が次の値より小さいならば別の命令にジャンプする
    • BGT – メモリー上の2つの整数を比較して、最初の値が次の値より大きいならば別の命令にジャンプする
  • その他の命令

    • SET – 定数をメモリー上に記憶させる
    • WRT – メモリー上の値を表示する
    • HLT – 仮想マシンの実行を終了させる

命令の形式

それぞれの命令はスプレッドシートの1行で表します。列数は命令によって異なりますが、いずれも1列目は命令の種類が入っています。

SET 1 0  
SET 0 1  
SET 101 2  
SET 1 3  
ADD 1 3 1
ADD 3 0 3
BLT 3 2 5
WRT 1    
HLT      

命令の2列目以降は、次のうちどれかです。

概要 意味
即値 そのまま計算に使う値として使う整数です。定数をメモリー上に記憶させるSETという命令で使います
インデックス メモリー上の位置を表す整数です。多くの命令でこれを使います
行番号 スプレッドシート上の行番号(1から始まる)を表す整数です。条件分岐などで使います (スクリプトエンジンのBRANCHで指定した値と同じです)

なお、仮想マシンの実行を終了させる「HLT」命令についてはあらかじめ作成してあるプログラムに書いておきました。それぞれの命令については、以降順番に紹介していきます。

a. 値の表示

まずは、メモリー上に値を記憶させた後に、その値を表示してみましょう。以下の2つの命令を使います。

1列目 2列目 3列目 4列目 意味
文字列「SET」 即値 インデックス 2列目の値を、3列目で指定したメモリー上の要素に記憶させる。
この命令が終わったら、次の命令を処理する。
文字列「WRT」 インデックス 2列目で指定したメモリー上の要素を取り出して、メッセージダイアログに表示する。
この命令が終わったら、次の命令を処理する。

この命令を使ったプログラムは、object10-1.txtをダウンロードして確かめられます (5列目には説明を書いておきました)。

SET 100 0   値100をメモリー[0]に記憶させる
SET 200 1   値200をメモリー[1]に記憶させる
WRT 0     メモリー[0]の内容を表示
WRT 1     メモリー[1]の内容を表示
HLT       プログラムを終了

プログラムを書き換えて、上記のファイルを読んだ際に「100」「200」の順にメッセージダイアログを表示するようにしてください。

具体的には、「doSet」「doWrite」メソッドの中身を、それぞれ「SET」「WRT」命令を実行するように書き換えてください。

引数の State クラスのインスタンスは、コンピューターの状態を複合データとして表しています。この State クラスを簡単に説明すると、次のような形式です。

フィールド 意味
row 現在実行中の命令の、スプレッドシート上の行番号(1から始まる)を表す。
それぞれの命令の処理でこの値を変更すると、次に処理される命令が変わる。
memory 長さ100の整数配列で、このコンピューターの「メモリー」を表す。

注意する点として、フィールドrowはそれぞれの命令を処理するメソッドで、次の命令の行番号を表すようにする必要があります。今回変更してもらう「doSet」や「doWrite」命令ではすでにこのフィールドを1加算する命令を書いていますので、それを使うのがよいと思います。

また、フィールドrowは1から始まる行番号を使っています。「現在の行の2列目のデータ」を利用する際には、次のように行番号と列番号を1ずつ引いてやる必要があります。

Spreadsheet.getInt(state.row - 1, 2 - 1)

b. 四則演算

次に、四則演算を行ってみましょう。次のような4つの命令を追加します。

1列目 2列目 3列目 4列目 意味
文字列「ADD」 インデックス インデックス インデックス 2列目で指定したメモリー上の要素と、3列目で指定したメモリー上の要素を足した結果を、
4列目で指定したメモリー上の要素に記憶させる。
この命令が終わったら、次の命令を処理する。
文字列「SUB」 インデックス インデックス インデックス 2列目で指定したメモリー上の要素から、3列目で指定したメモリー上の要素を引いた結果を、
4列目で指定したメモリー上の要素に記憶させる。
この命令が終わったら、次の命令を処理する。
文字列「MUL」 インデックス インデックス インデックス 2列目で指定したメモリー上の要素と、3列目で指定したメモリー上の要素を掛けた結果を、
4列目で指定したメモリー上の要素に記憶させる。
この命令が終わったら、次の命令を処理する。
文字列「DIV」 インデックス インデックス インデックス 2列目で指定したメモリー上の要素を、3列目で指定したメモリー上の要素で割った結果を、
4列目で指定したメモリー上の要素に記憶させる。
この命令が終わったら、次の命令を処理する。

この命令を使ったプログラムは、object10-2.txtをダウンロードして確かめられます (5列目には説明を書いておきました)。

SET 100 0   値100をメモリー[0]に記憶させる
SET 50 1   値50をメモリー[1]に記憶させる
ADD 0 1 10 メモリー[0]の値とメモリー[1]の値を足してメモリー[10]に記憶させる
SUB 0 1 11 メモリー[0]の値からメモリー[1]の値を引いてメモリー[11]に記憶させる
MUL 0 1 12 メモリー[0]の値とメモリー[1]の値を掛けてメモリー[12]に記憶させる
DIV 0 1 13 メモリー[0]の値をメモリー[1]の値で割ってメモリー[13]に記憶させる
WRT 10     メモリー[10]の内容を表示
WRT 11     メモリー[11]の内容を表示
WRT 12     メモリー[12]の内容を表示
WRT 13     メモリー[13]の内容を表示
HLT       プログラムを終了

プログラムを書き換えて、上記のファイルを読んだ際に「150」「50」「5000」「2」の順にメッセージダイアログを表示するようにしてください。

具体的には、「doAdd」「doSubtract」「doMultiply」「doDivide」メソッドの中身を、それぞれ「ADD」「SUB」「MUL」「DIV」命令を実行するように書き換えてください。

c. 条件分岐

最後に、if文のような条件分岐を行う命令を4つ追加します。

1列目 2列目 3列目 4列目 意味
文字列「BEQ」 インデックス インデックス 行番号 2列目で指定したメモリー上の要素と、3列目で指定したメモリー上の要素が同じ内容であれば、
次の命令を4列目に指定した行番号(1から始まる)から処理を始める。
そうでなければ、この命令が終わったら次の命令を処理する。
文字列「BNE」 インデックス インデックス 行番号 2列目で指定したメモリー上の要素と、3列目で指定したメモリー上の要素が違う内容であれば、
次の命令を4列目に指定した行番号(1から始まる)から処理を始める。
そうでなければ、この命令が終わったら次の命令を処理する。
文字列「BLT」 インデックス インデックス 行番号 2列目で指定したメモリー上の要素が、3列目で指定したメモリー上の要素よりも小さければ、
次の命令を4列目に指定した行番号(1から始まる)から処理を始める。
そうでなければ、この命令が終わったら次の命令を処理する。
文字列「BGT」 インデックス インデックス 行番号 2列目で指定したメモリー上の要素が、3列目で指定したメモリー上の要素よりも大きければ、
次の命令を4列目に指定した行番号(1から始まる)から処理を始める。
そうでなければ、この命令が終わったら次の命令を処理する。

この命令を使ったプログラムは、

をダウンロードしてそれぞれ確かめられます (5列目には説明を書いておきました)。以下は、object10-4.txtの内容です。

SET 1 0   値1をメモリー[0]に記憶させる
SET 2 1   値2をメモリー[1]に記憶させる
SET 1 2   値1をメモリー[2]に記憶させる
BNE 0 2 6 メモリー[0]!=メモリー[2]のとき、6行目へ
BNE 0 1 7 メモリー[0]!=メモリー[1]のとき、7行目へ
HLT       ここに来たら失敗
WRT 0     ここに来たら正解
HLT       プログラムを終了

プログラムを書き換えて、上記のファイルを読んだ際に「150」「50」「5000」「2」の順にメッセージダイアログを表示するようにしてください。

具体的には、「doBranchIfEqual」「doBranchIfNotEqual」「doBranchIfLessThan」「doBranchIfGreaterThan」メソッドの中身を、それぞれ「BEQ」「BNE」「BLT」「BGT」命令を実行するように書き換えてください。

これらの命令は、引数に取ったStateクラスのインスタンスのうち、条件に応じてフィールドrowを変更します。今回はこのフィールドを次に進める命令をあらかじめ書いていませんので、考えながらプログラムを書きすすめて下さい。

おまけ

次のように、繰り返し処理も書けます (object10-7.txt)。これの結果は何になるでしょうか?余裕があれば考えてみてください。

SET 1 0   ONE = 1;
SET 0 1   total = 0;
SET 101 2   n = 101;
SET 1 3   i = 1;
ADD 1 3 1 total = total + i
ADD 3 0 3 i = i + ONE
BLT 3 2 5 if (i < n) goto 5
WRT 1     write(total)
HLT