mapper) {
+ return input -> this.parse(input).map(tup -> tup.map(mapper));
+ }
+
+ default Parser seqApply(Parser> parser) {
+ return input -> parser.parse(input)
+ .flatMap(ftup -> this.parse(ftup.first).map(tup -> tup.map(ftup.second)));
+ }
+
+ default
Parser leftSeqApply(Parser parser) {
+ return this.apply((x, y) -> y, parser);
+ }
+
+ default
Parser rightSeqApply(Parser parser) {
+ return this.apply((x, y) -> x, parser);
+ }
+
+ default
Parser apply(BiFunction function, Parser parser) {
+ return parser.seqApply(this.map(o -> p -> function.apply(o, p)));
+ }
+
+ default Parser or(Parser parser) {
+ return input -> this.parse(input).or(() -> parser.parse(input));
+ }
+
+ default Parser> many() {
+ return input -> {
+ I rest = input;
+ List output = new ArrayList<>();
+
+ Optional> parsed = this.parse(rest);
+ while (parsed.isPresent()) {
+ Tuple tup = parsed.get();
+ rest = tup.first;
+ output.add(tup.second);
+ parsed = this.parse(rest);
+ }
+ return Optional.of(new Tuple<>(rest, output));
+ };
+ }
+
+ default Parser> some() {
+ return input -> {
+ Tuple> output = many().parse(input).get();
+ if (output.second.isEmpty()) {
+ return Optional.empty();
+ } else {
+ return Optional.of(output);
+ }
+ };
+ }
+
+}
+
+class CharPredicateParser implements Parser {
+ private Predicate predicate;
+
+ CharPredicateParser(Predicate predicate) {
+ this.predicate = predicate;
+ }
+
+ @Override
+ public Optional> parse(String input) {
+ if (input.isEmpty()) {
+ return Optional.empty();
+ }
+
+ Character i = input.charAt(0);
+ if (predicate.test(i)) {
+ return Optional.of(new Tuple<>(input.substring(1), i));
+ }
+
+ return Optional.empty();
+ }
+}
+
+class Parsers {
+ public static final Parser DIGIT_PARSER =
+ new CharPredicateParser(Character::isDigit);
+
+ public static final Parser NUMBER_PARSER =
+ DIGIT_PARSER
+ .some()
+ .map(Parsers::digitsToInt);
+
+ public static final Parser ENGLISH_LOWER_CASE_CHAR_PARSER =
+ new CharPredicateParser(c -> c >= 97 && c <= 122);
+
+ public static Parser charParser(Character c) {
+ return new CharPredicateParser(c::equals);
+ }
+
+ private static Integer digitsToInt(List digits) {
+ int i = 0;
+ for (char c : digits) {
+ i = i * 10 + (c - 48);
+ }
+ return i;
+ }
+}
+
+class CompressedString {
+ private List parts;
+
+ CompressedString(List parts) {
+ this.parts = parts;
+ }
+
+ interface Part {
+ String toString();
+ }
+
+ @Override
+ public String toString() {
+ return parts.stream()
+ .map(Object::toString)
+ .collect(Collectors.joining());
+ }
+
+ public static final Parser PARSER =
+ CompressedPart.PARSER
+ .or(StringPart.PARSER)
+ .many()
+ .map(CompressedString::new);
+}
+
+
+class StringPart implements CompressedString.Part {
+ private String string;
+
+ StringPart(String string) {
+ this.string = string;
+ }
+
+ @Override
+ public String toString() {
+ return this.string;
+ }
+
+ public static final Parser PARSER =
+ Parsers.ENGLISH_LOWER_CASE_CHAR_PARSER
+ .some()
+ .map(chars -> new StringPart(charListToString(chars)));
+
+ private static String charListToString(List chars) {
+ return chars.stream()
+ .map(String::valueOf)
+ .collect(Collectors.joining());
+ }
+}
+
+class CompressedPart implements CompressedString.Part {
+ private int repetition;
+ private CompressedString string;
+
+ CompressedPart(int repetition, CompressedString string) {
+ this.repetition = repetition;
+ this.string = string;
+ }
+
+ @Override
+ public String toString() {
+ String s = string.toString();
+ return Stream.generate(() -> s)
+ .limit(repetition)
+ .collect(Collectors.joining());
+ }
+
+ private static final Parser BRACKETED_STRING_PARSER =
+ Parsers.charParser('[')
+ .leftSeqApply(input -> CompressedString.PARSER
+ .rightSeqApply(Parsers.charParser(']'))
+ .parse(input));
+
+ public static final Parser PARSER =
+ Parsers.NUMBER_PARSER.apply(CompressedPart::new, BRACKETED_STRING_PARSER);
+}
+