加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 编程开发 > Java > 正文

java – 如何到达AST表达式的底部

发布时间:2020-12-15 00:43:27 所属栏目:Java 来源:网络整理
导读:我是AST的新手(我第一次写插件).表达在现实生活中可能相当复杂.例如,我想知道如何解决对齐的左侧和右侧. class Visitor extends ASTVisitor{ @Override public boolean visit(Assignment node) { //here,how do I get the final name to each each side of t
我是AST的新手(我第一次写插件).表达在现实生活中可能相当复杂.例如,我想知道如何解决对齐的左侧和右侧.
class Visitor extends ASTVisitor
{
    @Override
    public boolean visit(Assignment node)
    {
        //here,how do I get the final name to each each side of the assignment resolves?
    }
}

我还有另一个疑问,我如何获取用于调用方法的实例?

public boolean visit(MethodInvocation node)
{
    //how do I get to know the object used to invoke this method?
    //like,for example,MyClass is a class,and it has a field called myField
    //the type of myField has a method called myMethod.
    //how do I find myField? or for that matter some myLocalVariable used in the same way.
}

假设以下任务

SomeType.someStaticMethod(params).someInstanceMethod(moreParams).someField =
     [another expression with arbitrary complexity]

如何从Assigment节点访问someField?

而且,MethodInvocation的哪个属性为我提供了用于调用方法的实例?

编辑1:鉴于我收到的答案,我的问题显然不清楚.我不想解决这个特殊的表达方式.我希望能够在给定任何赋值的情况下找出它所分配的名称,以及分配给第一个名称的名称(如果不是rvalue).

因此,例如,方法调用的参数可以是字段访问或先前声明的局部变量.

SomeType.someStaticMethod(instance.field).someInstanceMethod(type.staticField,localVariable,localField).Field.destinationField

所以,这是一个充满希望的客观问题:给定左侧和右侧具有任意复杂性的任何赋值语句,如何获得分配给的最终字段/变量,以及分配的最终(如果有)字段/变量它.

编辑2:更具体地说,我想要实现的是不变性,通过注释@Const:

/**
* When Applied to a method,ensures the method doesn't change in any
* way the state of the object used to invoke it,i.e.,all the fields
* of the object must remain the same,and no field may be returned,* unless the field itself is marked as {@code @Const} or the field is
* a primitive non-array type. A method  annotated with {@code @Const} 
* can only invoke other {@code @Const} methods of its class,can only 
* use the class's fields to invoke {@code @Const} methods of the fields 
* classes and can only pass fields as parameters to methods that 
* annotate that formal parameter as {@code @Const}.
*
* When applied to a formal parameter,ensures the method will not
* modify the value referenced by the formal parameter. A formal   
* parameter annotated as {@code @Const} will not be aliased inside the
* body of the method. The method is not allowed to invoke another 
* method and pass the annotated parameter,save if the other method 
* also annotates the formal parameter as {@code @Const}. The method is 
* not allowed to use the parameter to invoke any of its type's methods,* unless the method being invoked is also annotated as {@code @Const}
* 
* When applied to a field,ensures the field cannot be aliased and that
* no code can alter the state of that field,either from inside the   
* class that owns the field or from outside it. Any constructor in any
* derived class is allowed to set the value of the field and invoke any
* methods using it. As for methods,only those annotated as
* {@code @Const} may be invoked using the field. The field may only be
* passed as a parameter to a method if the method annotates the 
* corresponding formal parameter as {@code @Const}
* 
* When applied to a local variable,ensures neither the block where the
* variable is declared or any nested block will alter the value of that 
* local variable. The local variable may be defined only once,at any
* point where it is in scope and cannot be aliased. Only methods
* annotated as {@code @Const} may be invoked using this variable,and 
* the variable  may only be passed as a parameter to another method if 
* said method annotates its corresponding formal parameter as
* {@code @Const}
*
*/
@Retention(RetentionPolicy.SOURCE)
@Target({ElementType.METHOD,ElementType.PARAMETER,ElementType.FIELD,ElementType.LOCAL_VARIABLE})
@Inherited
public @interface Const
{

}

要做到这一点,我要做的第一件事就是将作业的左侧标记为@Const(足够简单).我还必须检测何时和表达式的右侧是标记为@Const的字段,在这种情况下,它只能在相同类型的@Const变量的定义中分配.

问题是我很难找到表达式右侧的终极字段,以避免使字段混叠并使@Const注释无效.

解决方法

访问者是一个非常好的工具,但是对于特定问题的正确解决方案并不总是让一个访问者耐心地等待它的访问方法被调用…你问的问题就是这种情况的一个例子.

让我们改写你想要做的事情:

>您想要识别以识别每个任务(即leftSide = rightSide)
>对于每个赋值,您要确定左侧的性质(即,它是局部变量还是字段访问),如果它确实是字段访问,则您要构建对应的“路径”到该字段(即源对象,后跟一系列方法调用或字段访问,以字段访问结束).
>对于每个作业,您要确定与右侧对应的类似“路径”.

我认为你已经解决了第1点:你只需创建一个扩展org.eclipse.jdt.core.dom.ASTVisitor的类;在那里,你重写#visit(Assignment)方法.最后,在适当的地方,您实例化您的访问者类,并让它访问AST树,从哪个节点开始匹配您的需求(很可能是CompilationUnit,TypeDeclaration或MethodDeclaration的实例).

那又怎样? #visit(Assignment)方法确实接收Assignment节点.直接在该对象上,您可以获得左侧和右侧表达式(assignment.getLeftHandSide()和assignment.getRightHandSide()).正如你所提到的,两个都是表达式,它们可能变得相当复杂,那么我们如何从这些子树中提取干净,线性的“路径”呢?访问者肯定是这样做的最好方式,但这里有捕获,应该使用不同的访问者来完成,而不是让你的第一个访问者(一个捕获的分配)继续下降任何一方的表达.技术上可以使用单个访问者完成所有操作,但这将涉及访问者内部的重要状态管理.无论如何,我非常相信这种管理的复杂程度如此之高,以至于这种实施实际上效率会低于不同的访问者.

所以我们可以像这样形象:

class MyAssignmentListVisitor extends ASTVisitor {
    @Override
    public boolean visit(Assignment assignment) {
        FieldAccessLineralizationVisitor leftHandSideVisitor = new FieldAccessLineralizationVisitor();
        assignment.getLeftHandSide().accept(leftHandSideVisitor);
        LinearFieldAccess leftHandSidePath = leftHandSideVisitor.asLinearFieldAccess();

        FieldAccessLineralizationVisitor rightHandSideVisitor = new FieldAccessLineralizationVisitor();
        assignment.getRightHandSide().accept(rightHandSideVisitor);
        LinearFieldAccess rightHandSidePath = rightHandSideVisitor.asLinearFieldAccess();

        processAssigment(leftHandSidePath,rightHandSidePath);

        return true;
    }
}

class FieldAccessLineralizationVisitor extends ASTVisitor {

    List<?> significantFieldAccessParts = [...];

    // ... various visit method expecting concrete subtypes of Expression ...

    @Override
    public boolean visit(Assignment assignment) {
        // Found an assignment inside an assignment; ignore its
        // left hand side,as it does not affect the "path" for 
        // the assignment currently being investigated

        assignment.getRightHandSide().accept(this);

        return false;
    }
}

请注意,此代码中MyAssignmentListVisitor.visit(Assignment)返回true,表示应递归检查赋值的子项.这听起来可能没必要,Java语言确实支持几种结构,其中赋值可能包含其他赋值;例如考虑以下极端情况:

(varA = someObject).someField = varB = (varC = new SomeClass(varD = "string").someField);

出于同样的原因,在表达式的线性化期间仅访问赋值的右侧,因为赋值的“结果值”是其右侧.在这种情况下,左手边只是一个可以安全忽略的副作用.

鉴于我不知道您的特定情况所需的信息的性质,我将不再进一步描述路径实际建模的原型.您可能更适合为左侧表达式和右侧表达式分别创建不同的访问者类,例如为了更好地处理右侧可能实际涉及多个变量/字段/方法调用的事实通过二元运算符.这将是你的决定.

关于要讨论的AST树的访问者遍历仍然存在一些主要问题,即通过依赖于默认节点遍历顺序,您失去了获取每个节点之间关系的信息的机会.例如,给定表达式this.someMethod(this.fieldA).fieldB,您将看到类似于以下序列的内容:

FieldAccess      => corresponding to the whole expression
MethodInvovation => corresponding to this.someMethod(this.fieldA)
ThisExpression
SimpleName ("someMethod")
FieldAccess      => corresponding to this.fieldA
ThisExpression
SimpleName ("fieldA")
SimpleName ("fieldB")

根本无法从这一系列事件中推断出线性化表达式.您将希望明确拦截每个节点,并仅在适当的情况下以适当的顺序显式递归节点的子节点.例如,我们可以做到以下几点:

@Override
    public boolean visit(FieldAccess fieldAccess) {
        // FieldAccess :: <expression>.<name>

        // First descend on the "subject" of the field access
        fieldAccess.getExpression().accept(this);

        // Then append the name of the accessed field itself
        this.path.append(fieldAccess.getName().getIdentifier());

        return false;
    }

    @Override
    public boolean visit(MethodInvocation methodInvocation) {
        // MethodInvocation :: <expression>.<methodName><<typeArguments>>(arguments)

        // First descend on the "subject" of the method invocation
        methodInvocation.getExpression().accept(this);

        // Then append the name of the accessed field itself
        this.path.append(methodAccess.getName().getIdentifier() + "()");

        return false;
    }

    @Override
    public boolean visit(ThisExpression thisExpression) {
        // ThisExpression :: [<qualifier>.] this

        // I will ignore the qualifier part for now,it will be up
        // to you to determine if it is pertinent
        this.path.append("this");

        return false;
    }

在前面的示例中,这些方法将在路径中收集以下序列:this,someMethod(),fieldB.我认为,这非常接近你所寻找的.如果您想收集所有字段访问/方法调用序列(例如,您希望访问者返回this,fieldB和this,fieldA),那么您可以重写访问(MethodInvocation)方法大致类似于这个:

@Override
    public boolean visit(MethodInvocation methodInvocation) {
        // MethodInvocation :: <expression>.<methodName><<typeArguments>>(arguments)

        // First descend on the "subject" of the method invocation
        methodInvocation.getExpression().accept(this);

        // Then append the name of the accessed field itself
        this.path.append(methodAccess.getName().getIdentifier() + "()");

        // Now deal with method arguments,each within its own,distinct access chain
        for (Expression arg : methodInvocation.getArguments()) {
            LinearPath orginalPath = this.path;
            this.path = new LinearPath();

            arg.accept(this);

            this.collectedPaths.append(this.path);
            this.path = originalPath;
        }

        return false;
    }

最后,如果您想知道路径中每一步的值类型,则必须查看与每个节点关联的绑定对象,例如:methodInvocation.resolveMethodBinding().getDeclaringClass().但请注意,必须在构建AST树时明确请求绑定解析.

上面的代码将无法正确处理更多的语言结构;不过,我相信你应该能够自己解决这些遗留问题.如果您需要查看引用实现,请查看类org.eclipse.jdt.internal.core.dom.rewrite.ASTRewriteFlattener,它基本上从现有的AST树重构Java源代码;虽然这个特定的访问者比大多数其他ASTVisitors大得多,但它更容易理解.

对OP编辑#2的响应更新

这是您最近编辑后的更新起点.仍有许多情况需要处理,但这更符合您的具体问题.另请注意,尽管我使用了大量的instanceof检查(因为这对我来说比较容易,因为我在简单的文本编辑器中编写代码,并且没有在ASTNode常量上完成代码),您可以选择node.getNodeType()上的switch语句,通常效率更高.

class ConstCheckVisitor extends ASTVisitor {

    @Override
    public boolean visit(MethodInvocation methodInvocation) {    
        if (isConst(methodInvocation.getExpression())) {
            if (isConst(methodInvocation.resolveMethodBinding().getMethodDeclaration()))
                reportInvokingNonConstMethodOnConstSubject(methodInvocation);
        }

        return true;
    }

    @Override
    public boolean visit(Assignment assignment) {
        if (isConst(assignment.getLeftHandSide())) {
            if ( /* assignment to @Const value is not acceptable in the current situation */ )
                reportAssignmentToConst(assignment.getLeftHandSide());

            // FIXME: I assume here that aliasing a @Const value to
            //        another @Const value is acceptable. Is that right?

        } else if (isImplicitelyConst(assigment.getLeftHandSide())) {
            reportAssignmentToImplicitConst(assignment.getLeftHandSide());        

        } else if (isConst(assignment.getRightHandSide())) {
            reportAliasing(assignment.getRightHandSide());
        }

        return true;
    }

    private boolean isConst(Expression expression) {
        if (expression instanceof FieldAccess)
            return (isConst(((FieldAccess) expression).resolveFieldBinding()));

        if (expression instanceof SuperFieldAccess)
            return isConst(((SuperFieldAccess) expression).resolveFieldBinding());

        if (expression instanceof Name)
            return isConst(((Name) expression).resolveBinding());

        if (expression instanceof ArrayAccess)
            return isConst(((ArrayAccess) expression).getArray());

        if (expression instanceof Assignment)
            return isConst(((Assignment) expression).getRightHandSide());

        return false;
    }

    private boolean isImplicitConst(Expression expression) {
        // Check if field is actually accessed through a @Const chain
        if (expression instanceof FieldAccess)
            return isConst((FieldAccess expression).getExpression()) ||
                   isimplicitConst((FieldAccess expression).getExpression());

        // FIXME: Not sure about the effect of MethodInvocation,assuming
        //        that its subject is const or implicitly const

        return false;
    }

    private boolean isConst(IBinding binding) {
        if ((binding instanceof IVariableBinding) || (binding instanceof IMethodBinding))
            return containsConstAnnotation(binding.getAnnotations());

        return false;
    }
}

希望有所帮助.

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读