PEGParserCombinators

2016/04/30 ARRAYとOBJECTで要素0のとき未対応だったので修正しました。

kmizuさんがJava版のパーサコンビネータを作っていたのでそれを使ってみた。
サンプルとして、JSONのパーサを作ってみた。


以下は、kmizuさんのパーサコンビネータを改造したもの。

package jp.gr.java_conf.mizu;

import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static java.lang.String.format;

/**
 * kmizuさんのPEGParserCombinatorsを改造した。
 * ・整形し、コメントを入れた。
 * ・正規表現を使って解析するパーサregex()を追加した。
 * ・repsec(), rep1sep()を追加した。
 * ・or()を可変長引数にした。
 * @see http://kmizu.hatenablog.com/entry/20090225/1235576560
 */
public class PEGParserCombinators2 {

    /** ペア */
    public static final class Pair<A, B> {
        public final A _1;
        public final B _2;
        public Pair(A _1, B _2) { this._1 = _1; this._2 = _2; }
        public String toString() { return format("(%s, %s)", _1, _2); }
    }

    /** 解析結果 */
    public static final class ParseResult<T> {
        public final T value;
        public final String rest;
        public ParseResult(T value, String rest) {
            this.value = value; this.rest = rest;
        }
        public String toString() {
            return format("(value = %s, rest = %s)", value, rest);
        }
    }

    /** パーサ・インターフェース */
    public interface Parser<T> {
        ParseResult<? extends T> parse(String input);
    }

    public interface Transformer<A, B> {
        B apply(A param);
    }

    public static class ParserProxy<T> implements Parser<T> {
        public Parser<? extends T> target;

        public ParserProxy(Parser<? extends T> target) {
            this.target = target;
        }

        public ParseResult<? extends T> parse(String input) {
            return target.parse(input);
        }
    }

    /** パーサのプロキシを作る関数。 */
    public static <T> ParserProxy<T> proxy(Parser<T> p) {
        return new ParserProxy<T>(p);
    }

    /*** 指定文字列から始まっているかどうかを解析するパーサ */
    public static Parser<String> string(final String param) {
        return new Parser<String>() {
            public ParseResult<? extends String> parse(String input) {
                return input.startsWith(param) ? new ParseResult<String>(param, input.substring(param.length())) : null;
            }
        };
    }

    /*** 正規表現を使って解析するパーサ */
    public static Parser<String> regex(final String regex) {
        return new Parser<String>() {
            Pattern p = Pattern.compile(regex);

            public ParseResult<? extends String> parse(String input) {
                Matcher m = p.matcher(input);
                if (m.lookingAt()) {
                    return new ParseResult<String>(m.group(), input.substring(m.end()));
                }
                return null;
            }
        };
    }

    /** 任意の1文字とマッチするパーサ */
    public static Parser<String> any = new Parser<String>() {
        public ParseResult<? extends String> parse(String input) {
            return input.length() > 0 ? new ParseResult<String>(input.substring(0, 1), input.substring(1)) : null;
        }
    };

//  /** 2つのパーサのいずれかをマッチさせるパーサ */
//  public static <A> Parser<A> or(final Parser<? extends A> p1, final Parser<? extends A> p2) {
//      return new Parser<A>() {
//          public ParseResult<? extends A> parse(String input) {
//              ParseResult<? extends A> r1 = p1.parse(input);
//              if (r1 != null)
//                  return r1;
//              return p2.parse(input);
//          }
//      };
//  }
    /** 複数のパーサを先頭からマッチさせるパーサ */
    public static <A> Parser<A> or(final Parser<? extends A>... ps) {
        return new Parser<A>() {
            public ParseResult<? extends A> parse(String input) {
                for (Parser<? extends A> p: ps) {
                    ParseResult<? extends A> r = p.parse(input);
                    if (r != null) return r;
                }
                return null;    // いずれも成功しなかった
            }
        };
    }

    /** 2つのパーサを結合 */
    public static <A, B> Parser<Pair<A, B>> seq(final Parser<A> p1, final Parser<B> p2) {
        return new Parser<Pair<A, B>>() {
            public ParseResult<? extends Pair<A, B>> parse(String input) {
                ParseResult<? extends A> r1 = p1.parse(input);
                if (r1 == null)
                    return null;
                ParseResult<? extends B> r2 = p2.parse(r1.rest);
                if (r2 == null)
                    return null;
                return new ParseResult<Pair<A, B>>(new Pair<A, B>(r1.value, r2.value), r2.rest);
            }
        };
    }

    /** 複数のパーサを結合。 */
    public static <A> Parser<List<A>> seqN(final Parser<? extends A>... ps) {
        return new Parser<List<A>>() {
            public ParseResult<? extends List<A>> parse(String input) {
                List<A> rs = new ArrayList<A>();
                for (Parser<? extends A> p : ps) {
                    ParseResult<? extends A> r = p.parse(input);
                    if (r == null)
                        return null;
                    rs.add(r.value);
                    input = r.rest;
                }
                return new ParseResult<List<A>>(rs, input);
            }
        };
    }

