PrologCafeでProlog実行エンジンをシングルスレッドで動かすの巻


## はじめに

[PrologCafe](http://kaminari.istc.kobe-u.ac.jp/PrologCafe/)とはJava言語で実装されたProlog言語の処理系である。Prolog言語のソースコードをJava言語に変換したり、Java言語でProlog言語の項や節や述語を記述したりして、それらをJVMで実行できる。

PrologCafeは主に以下のクラスから構成されている。（他にも様々な構成要素が有るが今回の説明に必要な分だけ列挙してある。）

- `Prolog`クラス: Prologの実行エンジン
- `PrologControl`クラス: Prologの実行エンジンを制御するコントローラ
- `Predicate`クラス: Prologの述語を表現する抽象クラス
- （参考）`Term`クラス: Prologの項を表現する抽象クラス

PrologCafeは、並行処理をサポートしており、1つのJVMプロセスで複数のPrologの実行エンジンをマルチスレッドで動作させることができる。例えば、[PrologControl (Prolog Cafe v1.2 API Specification)](http://kaminari.istc.kobe-u.ac.jp/PrologCafe/javadoc/jp/ac/kobe_u/cs/prolog/lang/PrologControl.html)のコーディング例では、複数の`PrologControl`インスタンスを生成し、複数のPrologの実行エンジン（`Prolog`インスタンス）をそれぞれ異なるスレッドでラップして制御している。

しかし、 `PrologControl`インスタンスは、内部でスレッドを必ず生成するため、スレッドを生成せずにPrologの実行エンジンを使いたい場合に勝手が良くない。エンジンを生成する度にスレッドも生成されては困る場合もあるだろう。

そこで、本稿では **スレッドを生成せずに** Prologの実行エンジンを制御するコントローラの作成方法を記録する。なお、本稿で扱うPrologCafeのバージョンは`1.2.5`とする。

## 設計

### 方針

`PrologControl`インスタンスは、`Prolog`インスタンスの制御だけでなく、ハンドラの役割も持っている。例えば、`Prolog`インスタンスは、自身（実行エンジン）が`success`あるいは`fail`した場合、`PrologControl#success`や`PrologControl#fail`を呼ぶことで、`PrologControl`に成功または失敗したことを通知している。

従って、`Prolog`は`PrologControl`と相互に依存しており、言い換えれば相互に参照している。このため、`Prolog`インスタンスは単体で動作させることができず、`PrologControl`のインスタンスも動作に必須となっている。

設計方針として、これらの相互依存を断ち切る方法もあるが、大掛かりな変更を要する（動かなくなるサンプルが多量に出てくると思われる）ため、今回は元のクラスの変更は控えて継承を用いる方針とする。

### 手順

大まかな手順は以下のとおり。要するに、スレッド関連のメソッドを全て潰して、状態モデルとAPIを再定義する。

1. `PrologControl`を継承する。
2. `PrologControl`の全てのメソッドをオーバーライドする。
3. `PrologControl`のスレッド制御に関する全てのメソッドで`UnsupportedOperationException`をスローさせる。
4. スレッドの状態遷移モデルをベースに設計されているので、スレッドを使わない状態遷移モデルを新たに定義する。
5. 実行結果の表現の設計が甘いので再定義する。
6. ちなみに、あんまり好みじゃないメソッドもついでに`UnsupportedOperationException`をスローさせている。

## 実装

こんな感じ。

```java:PrologContext
package maglog.prolog;

import java.lang.annotation.ElementType;
import java.lang.annotation.Target;

import jp.ac.kobe_u.cs.prolog.builtin.PRED_call_1;
import jp.ac.kobe_u.cs.prolog.lang.Predicate;
import jp.ac.kobe_u.cs.prolog.lang.Prolog;
import jp.ac.kobe_u.cs.prolog.lang.PrologControl;
import jp.ac.kobe_u.cs.prolog.lang.PrologException;
import jp.ac.kobe_u.cs.prolog.lang.Success;
import jp.ac.kobe_u.cs.prolog.lang.SystemException;
import jp.ac.kobe_u.cs.prolog.lang.Term;

/**
 * スレッドレス版の実行エンジンのコントローラ
 * 
 * @author Masayuki Higashino
 */
public class PrologContext extends PrologControl {

	/**
	 * 実行エンジンの状態
	 * 
	 * @author Masayuki Higashino
	 */
	public enum State {
		NEW, RUNNABLE, STOPPED
	}

	/**
	 * 実行エンジンの処理結果
	 * 
	 * @author Masayuki Higashino
	 */
	public enum Result {
		SUCCESS, FAILURE
	}

	private static final long serialVersionUID = -327097420774349657L;
	private Prolog engine;
	private Predicate code;
	private State state;
	private Result result;

	public PrologContext() {
		engine = new Prolog(this);
		code = null;
		state = State.NEW;
		result = null;
	}

	public Prolog getEngine() {
		return engine;
	}

	public Predicate getCode() {
		return code;
	}

	public State getState() {
		return state;
	}

	public Result getResult() {
		return result;
	}

	public boolean isStopped() {
		return state == State.STOPPED;
	}

	public void init() {
		engine.init();
	}

	public void setPredicate(Predicate code) {
		this.code = code;
	}

	@Override
	public void setPredicate(Term code) {
		this.code = new PRED_call_1();
		this.code.setArgument(new Term[] { engine.copy(code) }, new Success(this));
	}

	public Result execute() throws PrologException {
		state = State.RUNNABLE;
		main_loop: while (true) {
			while (engine.exceptionRaised == 0) {
				if (state != State.RUNNABLE)
					break main_loop;
				code = code.exec(engine);
			}
			switch (engine.exceptionRaised) {
			case 1: // halt/0
				break main_loop;
			case 2: // freeze/2
				throw new SystemException("freeze/2 is not supported yet");
			default:
				throw new SystemException("Invalid value of exceptionRaised");
			}
		}
		return result;
	}

	// マーカーアノテーションを使ってみたかっただけ。無くてもよし。
	@Target(ElementType.METHOD)
	private @interface Handler {
	}

	@Handler
	@Override
	protected void success() {
		state = State.STOPPED;
		result = Result.SUCCESS;
	}

	@Handler
	@Override
	protected void fail() {
		state = State.STOPPED;
		result = Result.FAILURE;
	}

	@Override
	public void start() {
		throw new UnsupportedOperationException();
	}

	@Override
	public void stop() {
		throw new UnsupportedOperationException();
	}

	@Override
	public void join() {
		throw new UnsupportedOperationException();
	}

	@Override
	public boolean ready() {
		throw new UnsupportedOperationException();
	}

	@Override
	public void run() {
		throw new UnsupportedOperationException();
	}

	@Override
	public void cont() {
		throw new UnsupportedOperationException();
	}

	@Override
	public boolean next() {
		throw new UnsupportedOperationException();
	}

	@Override
	public boolean execute(Predicate p, Term[] args) {
		throw new UnsupportedOperationException();
	}

	@Override
	public boolean call() {
		throw new UnsupportedOperationException();
	}

	@Override
	public boolean redo() {
		throw new UnsupportedOperationException();
	}

	@Override
	public boolean in_success() {
		throw new UnsupportedOperationException();
	}

	@Override
	public boolean in_failure() {
		throw new UnsupportedOperationException();
	}

	@Override
	public void setPredicate(Predicate code, Term[] args) {
		throw new UnsupportedOperationException();
	}

}
```

## 実験

Java言語でProlog言語の`hello, world`を書いてみる。

```java:Example.java
package maglog;

import java.io.PrintWriter;
import maglog.prolog.PrologContext;
import jp.ac.kobe_u.cs.prolog.builtin.PRED_flush_output_0;
import jp.ac.kobe_u.cs.prolog.builtin.PRED_nl_0;
import jp.ac.kobe_u.cs.prolog.builtin.PRED_write_1;
import jp.ac.kobe_u.cs.prolog.lang.Predicate;
import jp.ac.kobe_u.cs.prolog.lang.Success;
import jp.ac.kobe_u.cs.prolog.lang.SymbolTerm;
import jp.ac.kobe_u.cs.prolog.lang.Term;

public class Example {

	public static void main(String[] args) {
		PrologContext context = new PrologContext();
		context.init();
		context.getEngine().setCurrentOutput(new PrintWriter(System.out));

		Predicate p1 = new Success(context);
		Predicate p2 = new PRED_flush_output_0();
		p2.setArgument(new Term[] {}, p1);
		Predicate p3 = new PRED_nl_0();
		p3.setArgument(new Term[] {}, p2);
		Predicate p4 = new PRED_write_1();
		p4.setArgument(new Term[] { SymbolTerm.makeSymbol("hello, world") }, p3);

		context.setPredicate(p4);
		while (!context.isStopped())
			context.execute();
	}
}

```

```bash:出力
hello, world
```

## おわりに

ニッチすぎてストックされる気がしない。