Optimise primary prefix lookaheads

This commit is contained in:
Clément Fournier
2019-05-24 16:33:48 +02:00
parent 399ef5c172
commit 0ef4ec74af
6 changed files with 93 additions and 40 deletions

View File

@ -503,6 +503,14 @@ public class JavaParser {
}
}
// make the top node a child of the second node on the stack
private void injectTop() {
AbstractJavaNode top = (AbstractJavaNode) jjtree.popNode();
AbstractJavaNode prev = (AbstractJavaNode) jjtree.peekNode();
prev.jjtAddChild(top, prev.jjtGetNumChildren());
prev.jjtSetLastToken(top.jjtGetLastToken());
}
}
PARSER_END(JavaParser)
@ -2101,7 +2109,8 @@ void ClassOrInterfaceType() #void:
be sure that the last segment is a type name.
*/
( AmbiguousName() [ TypeArguments() ]) #ClassOrInterfaceType(>1)
AmbiguousName()
[ TypeArguments() #ClassOrInterfaceType(2) ]
{
// At this point the first ClassOrInterfaceType may be on top of the stack,
// but its image is not set. If it is on the stack we need to shrink the bounds
@ -2122,18 +2131,19 @@ void ClassOrInterfaceType() #void:
executes a loop. That scheme preserves the position of type arguments and annotations.
See #1150.
*/
( LOOKAHEAD(2)
(
"."
(TypeAnnotation())*
<IDENTIFIER> { jjtThis.setImage(getToken(0).getImage()); }
{jjtree.extendLeft();} // We'll enclose the previous segment
[ LOOKAHEAD( "<" ) TypeArguments() ]
) #ClassOrInterfaceType
)*
( LOOKAHEAD(2) "." ClassTypeSegment() )*
{ forceTypeContext(); }
}
private void ClassTypeSegment() #ClassOrInterfaceType:
{}
{
TypeAnnotationList()
<IDENTIFIER>
// We'll enclose the previous segment
{ setLastTokenImage(jjtThis); jjtree.extendLeft();}
[ TypeArguments() ]
}
void TypeArguments():
{}
@ -2478,31 +2488,57 @@ void PrimaryPrefix() #void :
Literal()
| "this" #ThisExpression
| "super" #SuperExpression
| UnqualifiedAllocationExpr()
| ("void" "." "class") #ClassLiteral
| (PrimitiveType() [ Dims() ] ) #ArrayType(>1)
(
MethodReference()
| "." "class" #ClassLiteral(1)
)
// todo we can probably simplify the lambda lookaheads
| LOOKAHEAD( <IDENTIFIER> "->", {!inSwitchLabel} ) LambdaExpression()
| LOOKAHEAD( "(" <IDENTIFIER> ( "," <IDENTIFIER> )* ")" "->" , {!inSwitchLabel} ) LambdaExpression()
| LOOKAHEAD( LambdaParameterList() "->", {!inSwitchLabel} ) LambdaExpression()
| ("(" Expression() ")") #ParenthesizedExpression
| UnqualifiedAllocationExpr()
| LOOKAHEAD( TypeOrVoid() "." "class" )
(TypeOrVoid() "." "class") #ClassLiteral
| LOOKAHEAD( ReferenceType() "::" [TypeArguments()] "new" )
ReferenceType() MethodReference()
//| LOOKAHEAD( TypeOrVoid() "." "class" )
// (TypeOrVoid() "." "class") #ClassLiteral
//| LOOKAHEAD( ReferenceType() "::" [TypeArguments()] "new" )
// ReferenceType() MethodReference()
// not a constructor reference, and is just a sequence of identifiers: could be an expression or a type
| LOOKAHEAD( Name() "::" )
AmbiguousName() MethodReference()
//| LOOKAHEAD( Name() "::" )
// AmbiguousName() MethodReference()
// we know it's not just a sequence of identifiers, so if
// ReferenceType matches, there are type parameters or annots,
// which means we shouldn't parse as an ambiguous name otherwise
// we choke on the type parameters
| LOOKAHEAD( ReferenceType() "::" )
ReferenceType() MethodReference()
| AmbiguousName() [ LOOKAHEAD("." "super" ".")
// The LHS of this or super is necessarily a type name, ie ambiguous
// Having this here instead of in PrimarySuffix makes the grammar more
// restrictive and also allows ExplicitConstructorInvocation to be parsed
"." "super" #SuperExpression(1) MemberSelector()
]
//| LOOKAHEAD( ReferenceType() "::" )
// ReferenceType() MethodReference()
| AmbiguousName() [ PrimaryStep2() ]
}
// Step right after the *first* ambiguous name if any
// then we have more options than a primary suffix,
// we can still expect the rest of a type name +
void PrimaryStep2() #void:
{}
{
{forceTypeContext();} TypeArguments() {injectTop();} ( "." ClassTypeSegment() )* MethodReference()
| MethodReference()
| ArgumentList() #MethodCall(2)
| "." (
{forceTypeContext();} "class" #ClassLiteral(1)
| "this" #ThisExpression(1)
| MemberSelector()
// we need to lookahead for the dot after "super", because in qualified
// super constructor invocation the "super" might be followed by a "("
| LOOKAHEAD("super" ".") "super" #SuperExpression(1) "." MemberSelector()
)
| LOOKAHEAD("@" | "[" "]") {forceTypeContext();} Dims() #ArrayType(2) (MethodReference() | "." "class" #ClassLiteral(1))
| "[" Expression() "]" #ArrayAccess(2)
}
/**
@ -2516,38 +2552,39 @@ void PrimarySuffix() #void :
{}
{
MethodReference()
| ArgumentList() #MethodCall(2)
| ("[" Expression() "]") #ArrayAccess(2)
//| ArgumentList() #MethodCall(2)
| "[" Expression() "]" #ArrayAccess(2)
// all the following start with a "."
| LOOKAHEAD(2) "." "this" #ThisExpression(1)
| LOOKAHEAD(2) QualifiedAllocationExpr()
| LOOKAHEAD("." [TypeArguments()] <IDENTIFIER>) MemberSelector() // more complex method call patterns
//| LOOKAHEAD(2) "." "this" #ThisExpression(1)
| "." MemberSelector()
}
private void SuffixLAhead() #void:
{}
{
// not "super", otherwise runs into conflicts with ExplicitConstructorInvocation
"." ("new" | "this" | [ TypeArguments() ] <IDENTIFIER>)
"." ("new" | <IDENTIFIER> | "<")
| "::"
| "["
| "("
}
void MemberSelector() #void :
{}
{
QualifiedAllocationExpr()
// if there are type arguments, this is a method call
LOOKAHEAD(2) ("." {jjtree.extendLeft();} TypeArguments() <IDENTIFIER> {setLastTokenImage (jjtThis) ;} ArgumentList()) #MethodCall
| LOOKAHEAD(3) ("." {jjtree.extendLeft();} <IDENTIFIER> {setLastTokenImage(jjtThis);} ArgumentList()) #MethodCall
| ("." {jjtree.extendLeft();} <IDENTIFIER> {setLastTokenImage(jjtThis);}) #FieldAccess
| (TypeArguments() <IDENTIFIER> {setLastTokenImage(jjtThis);} ArgumentList()) #MethodCall(3)
| LOOKAHEAD(2) (<IDENTIFIER> {setLastTokenImage(jjtThis);} ArgumentList()) #MethodCall(2)
| (<IDENTIFIER> {setLastTokenImage(jjtThis);}) #FieldAccess(1)
}
void MethodReference(): // LHS is injected
{checkForBadMethodReferenceUsage();}
{
"::" {jjtree.extendLeft();}
[TypeArguments()] ( "new" | <IDENTIFIER>) {setLastTokenImage(jjtThis);}
[TypeArguments()]
( "new" | <IDENTIFIER> ) {setLastTokenImage(jjtThis);}
{/*empty*/}
}
void LambdaExpression():
@ -2640,7 +2677,6 @@ void ArgumentList() :
void QualifiedAllocationExpr() #ConstructorCall:
{}
{
"."
"new"
{jjtree.extendLeft();}
[ TypeArguments() ]

View File

@ -14,7 +14,7 @@ package net.sourceforge.pmd.lang.java.ast;
*
* </pre>
*/
public final class ASTArrayType extends AbstractJavaTypeNode implements ASTReferenceType {
public final class ASTArrayType extends AbstractJavaTypeNode implements ASTReferenceType, LeftRecursiveNode {
ASTArrayType(int id) {
super(id);
}

View File

@ -15,7 +15,7 @@ import org.checkerframework.checker.nullness.qual.Nullable;
*
* </pre>
*/
public final class ASTClassLiteral extends AbstractJavaTypeNode implements ASTLiteral {
public final class ASTClassLiteral extends AbstractJavaTypeNode implements ASTLiteral, LeftRecursiveNode {
ASTClassLiteral(int id) {
super(id);
}

View File

@ -9,6 +9,8 @@
package net.sourceforge.pmd.lang.java.ast;
import java.util.List;
import org.checkerframework.checker.nullness.qual.Nullable;
import net.sourceforge.pmd.lang.java.typeresolution.typedefinition.JavaTypeDefinition;

View File

@ -35,12 +35,21 @@ public final class ASTMethodReference extends AbstractJavaTypeNode implements AS
}
@Override
public void jjtClose() {
super.jjtClose();
ASTReferenceType lhs = getLhsType();
if (isConstructorReference() && lhs instanceof ASTAmbiguousName) {
replaceChildAt(0, ((ASTAmbiguousName) lhs).forceTypeContext());
}
}
/**
* Returns true if this is a constructor reference,
* e.g. {@code ArrayList::new}.
*/
public boolean isConstructorReference() {
return getImage().equals("new");
return "new".equals(getImage());
}

View File

@ -83,6 +83,12 @@ class ASTMethodReferenceTest : ParserTestSpec({
}
}
}
"java.util.Map<String, String>.Entry<String, String>::foo" should matchExpr<ASTMethodReference> {
it::getMethodName shouldBe "foo"
it::getLhsType shouldBe classType("Entry") // ignore the rest
}
}
parserTest("Constructor reference") {