    public static <T> Parser<Void> not(final Parser<T> p) {
        return new Parser<Void>() {
            public ParseResult<? extends Void> parse(String input) {
                ParseResult<? extends T> r = p.parse(input);
                return r == null ? new ParseResult<Void>(null, input) : null;
            }
        };
    }

    public static <T> Parser<Void> and(final Parser<T> p) {
        return not(not(p));
    }

    public static <T> Parser<T> opt(final Parser<T> p) {
        return or(p, apply(string(""), new Transformer<String, T>() {
            public T apply(String param) {
                return null;
            }
        }));
    }

    public static <A> Parser<List<A>> rep1(final Parser<A> p) {
        // p (p)*
        return apply(seq(p, rep(p)), new Transformer<Pair<A, List<A>>, List<A>>() {
            public List<A> apply(Pair<A, List<A>> param) {
                param._2.add(param._1);
                return param._2;
            }
        });
    }

    public static <A> Parser<List<A>> rep(final Parser<A> p) {
        return new Parser<List<A>>() {
            public ParseResult<? extends List<A>> parse(String input) {
                List<A> rs = new ArrayList<A>();
                while (true) {
                    ParseResult<? extends A> r = p.parse(input);
                    if (r == null)
                        return new ParseResult<List<A>>(rs, input);
                    rs.add(r.value);
                    input = r.rest;
                }
            }
        };
    }

    /** セパレータで区切られた要素のリストを解析する。 */
    public static <A> Parser<List<A>> repsep(final Parser<A> p, final Parser<String> sep) {
        return new Parser<List<A>>() {
            public ParseResult<? extends List<A>> parse(String input) {
                List<A> rs = new ArrayList<A>();
                ParseResult<? extends A> r = p.parse(input);
                if (r != null) {
                    rs.add(r.value);
                    input = r.rest;
                    while (true) {
                        ParseResult<? extends String> s = sep.parse(input);
                        if (s == null) break;
                        input = s.rest;
                        r = p.parse(input);
                        if (r == null) return null;//throw new Exception("セパレータの次の要素が不正");
                        rs.add(r.value);
                        input = r.rest;
                    }
                }
                return new ParseResult<List<A>>(rs, input);
            }
        };
    }

    /** セパレータで区切られた要素のリストを解析する。 */
    public static <A> Parser<List<A>> rep1sep(final Parser<A> p, final Parser<String> sep) {
        return new Parser<List<A>>() {
            public ParseResult<? extends List<A>> parse(String input) {
                List<A> rs = new ArrayList<A>();
                ParseResult<? extends A> r = p.parse(input);
                if (r == null) return null;
                rs.add(r.value);
                input = r.rest;
                while (true) {
                    ParseResult<? extends String> s = sep.parse(input);
                    if (s == null) break;
                    input = s.rest;
                    r = p.parse(input);
                    if (r == null) return null; // throw new Exception("セパレータの次の要素が不正");
                    rs.add(r.value);
                    input = r.rest;
                }
                return new ParseResult<List<A>>(rs, input);
            }
        };
    }

    /** 「解析後の結果を好みの構造に変換する」というパーサを作る */
    public static <A, B> Parser<B> apply(final Parser<A> p, final Transformer<A, B> t) {
        return new Parser<B>() {
            public ParseResult<? extends B> parse(String input) {
                ParseResult<? extends A> r = p.parse(input);
                return r == null ? null : new ParseResult<B>(t.apply(r.value), r.rest);
            }
        };
    }
}


JSONのパーサ。
正規表現が使えるので、数値などの定義が簡単です。
「空白文字を読み飛ばす」処理をどう実装すべきか悩みました。
とりあえず、プリミティブな要素の前にスキップするようにしました。
ParserProxyとParserの使い分けですが、JSONのVALUEはOBJECTの要素でもあり、
OBJECT自体もVALUEになります。定義がループしています。
そういうときはParserProxyを使う必要があります。
定義済みの構文だけで定義できる場合はParserProxyでなくてもかまいません。
わからなければ、全部ParserProxyにしておけばおk。

package jp.gr.java_conf.mizu;

import static jp.gr.java_conf.mizu.PEGParserCombinators2.*;

import java.util.List;


/**
 * JSONのパーサ
 * @see http://www.json.org/json-ja.html
 */
public class TestJson2 {
    static Parser<String> WHITESPACE = regex("\\s*");           // 空白文字

    /** 空白文字を読み飛ばしてから解析する。 */
    public static <A> Parser<A> sp(final Parser<A> p) {
        return new Parser<A>() {
            public ParseResult<? extends A> parse(String input) {
                return p.parse(WHITESPACE.parse(input).rest);
            }
        };
    }

    /** 両側のカッコを無視する。実はカッコ以外でも使える。 */
    public static <A> Parser<A> ignoreBracket(final Parser<String> bra, final Parser<A> p, final Parser<String> ket) {
        return apply(seqN(bra, p, ket), list -> (A) list.get(1));
    }

