@@ -314,9 +314,29 @@ private RegExpTerm parseAtom() {
314314 return this .finishTerm (new Group (loc , capture , number , name , dis ));
315315 }
316316
317- char c = this .nextChar ();
318- if ("^$\\ .*+?()[]{}|" .indexOf (c ) != -1 ) this .error (Error .UNEXPECTED_CHARACTER , this .pos - 1 );
319- return this .finishTerm (new Constant (loc , String .valueOf (c )));
317+ // Parse consecutive constants into a single Constant node.
318+ // Due to speculative parsing of string literals, this part of the code is fairly hot.
319+ int startPos = this .pos ;
320+ int endPos = startPos ;
321+ while (endPos < src .length ()) {
322+ if ("^$\\ .*+?()[]{}|" .indexOf (src .charAt (endPos )) != -1 ) break ;
323+ ++endPos ;
324+ }
325+ if (startPos == endPos ) {
326+ this .error (Error .UNEXPECTED_CHARACTER , endPos );
327+ endPos = startPos + 1 ; // To ensure progress, make sure we parse at least one character.
328+ }
329+ if (endPos != startPos + 1
330+ && endPos < src .length ()
331+ && "*+?{" .indexOf (src .charAt (endPos )) != -1 ) {
332+ endPos --; // Last constant belongs under an upcoming quantifier.
333+ }
334+ String str = src .substring (startPos , endPos );
335+ this .pos = endPos ;
336+ loc .setEnd (pos ());
337+ loc .setSource (str );
338+ // Do not call finishTerm as it will create another copy of 'str'.
339+ return new Constant (loc , str );
320340 }
321341
322342 private RegExpTerm parseAtomEscape (SourceLocation loc , boolean inCharClass ) {
0 commit comments