はじめに
ここまでインタープリタを実装してきましたが、スクリプトはプログラムソースへ直接記述していました。
この記事ではスクリプトをテキストファイルから読み込んで実行できるようにします。
この記事は「スタティックメソッド呼び出しに対応する」の続きです。
スクリプトをファイルから読み込んで実行でやりたいこと
スクリプトをファイルから読み込んで実行でやりたいことを確認します。
例えば下のようなスクリプトを記述したテキストファイルがあります。
このスクリプトは0から6までのフィボナッチ数を出力するプログラムです。
このスクリプトを実行し、フィボナッチ数が出力されることを目指します。
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)をインスタンス化して準備します。
public class Driver {
Lexer lexer;
Parser parser;
Interpreter interpreter;
public Driver() {
lexer = new Lexer();
parser = new Parser();
interpreter = new Interpreter();
}
スクリプト実行を行うexecute()
メソッドです。
execute()
メソッドでは、次の処理を順に実行します。
- コマンドライン引数の妥当性検証
- ファイル内容の読込み
- ファイル内容のスクリプトの実行
variables
フィールド変数は、実行結果になる実行後のインタープリタのグローバル変数の値を保持します。
exception
フィールド変数は、実行中に投入された例外を保持します。
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つ指定され、それがファイルであることを検証します。
検証結果が正しくなければ例外を投入します。
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であることを前提にしています。
public String readText(String path) throws IOException {
return new String(Files.readAllBytes(Paths.get(path)), StandardCharsets.UTF_8);
}
ファイル内容のスクリプトを実行するrun()
メソッドです。
字句解析(Lexer)、構文解析(Parser)、インタプリタ(Interpreter)と順に処理へかけていきます。
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()
メソッドでスクリプト実行を行います。
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 は同じディレクトリにあるものとします。
$ 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