Skip to content

Commit 1fe5f3c

Browse files
committed
OrEmpty.optionallyFollowedBy()
1 parent 499e86a commit 1fe5f3c

3 files changed

Lines changed: 99 additions & 12 deletions

File tree

dot-parse/src/main/java/com/google/common/labs/parse/Grammar.java

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
package com.google.common.labs.parse;
22

3+
import java.util.function.BiFunction;
4+
import java.util.function.Function;
5+
36
import com.google.common.labs.parse.Parser.ParseException;
47
import com.google.mu.util.CharPredicate;
58

@@ -46,9 +49,15 @@ default Parser<T> between(String prefix, String suffix) {
4649

4750
/** The current grammar must be enclosed between non-empty {@code prefix} and {@code suffix}. */
4851
default Parser<T> between(Parser<?> prefix, Parser<?> suffix) {
49-
return prefix.then(this).followedBy(suffix);
52+
return Parser.sequence(prefix, this, (p, t) -> t).followedBy(suffix);
5053
}
5154

55+
/**
56+
* Returns a parser that matches {@code this} pattern enclosed between {@code prefix} and {@code suffix},
57+
* both allowed to be empty
58+
*/
59+
Grammar<T> between(Parser<?>.OrEmpty prefix, Parser<?>.OrEmpty suffix);
60+
5261
/**
5362
* The current grammar must be <em>immediately</em> enclosed between
5463
* non-empty {@code prefix} and {@code suffix} (no skippable characters as specified by {@link
@@ -67,6 +76,9 @@ default <S> Parser<S> then(Parser<S> suffix) {
6776
return Parser.sequence(Parser.maybeZeroWidth(this), suffix, (a, b) -> b);
6877
}
6978

79+
/** After matching the current optional (or zero-or-more) parser, proceed to match {@code suffix}. */
80+
<S> Grammar<S> then(Parser<S>.OrEmpty suffix);
81+
7082
/** The current grammar must be followed by non-empty {@code suffix}. */
7183
default Parser<T> followedBy(String suffix) {
7284
return followedBy(Parser.string(suffix));
@@ -76,4 +88,31 @@ default Parser<T> followedBy(String suffix) {
7688
default Parser<T> followedBy(Parser<?> suffix) {
7789
return Parser.sequence(Parser.maybeZeroWidth(this), suffix, (a, b) -> a);
7890
}
91+
92+
/** The current optional (or zero-or-more) parser may optionally be followed by {@code suffix}. */
93+
<S> Grammar<T> followedBy(Parser<S>.OrEmpty suffix);
94+
95+
/** Returns an equivalent grammar except it allows {@code suffix} if present. */
96+
Grammar<T> optionallyFollowedBy(String suffix);
97+
98+
/**
99+
* If this parser matches, optionally applies the {@code op} function if the pattern is followed
100+
* by {@code suffix}.
101+
*/
102+
Grammar<T> optionallyFollowedBy(String suffix, Function<? super T, ? extends T> op);
103+
104+
/**
105+
* If this parser matches, optionally matches {@code suffix} with the {@code op} BiFunction
106+
* to transform the current parser's result.
107+
*
108+
* <p>For example:
109+
*
110+
* <pre>{@code
111+
* Parser<MarkdownLink> link = ...;
112+
* Parser<String> title = ...;
113+
* Parser<MarkdownLink> parser = link.optionallyFollowedBy(title, MarkdownLink::withTitle);
114+
* }</pre>
115+
*/
116+
<S> Grammar<T> optionallyFollowedBy(
117+
Parser<S> suffix, BiFunction<? super T, ? super S, ? extends T> op);
79118
}

dot-parse/src/main/java/com/google/common/labs/parse/Parser.java

Lines changed: 37 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -955,7 +955,7 @@ public final Parser<T> withPostfixes(String operator, UnaryOperator<T> postfixFu
955955
*
956956
* @since 9.5
957957
*/
958-
public final Parser<T> between(Parser<?>.OrEmpty prefix, Parser<?>.OrEmpty suffix) {
958+
@Override public final Parser<T> between(Parser<?>.OrEmpty prefix, Parser<?>.OrEmpty suffix) {
959959
return prefix.then(this).followedBy(suffix);
960960
}
961961

@@ -1006,7 +1006,7 @@ public final <R> Parser<R> thenReturn(R result) {
10061006
*
10071007
* @since 10.0
10081008
*/
1009-
public final <R> Parser<R> then(Grammar<R> next) {
1009+
@Override public final <R> Parser<R> then(Parser<R>.OrEmpty next) {
10101010
return sequence(this, next, (unused, value) -> value);
10111011
}
10121012

@@ -1038,7 +1038,7 @@ public final Parser<T> suchThat(Predicate<? super T> condition, String name) {
10381038
}
10391039

10401040
/** If this parser matches, continue to match the optional {@code suffix}. */
1041-
public final Parser<T> followedBy(Grammar<?> suffix) {
1041+
public final <S> Parser<T> followedBy(Parser<S>.OrEmpty suffix) {
10421042
return sequence(this, suffix, (value, unused) -> value);
10431043
}
10441044

@@ -1057,15 +1057,15 @@ final Parser<T> followedByEof() {
10571057
}
10581058

10591059
/** Returns an equivalent parser except it allows {@code suffix} if present. */
1060-
public final Parser<T> optionallyFollowedBy(String suffix) {
1060+
@Override public final Parser<T> optionallyFollowedBy(String suffix) {
10611061
return followedBy(string(suffix).orElse(null));
10621062
}
10631063

10641064
/**
10651065
* If this parser matches, optionally applies the {@code op} function if the pattern is followed
10661066
* by {@code suffix}.
10671067
*/
1068-
public final Parser<T> optionallyFollowedBy(String suffix, Function<? super T, ? extends T> op) {
1068+
@Override public final Parser<T> optionallyFollowedBy(String suffix, Function<? super T, ? extends T> op) {
10691069
return optionalPostfix(string(suffix).thenReturn(op::apply));
10701070
}
10711071

@@ -1083,7 +1083,7 @@ public final Parser<T> optionallyFollowedBy(String suffix, Function<? super T, ?
10831083
*
10841084
* @since 9.5
10851085
*/
1086-
public final <S> Parser<T> optionallyFollowedBy(
1086+
@Override public final <S> Parser<T> optionallyFollowedBy(
10871087
Parser<S> suffix, BiFunction<? super T, ? super S, ? extends T> op) {
10881088
requireNonNull(op);
10891089
return optionalPostfix(suffix.map(s -> p -> op.apply(p, s)));
@@ -1465,7 +1465,7 @@ private OrEmpty(Supplier<? extends T> defaultSupplier) {
14651465
*
14661466
* @since 9.5
14671467
*/
1468-
public final Parser<T>.OrEmpty between(Parser<?>.OrEmpty prefix, Parser<?>.OrEmpty suffix) {
1468+
@Override public final Parser<T>.OrEmpty between(Parser<?>.OrEmpty prefix, Parser<?>.OrEmpty suffix) {
14691469
return prefix.then(this).followedBy(suffix);
14701470
}
14711471

@@ -1503,12 +1503,12 @@ public Parser<List<T>>.OrEmpty delimitedBy(String delimiter) {
15031503
}
15041504

15051505
/** After matching the current optional (or zero-or-more) parser, proceed to match {@code suffix}. */
1506-
public <S> Parser<S>.OrEmpty then(Parser<S>.OrEmpty suffix) {
1506+
@Override public <S> Parser<S>.OrEmpty then(Parser<S>.OrEmpty suffix) {
15071507
return sequence(this, suffix, (a, b) -> b);
15081508
}
15091509

15101510
/** The current optional (or zero-or-more) parser may optionally be followed by {@code suffix}. */
1511-
public <S> Parser<T>.OrEmpty followedBy(Parser<S>.OrEmpty suffix) {
1511+
@Override public <S> Parser<T>.OrEmpty followedBy(Parser<S>.OrEmpty suffix) {
15121512
return sequence(this, suffix, (a, b) -> a);
15131513
}
15141514

@@ -1517,10 +1517,36 @@ public <S> Parser<T>.OrEmpty followedBy(Parser<S>.OrEmpty suffix) {
15171517
*
15181518
* @since 9.5
15191519
*/
1520-
public Parser<T>.OrEmpty optionallyFollowedBy(String suffix) {
1520+
@Override public Parser<T>.OrEmpty optionallyFollowedBy(String suffix) {
15211521
return followedBy(string(suffix).orElse(null));
15221522
}
15231523

1524+
/**
1525+
* If this parser matches, optionally applies the {@code op} function if the pattern is followed
1526+
* by {@code suffix}.
1527+
*
1528+
* @since 10.0
1529+
*/
1530+
@Override public final Parser<T>.OrEmpty optionallyFollowedBy(String suffix, Function<? super T, ? extends T> op) {
1531+
return optionallyFollowedBy(string(suffix).thenReturn(op::apply));
1532+
}
1533+
1534+
/**
1535+
* If this parser matches, optionally matches {@code suffix} with the {@code op} BiFunction
1536+
* to transform the current parser's result.
1537+
*
1538+
* @since 10.0
1539+
*/
1540+
@Override public final <S> Parser<T>.OrEmpty optionallyFollowedBy(
1541+
Parser<S> suffix, BiFunction<? super T, ? super S, ? extends T> op) {
1542+
requireNonNull(op);
1543+
return optionallyFollowedBy(suffix.map(s -> p -> op.apply(p, s)));
1544+
}
1545+
1546+
private Parser<T>.OrEmpty optionallyFollowedBy(Parser<UnaryOperator<T>> suffix) {
1547+
return sequence(this, suffix.orElse(identity()), (operand, op) -> op.apply(operand));
1548+
}
1549+
15241550
/**
15251551
* Returns the otherwise equivalent {@code Parser} that will fail instead of returning the
15261552
* default value if empty.
@@ -1678,7 +1704,7 @@ Stream<T> parseToStream(CharInput input, int fromIndex) {
16781704
* input is exhausted or matching failed.
16791705
*
16801706
* <p>Note that unlike {@link #parseToStream(String) parseToStream()}, a matching failure
1681-
* terminates the stream without throwing exception.
1707+
* terminates the stream out throwing exception.
16821708
*
16831709
* <p>This allows quick probing without fully parsing it.
16841710
*/

dot-parse/src/test/java/com/google/common/labs/parse/ParserTest.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3829,6 +3829,28 @@ public void orEmpty_optionallyFollowedBy_suffix_source() {
38293829
assertThat(parser.source().parse("[]")).isEqualTo("[]");
38303830
}
38313831

3832+
@Test
3833+
public void orEmpty_optionallyFollowedBy_string_function_success() {
3834+
Parser<String> parser = zeroOrMore(is('a'), "a's")
3835+
.optionallyFollowedBy("++", s -> s + "b")
3836+
.between("[", "]");
3837+
assertThat(parser.parse("[aa++]")).isEqualTo("aab");
3838+
assertThat(parser.parse("[aa]")).isEqualTo("aa");
3839+
assertThat(parser.parse("[]")).isEqualTo("");
3840+
}
3841+
3842+
@Test
3843+
public void orEmpty_optionallyFollowedBy_parser_biFunction_success() {
3844+
Parser<String> parser = zeroOrMore(is('a'), "a's")
3845+
.optionallyFollowedBy(
3846+
string("+").then(digits()).map(String::length),
3847+
(s, len) -> s + len)
3848+
.between("[", "]");
3849+
assertThat(parser.parse("[aa+123]")).isEqualTo("aa3");
3850+
assertThat(parser.parse("[aa]")).isEqualTo("aa");
3851+
assertThat(parser.parse("[]")).isEqualTo("");
3852+
}
3853+
38323854
@Test
38333855
public void parser_immediatelyBetween_success() {
38343856
Parser<String> parser = consecutive(noneOf("[]"), "content").immediatelyBetween("[", "]");

0 commit comments

Comments
 (0)