はじめに
PrologCafeとはJava言語で実装されたProlog言語の処理系です。PrologCafeではPrologの項をTerm
クラスによって表現しています。しかし、複雑な項をTerm
クラスで組み立てるには骨が折れます。
例えば以下の項をTerm
クラスで表現したいとします。
neko :- write(meow), nl
これは普通、節と呼ばれますが、別の書き方をすれば以下のようになります。つまり、節は':-'/2
や','/2
といった複合項を組み合わせて表現されています。ちなみに本体部の長い節では','/2
をネストさせて表現しています。
':-'(neko, ','(write(meow), nl))
すこし脱線しますが、SWI-Prologで試してみましょう。
$ swipl --quiet
?- assert((neko :- write(meow), nl)).
true.
?- assert(':-'(neko , ','(write(meow), nl))).
true.
?- listing.
:- thread_local thread_message_hook/3.
:- dynamic thread_message_hook/3.
:- volatile thread_message_hook/3.
:- dynamic neko/0.
neko :-
write(meow),
nl.
neko :-
write(meow),
nl.
true.
異なる記法でassert/1
された項がlisting
してみると同じであることが確認できます。
話を戻して、PrologCafeでこの節を組み立ててみましょう。
package maglog;
import jp.ac.kobe_u.cs.prolog.lang.StructureTerm;
import jp.ac.kobe_u.cs.prolog.lang.SymbolTerm;
import jp.ac.kobe_u.cs.prolog.lang.Term;
public class Example {
// 「neko :- write(meow), nl」の項による表現
public static Term makeNeko() throws Exception {
// nl
SymbolTerm nl = SymbolTerm.makeSymbol("nl", 0);
SymbolTerm write_nl = nl;
// write(meow)
SymbolTerm meow = SymbolTerm.makeSymbol("meow", 0);
SymbolTerm write = SymbolTerm.makeSymbol("write", 1);
StructureTerm write_meow = new StructureTerm(write, new Term[] { meow });
// ','(write(meow), nl)
SymbolTerm comma = SymbolTerm.makeSymbol(",", 2);
StructureTerm body = new StructureTerm(comma, new Term[] { write_meow, write_nl });
// neko
SymbolTerm neko = SymbolTerm.makeSymbol("neko", 0);
SymbolTerm head = neko;
// ':-'(neko, ','(write(meow), nl))
SymbolTerm tonbo = SymbolTerm.makeSymbol(":-", 2);
StructureTerm clause = new StructureTerm(tonbo, new Term[] { head, body });
return clause;
}
}
かなりしんどいです。さすがにこれはProlog言語で書きたい場合もあるでしょう。というわけで、今回はJava言語のString
で記述されたPrologの節をTerm
クラスに変換してみようというメモの巻です。
設計
Prologでは、read/1
やread/2
を使えば入力ストリームをパースして項として読み込むことができます。以下にSWI-Prologでのread/1
の例を示します。
$ swipl --quiet
?- read(Term), assert(Term).
|: neko :- write(meow), nl.
Term = (neko:-write(meow), nl).
?- listing.
:- thread_local thread_message_hook/3.
:- dynamic thread_message_hook/3.
:- volatile thread_message_hook/3.
:- dynamic neko/0.
neko :-
write(meow),
nl.
true.
?- neko.
meow
true.
簡単に動作をメモしておくと、まず、read(Term)
がコールされるとプロンプトが?-
ではなく|:
になり標準入力からの入力を受け付けます。ここでキーボードなどからneko :- write(meow), nl.
を入力すると、入力された文字列がパースされ、項となり、assert(Term)
によって節データベースへ格納されます。次に、listing/1
で節データベースの内容を確認しています。最後に、節データベースに格納されたneko/1
をコールしています。
したがって、PrologCafeを使ってJava言語でread/1
やread/2
を使えば書けば、String
をラップしたStringReader
からTerm
へ変換できそうです。
実装
先日のメモ「PrologCafeでProlog実行エンジンをシングルスレッドで動かすの巻」で作成したPrologContext
クラスを利用します。
手順は以下のとおりです。
- 項の文字列を
StringReader
に食わせる。 - 上記
StringReader
をread/2
の入力ストリームに結びつける。(read/2
はread/1
と違って任意のストリームを結びつけることができます。) -
PrologCafeの
read/2
は読み込みに失敗した場合、カレント出力ストリームにエラーメッセージを書き出すので、set_output/1
でStringWriter
をカレント出力ストリームに結びつけてエラーメッセージを取得できるようにする。 - Prologエンジンをグルグルまわすと
String
がTerm
に変換される。
package maglog.prolog;
import java.io.PrintWriter;
import java.io.PushbackReader;
import java.io.StringReader;
import java.io.StringWriter;
import jp.ac.kobe_u.cs.prolog.builtin.PRED_read_2;
import jp.ac.kobe_u.cs.prolog.builtin.PRED_set_output_1;
import jp.ac.kobe_u.cs.prolog.lang.JavaObjectTerm;
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.Term;
import jp.ac.kobe_u.cs.prolog.lang.VariableTerm;
/**
* @author Masayuki Higashino
*/
public class PrologUtil {
public static Term read(String s) throws Exception {
PrologContext context = new PrologContext();
context.init();
StringReader reader = new StringReader(s);
StringWriter writer = new StringWriter();
Term input = new JavaObjectTerm(new PushbackReader(reader));
Term output = new JavaObjectTerm(new PrintWriter(writer));
Term term = new VariableTerm(context.getEngine());
Predicate p1 = new Success(context);
Predicate p2 = new PRED_read_2();
p2.setArgument(new Term[] { input, term }, p1);
Predicate p3 = new PRED_set_output_1();
p3.setArgument(new Term[] { output }, p2);
context.setPredicate(p3);
while (!context.isStopped())
context.execute();
if (context.getResult() == PrologContext.Result.FAILURE)
throw new Exception(writer.toString());
return term;
}
}
実験
では実際に動かしてみましょう。
package maglog;
import maglog.prolog.PrologContext;
import maglog.prolog.PrologUtil;
import jp.ac.kobe_u.cs.prolog.lang.StructureTerm;
import jp.ac.kobe_u.cs.prolog.lang.SymbolTerm;
import jp.ac.kobe_u.cs.prolog.lang.Term;
public class Example {
// 「neko :- write(meow), nl」の項による表現
public static Term makeNeko() throws Exception {
// nl
SymbolTerm nl = SymbolTerm.makeSymbol("nl", 0);
SymbolTerm write_nl = nl;
// write(meow)
SymbolTerm meow = SymbolTerm.makeSymbol("meow", 0);
SymbolTerm write = SymbolTerm.makeSymbol("write", 1);
StructureTerm write_meow = new StructureTerm(write, new Term[] { meow });
// ','(write(meow), nl)
SymbolTerm comma = SymbolTerm.makeSymbol(",", 2);
StructureTerm body = new StructureTerm(comma, new Term[] { write_meow, write_nl });
// neko
SymbolTerm neko = SymbolTerm.makeSymbol("neko", 0);
SymbolTerm head = neko;
// ':-'(neko, ','(write(meow), nl))
SymbolTerm tonbo = SymbolTerm.makeSymbol(":-", 2);
StructureTerm clause = new StructureTerm(tonbo, new Term[] { head, body });
return clause;
}
public static void main(String[] args) {
try {
Term t1 = Example.makeNeko();
Term t2 = PrologUtil.read("neko :- write(meow), nl.");
System.err.println(t2);
System.err.println(t1);
if (t2.equals(t1)) {
System.err.println("equals: OK");
} else {
System.err.println("equals: NG");
}
PrologContext c = new PrologContext();
c.init();
if (t2.unify(t1, c.getEngine().trail)) {
System.err.println("unify: OK");
} else {
System.err.println("unify: NG");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
:-(neko,,(write(meow),nl))
:-(neko,,(write(meow),nl))
equals: OK
unify: OK
Java言語でのequals
とProlog言語でのunify
も成功していますね。PrologUtil.read("neko :- write(meow), nl.")
とすればString
がTerm
に変換されるわけです。よしよし。(テストはまた別の機会に。)
おわりに
ニッチすぎてストックされる気がしない。