2424import net .kautler .command .usage .UsageParser .PlaceholderWithWhitespaceContext ;
2525import net .kautler .command .usage .UsageParser .UsageContext ;
2626import org .antlr .v4 .runtime .RuleContext ;
27+ import org .antlr .v4 .runtime .tree .ParseTree ;
2728
2829import javax .enterprise .context .ApplicationScoped ;
2930import java .util .List ;
3031import java .util .Map ;
32+ import java .util .Optional ;
3133import java .util .StringJoiner ;
3234import java .util .concurrent .ConcurrentHashMap ;
3335import java .util .concurrent .CopyOnWriteArrayList ;
3436import java .util .regex .Pattern ;
35- import java .util .stream .Collector ;
3637
3738import static java .lang .String .format ;
38- import static java .util .regex .Matcher .quoteReplacement ;
3939import static java .util .stream .Collectors .joining ;
40- import static net .kautler .command .api .Command .PARAMETER_SEPARATOR_PATTERN ;
40+ import static net .kautler .command .api .Command .PARAMETER_SEPARATOR_CHARACTER ;
4141
4242/**
4343 * A usage visitor that constructs a regular expression pattern to parse command arguments according to the defined
4646@ ApplicationScoped
4747public class UsagePatternBuilder extends UsageBaseVisitor <String > {
4848 /**
49- * A regular expression part that matches a parameter separator or the end of string .
49+ * A regular expression part that matches a parameter separator that is not preceded by start or end of input .
5050 */
5151 private static final String PARAMETER_BOUNDARY_PATTERN_PART =
52- format ("(?:%s|$)" , PARAMETER_SEPARATOR_PATTERN .pattern ());
52+ format ("(?:(?<!^)%s++(?!$))?" , PARAMETER_SEPARATOR_CHARACTER );
53+
54+ /**
55+ * A regular expression part that matches a parameter separator character as lookbehind.
56+ */
57+ private static final String PRECEDED_BY_PARAMETER_SEPARATOR_CHARACTER =
58+ format ("(?<=^|(?<!^)%s)" , PARAMETER_SEPARATOR_CHARACTER );
59+
60+ /**
61+ * A regular expression part that matches a parameter separator character as lookahead.
62+ */
63+ private static final String FOLLOWED_BY_PARAMETER_SEPARATOR_CHARACTER =
64+ format ("(?=%s(?!$)|$)" , PARAMETER_SEPARATOR_CHARACTER );
5365
5466 /**
5567 * A cache for regular expression patterns for usage specifications, so that the usage string does not need to be
@@ -136,49 +148,17 @@ public String visitOptional(OptionalContext ctx) {
136148 * @return the visitor result
137149 */
138150 private String visitParentParserRuleContext (UsageParserRuleContext ctx ) {
139- boolean hasSingleChild = (ctx .placeholder () != null )
140- || (ctx .placeholderWithWhitespace () != null )
141- || (ctx .literal () != null )
142- || (ctx .optional () != null )
143- || (ctx .alternatives () != null );
144- if (hasSingleChild ) {
145- return ctx .getChild (0 ).accept (this );
151+ Optional <ParseTree > singleChild = ctx .getSingleChild ();
152+ if (singleChild .isPresent ()) {
153+ return singleChild .get ().accept (this );
146154 } else {
147155 List <ExpressionContext > expressions = ctx .expression ();
148156 if (expressions .isEmpty ()) {
149157 throw new AssertionError ("Unhandled case" );
150158 } else {
151- boolean [] first = { true };
152- boolean [] leftOptional = { false };
153- boolean [] leftHasWhitespace = { false };
154159 return expressions .stream ()
155160 .map (this ::visitParentParserRuleContext )
156- .collect (Collector .of (
157- StringBuilder ::new ,
158- (left , right ) -> {
159- boolean rightOptional = right .endsWith (")?" );
160- if (first [0 ]) {
161- left .append (right );
162- first [0 ] = false ;
163- leftHasWhitespace [0 ] = false ;
164- } else if (leftOptional [0 ] && !leftHasWhitespace [0 ]) {
165- left .insert (left .length () - 2 , PARAMETER_BOUNDARY_PATTERN_PART );
166- left .append (right );
167- leftHasWhitespace [0 ] = false ;
168- } else if (rightOptional ) {
169- left .append (right .replaceFirst (
170- "^\\ (\\ ?(?::|<[^>]++>)" ,
171- "$0" + quoteReplacement (PARAMETER_BOUNDARY_PATTERN_PART )));
172- leftHasWhitespace [0 ] = true ;
173- } else {
174- left .append (PARAMETER_BOUNDARY_PATTERN_PART );
175- left .append (right );
176- leftHasWhitespace [0 ] = false ;
177- }
178- leftOptional [0 ] = rightOptional ;
179- },
180- (left , right ) -> { throw new UnsupportedOperationException (); },
181- Object ::toString ));
161+ .collect (joining (PARAMETER_BOUNDARY_PATTERN_PART ));
182162 }
183163 }
184164 }
@@ -187,21 +167,33 @@ private String visitParentParserRuleContext(UsageParserRuleContext ctx) {
187167 public String visitPlaceholder (PlaceholderContext ctx ) {
188168 String tokenText = ctx .getText ();
189169 String tokenName = tokenText .substring (1 , tokenText .length () - 1 );
190- return format ("(?<%s>\\ S+)" , getGroupName (ctx , tokenName ));
170+ return format (
171+ "%s(?<%s>\\ S+)%s" ,
172+ PRECEDED_BY_PARAMETER_SEPARATOR_CHARACTER ,
173+ getGroupName (ctx , tokenName ),
174+ FOLLOWED_BY_PARAMETER_SEPARATOR_CHARACTER );
191175 }
192176
193177 @ Override
194178 public String visitPlaceholderWithWhitespace (PlaceholderWithWhitespaceContext ctx ) {
195179 String tokenText = ctx .getText ();
196180 String tokenName = tokenText .substring (1 , tokenText .length () - 4 );
197- return format ("(?<%s>.+)" , getGroupName (ctx , tokenName ));
181+ return format (
182+ "%s(?<%s>.+)$" ,
183+ PRECEDED_BY_PARAMETER_SEPARATOR_CHARACTER ,
184+ getGroupName (ctx , tokenName ));
198185 }
199186
200187 @ Override
201188 public String visitLiteral (LiteralContext ctx ) {
202189 String tokenText = ctx .getText ();
203190 String tokenName = tokenText .substring (1 , tokenText .length () - 1 );
204- return format ("(?<%s>%s)" , getGroupName (ctx , tokenName , "Literal" ), Pattern .quote (tokenName ));
191+ return format (
192+ "%s(?<%s>%s)%s" ,
193+ PRECEDED_BY_PARAMETER_SEPARATOR_CHARACTER ,
194+ getGroupName (ctx , tokenName , "Literal" ),
195+ Pattern .quote (tokenName ),
196+ FOLLOWED_BY_PARAMETER_SEPARATOR_CHARACTER );
205197 }
206198
207199 /**
@@ -264,7 +256,7 @@ private String getGroupName(UsageParserRuleContext ctx, String tokenName, String
264256 */
265257 private UsageContext getUsageContext (UsageParserRuleContext ctx ) {
266258 RuleContext current = ctx ;
267- while (current . getParent () != null ) {
259+ while (!( current instanceof UsageContext ) ) {
268260 current = current .getParent ();
269261 }
270262 return (UsageContext ) current ;
0 commit comments