Javaで始めるパーザコンビネータの作り方(5)~補足

前回までで、

  • 特定の文字列のパーズ
  • パーザ同士の連接
  • パーザ同士の選択
  • パーザの繰り返し

を構成できるようになりましたが、重要な点を飛ばして説明していました。今回は、その点について説明しようと思います。

EOFパーザ

今までの例では、パーズ対象の文字列がちょうど全部消費されるものばかりだったので、末尾にパーズされないゴミがくっついた場合どのようになるのかについて説明していませんでした。これは試してみるのが手っ取り早いです。次のコードを実行してみます。

package parser;
public class Main {
    public static void main(String[] args) {
        Parser<List<String>> hellos = Parser.string("Hello").many();
        System.out.println(hellos.invoke("Hello, World!"));
    }
}

実行結果は、

Success([Hello], , World!)

となりました。パーズされなかった、", World"がゴミとして残ってしまっていることがわかります。

実際にパーズするときには、末尾に何かゴミがあったときはエラーになって欲しいのでこれは困ります。そこで、入力の末尾、つまり残りの文字列が何もない場合にのみマッチするパーザを作成してみます。EOF(ファイルの末尾)にマッチするので、 EOFParser とします。

package parser;
public class EOFParser implements Parser<String> {
    @Override
    public ParseResult<String> invoke(String input) {
        if(input.length() != 0) {
            return new ParseResult.Failure<>("expected: EOF, actual: " + input.charAt(0), input);
        } else {
            return new ParseResult.Success<String>("", "");
    }
    }
}

入力文字列は引数として与えられ、これが空文字列の場合にのみEOFと判定すればいいので、このようなコードになります。簡単ですね。

package parser;
public interface Parser<T> {
    ParseResult<T> invoke(String input);
    static Parser<String> string(String literal) {
        return new StringParser(literal);
    }
    default Parser<T> or(Parser<T> rhs) {
        return new Or<>(this, rhs);
    }
    default <U> Parser<Tuple2<T, U>> cat(Parser<U> rhs) {
        return new Cat<>(this, rhs);
    }
    default Parser<List<T>> many() {
        return new ManyParser<T>(this);
    }
    static Parser<String> EOF() {
        return new EOFParser();
    }
}

この EOFParser を使って先程のパーザを書き直してみます。

public class Main {
    public static void main(String[] args) {
        Parser<Tuple2<List<String>, String>> hellos = Parser.string("Hello").many().cat(Parser.EOF());
        System.out.println(hellos.invoke("Hello"));
        System.out.println(hellos.invoke("Hello, World!"));
    }
}

出力は次のようになります。

Success(Tuple2{item1=[Hello], item2=}, )
Failure(expected: EOF, actual: ,, , World!

無事、失敗して欲しいケースで失敗するようになりました。これからは、パーザの末尾には EOFParser を挿入していくようにして、正確にパーズできるようにしましょう。