Java
Parser
parser_combinator

Javaで始めるパーザコンビネータの作り方(6)~(Hello)?~

前回の更新から間が空いてしまいました。これまでで、

  • 特定の文字列のパーズ
  • パーザ同士の連接
  • パーザ同士の選択
  • パーザの(0回以上の)繰り返し

ができるようになりました。今回は、あるパーザが0回または1回のときにマッチするパーザを作ります。正規表現における ? に相当するものですね。

次のパーザと入力文字列、結果の値 java.util.Optional<A> があったとします。

Parser<A> p = ...;
String input = ...;
Optional<A> values = ...;

0回または1回ということは、パーズは必ず成功しなければいけません。ということで、コードは以下のようになります:

ParseResult<T> result = parser.invoke(input);
if(result instanceof ParseResult.Failure<?>) {
  return new ParseResult.Success<>(Optional.ofNullable(null), input);
} else {
  ParseResult.Success<T> success = (ParseResult.Success<T>)result;
  return new ParseResult.Success<>(Optional.of(success.value), success.next);
}

parser は0回または1回繰り返すパーザです。それに対して input をまずパーズさせます。次に、結果が失敗か成功によって分岐します。

  • 失敗の場合:成功して、中身の値は、空の Optional になる。残りの文字列は変化なし
  • 成功の場合:成功して、中身の値は、空でない Optional になる。残りの文字列が変化

あらためて書きますが、繰り返しのときと同様に、いずれにせよ成功するということがポイントです。

今までと同様に、このコードをクラスの中に閉じ込めます。

public class OptionParser<T> implements Parser<Optional<T>> {
  private Parser<T> parser;
  public OptionParser(Parser<T> parser) {
    this.parser = parser;
  }

    @Override
    public ParseResult<Optional<T>> invoke(String input) {
        ParseResult<T> result = parser.invoke(input);
        if(result instanceof ParseResult.Failure<?>) {
            return new ParseResult.Success<>(Optional.ofNullable(null), input);
        } else {
            ParseResult.Success<T> success = (ParseResult.Success<T>)result;
            return new ParseResult.Success<>(Optional.of(success.value), success.next);
        }
    }
}
public interface Parser<T> {
    ParseResult<T> invoke(String input);
    static Parser<String> string(String literal) {
        return new StringParser(literal);
    }
    static Parser<String> EOF() {
        return new EOFParser();
    }
    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<>(this);
    }
    default Parser<Optional<T>> option() {
        return new OptionParser<>(this);
    }
}

動作を確認します。

package java;
import java.util.List;
import java.util.Optional;

public class Main {
    public static void main(String[] args) {
        Parser<Optional<String>> hello = Parser.string("Hello").option();
        System.out.println(hello.invoke(""));
        System.out.println(hello.invoke("Hello"));
        System.out.println(hello.invoke("HelloHello"));
        System.out.println(hello.invoke("HelloHelloHello"));
    }
}

実行結果は次のようになります。

Success(Optional.empty, )
Success(Optional[Hello], )
Success(Optional[Hello], Hello)
Success(Optional[Hello], HelloHello)

hello パーザが失敗した場合も含めて、必ず成功になっていることがわかります。

次回は、否定パーザについて説明します。否定パーザは、これまでのパーザと違って入力を消費しないという点
でやや特殊ですが、それほど難しいものでもありません。