21 スクリプトをファイルから読み込んで実行する

# はじめに

ここまでインタープリタを実装してきましたが、スクリプトはプログラムソースへ直接記述していました。
この記事ではスクリプトをテキストファイルから読み込んで実行できるようにします。

この記事は「[スタティックメソッド呼び出しに対応する](http://qiita.com/quwahara/items/a8ef47f78b1479a117de)」の続きです。

# スクリプトをファイルから読み込んで実行でやりたいこと

スクリプトをファイルから読み込んで実行でやりたいことを確認します。

例えば下のようなスクリプトを記述したテキストファイルがあります。
このスクリプトは0から6までのフィボナッチ数を出力するプログラムです。
このスクリプトを実行し、フィボナッチ数が出力されることを目指します。

```JS:fibo.txt
function fibonacci(n) {
  if (n == 0) {
    return 0
  } else if (n == 1) {
    return 1
  } else {
    return fibonacci(n - 2) + fibonacci(n - 1)
  }
}

var i = 0
while (i <= 6) {
  var n = fibonacci(i)
  println("f(" + i.toString() + ") -> " + n.toString())
  i = i + 1
}
```

# 実装の仕方

コマンドライン引数からファイル名を取得して、そのファイル内容を読み込みます。
読み込んだ内容を、字句解析(Lexer)、構文解析(Parser)、インタプリタ(Interpreter)と順に処理にかけていくクラスを導入します。
その様な処理を行うドライバ(Driver)クラスを導入します。

# Javaで実装してみる

実装にうつります。
導入したドライバ(Driver)クラスを順にみてみます。

## Driver.java

Driver.javaの実装です。

Driverクラスのコンストラクタです。
後の処理で使えるように、字句解析(Lexer)、構文解析(Parser)、インタプリタ(Interpreter)をインスタンス化して準備します。

```Java:Driver.java
public class Driver {

    Lexer lexer;
    Parser parser;
    Interpreter interpreter;
    
    public Driver() {
        lexer = new Lexer();
        parser = new Parser();
        interpreter = new Interpreter();
    }
```

スクリプト実行を行う`execute()`メソッドです。
`execute()`メソッドでは、次の処理を順に実行します。

* コマンドライン引数の妥当性検証
* ファイル内容の読込み
* ファイル内容のスクリプトの実行

`variables`フィールド変数は、実行結果になる実行後のインタープリタのグローバル変数の値を保持します。
`exception`フィールド変数は、実行中に投入された例外を保持します。

```Java:Driver.java
    public Map<String, Interpreter.Variable> variables;
    public Exception exception;
    
    public int execute(String[] args) {
        try {
            validateArguments(args);
            String path = args[0];
            String text = readText(path);
            variables = run(text);
            return 0;
        } catch (Exception e) {
            exception = e;
            System.err.println(e.getMessage());
            return -1;
        }
    }
```

コマンドライン引数の妥当性を検証する`validateArguments()`メソッドです。
コマンドライン引数が1つ指定され、それがファイルであることを検証します。
検証結果が正しくなければ例外を投入します。

```Java:Driver.java
    public static final String ARGS_NULL = "Arguments were null.";
    public static final String NO_ARGS = "No Arguments.";
    public static final String FILE_NOT_FOUND = "File not found.: ";
    public static final String PATH_NOT_FILE = "The path was not a file.: ";
    
    public void validateArguments(String[] args) throws Exception {
        if (args == null) {
            throw new Exception(ARGS_NULL);
        }
        if (args.length != 1) {
            throw new Exception(NO_ARGS);
        }
        String path = args[0];
        File f = new File(path);
        if (!f.exists()) {
            throw new FileNotFoundException(FILE_NOT_FOUND + path);
        }
        if (!f.isFile()) {
            throw new Exception(PATH_NOT_FILE + path);
        }
    }

```

ファイル内容を読み込んで返す`readText`メソッドです。
ファイル内容の文字コードはUTF-8であることを前提にしています。

```Java:Driver.java
    public String readText(String path) throws IOException {
        return new String(Files.readAllBytes(Paths.get(path)), StandardCharsets.UTF_8);
    }
```

ファイル内容のスクリプトを実行する`run()`メソッドです。
字句解析(Lexer)、構文解析(Parser)、インタプリタ(Interpreter)と順に処理へかけていきます。

```Java:Driver.java
    public Map<String, Interpreter.Variable> run(String text) throws Exception {
        exception = null;
        variables = null;
        List<Token> tokens = lexer.init(text).tokenize();
        List<Token> blk = parser.init(tokens).block();
        return interpreter.init(blk).run();
    }
```

実行時の開始点になる`main()`メソッドです。
Driverクラスをインスタンス化し、`execute()`メソッドでスクリプト実行を行います。

```Java:Driver.java
    public static void main(String[] args) {
        Driver driver = new Driver();
        int status = driver.execute(args);
        System.exit(status);
    }
    
}
```

実装は以上です。


ビルドした Driver.class でスクリプトファイルの fibo.txt を実行した様子です。
Driver.class と fibo.txt は同じディレクトリにあるものとします。

```bash
$ java Driver fibo.txt
f(0) -> 0
f(1) -> 1
f(2) -> 1
f(3) -> 2
f(4) -> 3
f(5) -> 5
f(6) -> 8
```

ありがとうございました。

# おわりに

ソースの全文はこちらで公開しています。

**Calc**
https://github.com/quwahara/Calc/tree/article-21-driver/Calc/src/main/java