package ch.usi.pl.parser; import static ch.usi.pl.parser.Parsers.oneOf; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Scanner; /** Demostration of a parser combinator library in Java. */ public class Parsers { /** * A parser combinator. This is mostly just a function from an input String * to a Result, which is either an A and the unconsumed tail of the input * String, or a failure. */ static abstract class Parser { /** The parser function proper. */ abstract Result parse(String in); Parser> then(final Parser pb) { final Parser pa = this; return new SequenceParser(pa, pb); } Parser thenWithConstructor(final Parser pb, Function2 f) { throw new UnsupportedOperationException("probably a bad idea to implement this; bind will be more useful"); } Parser bind(Function1> f) { throw new UnsupportedOperationException("unimplemented: we'll do this Tuesday"); } // Alternative version of 'then', using 'bind'. // Note, I don't need to deal with parser success or failure or the // input string. Parser> thenWithBind(final Parser pb) { return bind(new Function1>>() { public Parser> apply(final A fst) { return pb.map(new Function1>() { public Pair apply(B snd) { return new Pair(fst, snd); } }); } }); } Parser or(final Parser pb) { final Parser pa = this; return new AltParser(pa, pb); } Parser> zeroOrMore() { return new StarParser(this); } // Alternative version of zeroOrMore. // This would work except for the recursive call! // It goes into an infinite loop building the parser. Parser> zeroOrMore_Broken() { return this.then(this.zeroOrMore_Broken()).map(Parsers. cons()).or(empty(Collections. emptyList())); } // Alternative version of zeroOrMore_Broken where we push the // construction into the parse method. // Unsatisfying. Parser> zeroOrMore_Hack() { return new AtLeastZeroParser(this); } // Version of atLeastZero, using bind rather than then. Parser> zeroOrMore_Bind() { return this.bind(new Function1>>() { @Override public Parser> apply(final A head) { Function1, List> prepend = new Function1, List>() { public List apply(List tail) { List xs = new ArrayList(); xs.add(head); xs.addAll(tail); return xs; } }; return zeroOrMore_Bind().map(prepend); } }).or(empty(Collections. emptyList())); } Parser map(Function1 f) { return new MapParser(this, f); } } /** * Slightly nicer version of StarParser, where I don't need to mess with the * parser plumbing as much. We still define a subclass of Parser to avoid * the infinite loop in zeroOrMore, above. */ static class AtLeastZeroParser extends Parser> { Parser p; AtLeastZeroParser(Parser p) { this.p = p; } @Override Result> parse(String in) { return p.then(this).map(Parsers. cons()).or(empty(Collections. emptyList())).parse(in); } } static Function1>, List> cons() { return new Function1>, List>() { @Override public List apply(Pair> arg) { List xs = new ArrayList(); xs.add(arg.fst); xs.addAll(arg.snd); return xs; } }; } static class MapParser extends Parser { private Parser p; private Function1 f; MapParser(Parser p, Function1 f) { this.p = p; this.f = f; } @Override Result parse(String in) { Result a = p.parse(in); if (a.success) { return new Result(f.apply(a.result), true, a.tail); } else { return fail(in); } } } /** Return a parser that consumes no input, but returns a value of type A. */ static Parser empty(final A value) { return new Parser() { @Override Result parse(String in) { return new Result(value, true, in); } }; } static class StarParser extends Parser> { private final Parser p; StarParser(Parser p) { this.p = p; } @Override Result> parse(String in) { Result r = p.parse(in); if (!r.success) { return new Result>(Collections. emptyList(), true, in); } else { Result> t = this.parse(r.tail); if (t.success) { List xs = new ArrayList(); xs.add(r.result); xs.addAll(t.result); return new Result>(xs, true, t.tail()); } else { return new Result>(Collections.singletonList(r.result), true, r.tail); } } } } static class AltParser extends Parser { private final Parser pa; private final Parser pb; AltParser(Parser pa, Parser pb) { this.pa = pa; this.pb = pb; } Result parse(String in) { Result ra = pa.parse(in); if (ra.success()) { return ra; } else { return pb.parse(in); } } } /** A parser that parses first an A, then a B, returning both. */ static class SequenceParser extends Parser> { private final Parser pa; private final Parser pb; SequenceParser(Parser pa, Parser pb) { this.pa = pa; this.pb = pb; } @Override Result> parse(String in) { Result ra = pa.parse(in); if (ra.success) { Result rb = pb.parse(ra.tail); if (rb.success) { return new Result>(new Pair(ra.result, rb.result), true, rb.tail); } } return fail(in); } } /** * A parser that parses a single character in a String of possible matching * characters. */ static Parser oneOf(final String set) { return new OneOfParser(set); } /** * A parser that parses a single character in a String of possible matching * characters. */ static class OneOfParser extends Parser { private final String set; OneOfParser(String set) { this.set = set; } @Override Result parse(String in) { if (in.length() >= 1) { for (int i = 0; i < set.length(); i++) { char ch = set.charAt(i); if (in.charAt(0) == ch) { return new Result(ch, true, in.substring(1)); } } } return fail(in); } } /** A failed parse result. */ static Result fail(String in) { return new Result(null, false, in); } /** Parser result */ static class Result { /** Whether the parse succeeded or not. */ private final boolean success; /** The actual result if the parser was successful. */ private final A result; /** The uncomsumed input. */ private final String tail; Result(A result, boolean success, String tail) { this.result = result; this.success = success; this.tail = tail; } boolean success() { return success; } A result() { if (success) return result; else throw new RuntimeException("parse failed"); } String tail() { if (success) return tail; else throw new RuntimeException("parse failed"); } } static class Pair { final A fst; final B snd; Pair(A fst, B snd) { this.fst = fst; this.snd = snd; } } interface Either { } class Left implements Either { A a; void match(Visitor m) { m.handleA(a); } } class Right implements Either { B b; void match(Visitor m) { m.handleB(b); } } interface Visitor { void handleA(A a); void handleB(B b); } /** Read an InputStream into a String. */ static String slurp(InputStream is) throws IOException { Scanner s = new Scanner(is).useDelimiter("\\A"); return s.hasNext() ? s.next() : ""; } public static void main(String[] args) throws IOException { Parser ws = oneOf(" \t\n"); Parser digit = oneOf("0123456789"); Parser lower = oneOf("abcdefghijklmnopqrstuvwxyz"); Parser upper = oneOf("ABCDEFGHIJKLMNOPQRSTUVWXYZ"); Parser letter = lower.or(upper); assert (digit.parse("0").result() == '0'); assert (ws.parse(" ").result() == ' '); String s = Parsers.slurp(System.in); Result> r = letter.or(digit).zeroOrMore_Hack().parse(s); if (r.success()) { System.out.println(r.result()); } else { System.out.println("fail"); } } }