    // 構文の定義。プリミティブな要素は先頭の空白文字を読み飛ばすようにする。
    static Parser<Double> NUMBER = apply(
            sp(regex("-?(0|[1-9][0-9]*)(\\.[0-9]+)?([eE][+-]?[0-9]+)?")),   // 数値。この正規表現は正しい。
            s -> Double.parseDouble(s));
    // 文字列。この正規表現はあってないかも???
    static Parser<String> STRING = sp(regex("\""+"([^\"\\p{Cntrl}\\\\]|\\\\[\\\\/bfnrt]|\\\\u[a-fA-F0-9]{4})*"+"\""));  // 文字列
    //static Parser<String> IDENT = regex("[a-zA-Z_]\\w*"); // 識別子
    static Parser<Boolean> TRUE = apply(sp(string("true")), s -> Boolean.TRUE);
    static Parser<Boolean> FALSE = apply(sp(string("false")), s -> Boolean.FALSE);
    static Parser<Object> NULL = apply(sp(string("null")), s -> null);

    // 構文の定義。合成した構文は再帰になって定義できないことがあるからProxyで定義。
    static ParserProxy<Object> VALUE = proxy(null);
    static Parser<Pair<String,Object>> MEMBER = apply(
            seqN(STRING, sp(string(":")), VALUE),
            list -> {
                String key = (String) list.get(0);
                Object value = list.get(2);
                return new Pair<String, Object>(key, value);
            });
    static Parser<List<Pair<String,Object>>> OBJECT = ignoreBracket(
            sp(string("{")),
            repsep(MEMBER, sp(string(","))),
            sp(string("}")));
    static Parser<List<Object>> ARRAY = ignoreBracket(
            sp(string("[")),
            repsep(VALUE, sp(string(","))),
            sp(string("]")));
    static {
        /** value <- string / number / object / array / true / false / null */
        VALUE.target = or(STRING, NUMBER, OBJECT, ARRAY, TRUE, FALSE, NULL);
    }

    public static void main(String[] args) {
        System.out.println(NUMBER.parse("1024"));       // (value = 1024, rest = )
        System.out.println(NUMBER.parse("3.14159"));    // (value = 3, rest = .14159)
        System.out.println(NUMBER.parse("-255"));       // (value = -255, rest = )
        System.out.println(WHITESPACE.parse(" \t\r\n123")); // (value = " \t\r\n", rest = 123)
        System.out.println(WHITESPACE.parse("123"));            // (value = , rest = 123)
        System.out.println(TRUE.parse(" true "));       // (value = true, rest =  )
        System.out.println(TRUE.parse(" truee"));       // (value = true, rest = e)     うーん
        System.out.println(FALSE.parse(" false"));      // (value = false, rest = )
        System.out.println(NULL.parse(" null"));        // (value = null, rest = )
        System.out.println(MEMBER.parse(" \"Key\" : 999,"));    // (value = ("Key", 999), rest = ,)
        System.out.println(MEMBER.parse("\"Key\":999,"));       // (value = ("Key", 999), rest = ,)
        System.out.println(MEMBER.parse("\"Key\":999,"));       // (value = ("Key", 999), rest = ,)
        System.out.println(ARRAY.parse("[1,1,2,3,5,8,13]"));        // (value = [1.0, 1.0, 2.0, 3.0, 5.0, 8.0, 13.0], rest = )
        System.out.println(ARRAY.parse("[ ]"));     // (value = [], rest = )
        System.out.println(OBJECT.parse("{\"apple\":\"りんご\",\"boat\":\"ふね\"}"));       // (value = [("apple", "りんご"), ("boat", "ふね")], rest = )
        System.out.println(OBJECT.parse("{ }"));        // (value = [], rest = )
        System.out.println(VALUE.parse("true"));            // (value = true, rest = )
        System.out.println(VALUE.parse("3.14"));            // (value = 3.14, rest = )
        System.out.println(VALUE.parse("\"文字列\""));      // (value = "文字列", rest = )
        System.out.println(VALUE.parse("[1,1,2,3,5,8,13]"));        // (value = [1.0, 1.0, 2.0, 3.0, 5.0, 8.0, 13.0], rest = )
        System.out.println(VALUE.parse("{\"apple\":\"りんご\",\"boat\":\"ふね\"}"));        // (value = [("apple", "りんご"), ("boat", "ふね")], rest = )

        String source =
                " {\n\t\"address book\": {\n"+
                "\t\t\"name\":\"John Smith\",\n"+
                "\"address\" : {\n"+
                "\t\t\"street\":\"10 Market Street\",\n"+
                "\t\t\"city\": \"San Francisco, CA\","+
                "\t\t\"zip\" : 94111\n"+
                "\t},"+
                "\"phone numbers\": [\"408 338-4238\",\"408 111-6832\"]\n"+
                "}\n"+
            "}";
        System.out.println(VALUE.parse(source));
    }
}


これ、解析中に構文エラーがあったら、例外とかにならずにnullを返すだけです。
どこが間違っているのかわかりません。
機械が吐くデータならこのパーサで問題ありませんが、人間が書くソースを読み込むのであればイマイチ。