LoginSignup
9
11

More than 5 years have passed since last update.

PrologCafeを使ってJava言語でProlog言語で書かれた項の文字列をパースするの巻

Posted at

はじめに

PrologCafeとはJava言語で実装されたProlog言語の処理系です。PrologCafeではPrologの項をTermクラスによって表現しています。しかし、複雑な項をTermクラスで組み立てるには骨が折れます。

例えば以下の項をTermクラスで表現したいとします。

neko :- write(meow), nl

これは普通、節と呼ばれますが、別の書き方をすれば以下のようになります。つまり、節は':-'/2','/2といった複合項を組み合わせて表現されています。ちなみに本体部の長い節では','/2をネストさせて表現しています。

項による節の表現
':-'(neko, ','(write(meow), nl))

すこし脱線しますが、SWI-Prologで試してみましょう。

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/1read/2を使えば入力ストリームをパースして項として読み込むことができます。以下にSWI-Prologでのread/1の例を示します。

bash
$ 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/1read/2を使えば書けば、StringをラップしたStringReaderからTermへ変換できそうです。

実装

先日のメモ「PrologCafeでProlog実行エンジンをシングルスレッドで動かすの巻」で作成したPrologContextクラスを利用します。

手順は以下のとおりです。

  1. 項の文字列をStringReaderに食わせる。
  2. 上記StringReaderread/2の入力ストリームに結びつける。(read/2read/1と違って任意のストリームを結びつけることができます。)
  3. PrologCafeのread/2は読み込みに失敗した場合、カレント出力ストリームにエラーメッセージを書き出すので、set_output/1StringWriterをカレント出力ストリームに結びつけてエラーメッセージを取得できるようにする。
  4. PrologエンジンをグルグルまわすとStringTermに変換される。
PrologUtil.java
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;
    }

}

実験

では実際に動かしてみましょう。

Example.java
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.")とすればStringTermに変換されるわけです。よしよし。(テストはまた別の機会に。)

おわりに

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

9
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
11