以下、すみませんが記事としてしばらく読むに耐えない状態で放置させていただきます…
文字列のパース
文字列をパースするために Utf32Iterator なるクラスと IteratorInput なるクラスを作成。サロゲートペアも考慮するぞ。
StringParser.java
public class StringParser {
// 文字列を unicode codepoint として取り扱う iterator。サロゲートペアも考慮する。
public static class Utf32Iterator implements Iterator<Integer> {
private int position, nextCodePoint = -1;
private final String source;
public Utf32Iterator(String source_) {source = source_; position = 0;}
@Override public boolean hasNext() {
nextCodePoint = -1;
return position <= source.length(); // position == source.length() のケースは EOF。nullを返して、その次から EndOfInputExceptionとする。
}
@Override public Integer next() {
if (position == source.length()) { // 文字列の長さぴったりの場合には EOF。nullをかえす。
position ++;
return null;
}
if (nextCodePoint < 0) { // nextCodePoint が未設定状態なら一度だけ codePointAt で文字を取得して値を設定する。
nextCodePoint = source.codePointAt(position);
position = source.offsetByCodePoints(position, 1);
}
return nextCodePoint;
}
}
// iterator を渡して Parser にかけるための Inputクラス
public static class IteratorInput<T> implements Input<T> {
private final Iterator<T> iterator;
private final int position;
private final T current;
public IteratorInput(Iterator<T> iterator_) {iterator = iterator_; position = 0; current = iterator.hasNext() ? iterator.next(): null;}
public IteratorInput(Iterator<T> iterator_, int position_) {iterator = iterator_; position = position_; current = iterator.hasNext() ? iterator.next() : null;}
@Override public T current() {return current;}
@Override public String positionDescription() {return "" + position;}
private IteratorInput<T> next = null; // or の関係で複数回 next が要求される場合があるので、キャッシュを保持する
@Override public Input<T> next() throws EndOfInputException {
if (next != null) return next;
if (iterator.hasNext()) return (next = new IteratorInput<T>(iterator, position + 1)); throw new EndOfInputException();
}
}
// unicode codepoint の List を文字列に積なげる
public static Parser<Integer, String> concat(Parser<Integer, List<Integer>> parser) {
return apply(reduce(parser, () -> new StringBuilder(), (sb, i) -> sb.appendCodePoint(i)), sb -> sb.toString());
}
public static Parser<Integer, String> concatStr(Parser<Integer, List<String>> parser) {
return apply(reduce(parser, () -> new StringBuilder(), (sb, i) -> sb.append(i)), sb -> sb.toString());
}
// str に含まれる文字を一文字だけ通す parser
public static Parser<Integer, Integer> consistsOf(String str) {return satisfy(i -> str.indexOf(i) >= 0);}
// str と同じ文字列を消費する parser
public static Parser<Integer, String> word(String str) {
List<Parser<Integer, Integer>> result = new ArrayList<>();
str.chars().forEach(i -> result.add(satisfy(j -> j == i)));
return concat(lst(result));
}
public static String codePointToString(int[] codePoint) {return new String(codePoint, 0, codePoint.length);}
public static String codePointToString(int codePoint) {return codePointToString(new int[] {codePoint});}
// str と同じ文字列がでてくるまで入力を消費する parser
public static Parser<Integer, String> until(String str) {
ParserMemoizer<Integer, String> result = new ParserMemoizer<Integer, String>();
result.defun(() -> or(word(str), apply(seq(satisfy(i -> true) /* 任意の一文字を消費する parser */ , result), tpl2 -> codePointToString(tpl2.car) + tpl2.cdr.car)));
return result;
}
}