访问者模式和编译器代码生成,如何获取子属性?

时间:2011-01-14 11:51:20

标签: design-patterns code-generation visitor-pattern

我想修改我的编译器代码生成器以使用访问者模式,因为当前方法必须使用多个条件语句来检查子代的实际类型,然后才生成相应的代码。但是,我在访问后获得儿童属性时遇到了问题。例如,在二进制表达式中我使用:

LHSCode := GenerateExpressionCode(LHSNode);
RHSCode := GenerateExpressionCode(RHSNode);
CreateBinaryExpression(Self,LHS,RHS);

在访问者模式中,访问方法通常是无效的,因此我无法从LHS和RHS获取表达式代码。保持共享全局变量不是一种选择,因为表达式代码生成是递归的,因此可以删除变量中保存的先前值。

我只会显示二进制表达式,因为这是最复杂的部分(现在):

function TLLVMCodeGenerator.GenerateExpressionCode(
  Expr: TASTExpression): TLLVMValue;
var
  BinExpr: TASTBinaryExpression;
  UnExpr: TASTUnaryExpression;
  LHSCode, RHSCode, ExprCode: TLLVMValue;
  VarExpr: TASTVariableExpression;
begin
  if Expr is TASTBinaryExpression then begin
    BinExpr := Expr as TASTBinaryExpression;
    LHSCode := GenerateExpressionCode(BinExpr.LHS);
    RHSCode := GenerateExpressionCode(BinExpr.RHS);
    case BinExpr.Op of
      '<': Result := FBuilder.CreateICmp(ccSLT, LHSCode, RHSCode);
      '<=': Result := FBuilder.CreateICmp(ccSLE, LHSCode, RHSCode);
      '>': Result := FBuilder.CreateICmp(ccSGT, LHSCode, RHSCode);
      '>=': Result := FBuilder.CreateICmp(ccSGE, LHSCode, RHSCode);
      '==': Result := FBuilder.CreateICmp(ccEQ, LHSCode, RHSCode);
      '<>': Result := FBuilder.CreateICmp(ccNE, LHSCode, RHSCode);
      '/\': Result := FBuilder.CreateAnd(LHSCode, RHSCode);
      '\/': Result := FBuilder.CreateOr(LHSCode, RHSCode);
      '+': Result := FBuilder.CreateAdd(LHSCode, RHSCode);
      '-': Result := FBuilder.CreateSub(LHSCode, RHSCode);
      '*': Result := FBuilder.CreateMul(LHSCode, RHSCode);
      '/': Result := FBuilder.CreateSDiv(LHSCode, RHSCode);
    end;
  end else if Expr is TASTPrimaryExpression then
    if Expr is TASTBooleanConstant then
      with Expr as TASTBooleanConstant do
        Result := FBuilder.CreateConstant(Ord(Value), ltI1)
    else if Expr is TASTIntegerConstant then
      with Expr as TASTIntegerConstant do
        Result := FBuilder.CreateConstant(Value, ltI32)
    else if Expr is TASTUnaryExpression then begin
      UnExpr := Expr as TASTUnaryExpression;
      ExprCode := GenerateExpressionCode(UnExpr.Expr);
      case UnExpr.Op of
        '~': Result := FBuilder.CreateXor(
            FBuilder.CreateConstant(1, ltI1), ExprCode);
        '-': Result := FBuilder.CreateSub(
            FBuilder.CreateConstant(0, ltI32), ExprCode);
      end;
    end else if Expr is TASTVariableExpression then begin
      VarExpr := Expr as TASTVariableExpression;
      with VarExpr.VarDecl do
        Result := FBuilder.CreateVar(Ident, BaseTypeLLVMTypeMap[BaseType]);
    end;
end;

希望你理解:)

1 个答案:

答案 0 :(得分:3)

  

在访问者模式中,访问方法通常是无效的,因此我无法从LHS和RHS获取表达式代码。保持共享全局变量不是一种选择,因为表达式代码生成是递归的,因此可以删除变量中保存的先前值。

您需要在访问时获取子属性,保留您需要的任何属性,并确保在您需要时仍然拥有它们。这可能会使访问者的内部结构变得更复杂,但这肯定是可行的。代码生成绝对是访问者模式的常见用法。

通常您不必保留属性,但是您需要保留中间结果并将其合并到其他对象的其他中间结果中。我认为这就是这种情况,但这种互动很复杂,有点令人困惑。

我不是Object Pascal的专家,所以我不会尝试编写实际的代码,而只是描述我将如何处理它。

在这种情况下,我可能会使用堆栈保持中间结果。

遍历顺序可以在节点的接受方法或访问者的访问方法或外部迭代器中驱动。为简单起见,我将在接受方法中假设它。

在简单对象的accept方法中,您只需执行标准visitor.visit(this)(但是您可以在Object Pascal中说明。)

在像TASTBooleanConstant这样的简单对象的访问方法中,您将调用适当的方法,在本例中为FBuilder.CreateConstant,其中包含您从对象中提取的值并将该方法的结果推送到访问者的叠加。

在像TASTBinaryExpression这样的更复杂对象的accept方法中,您首先会调用子项上的accept方法,然后执行标准visitor.visit(this),确保首先访问子项。< / p>

然后,由于首先访问了子节点,因此在调用复杂对象的visit方法时,它们的结果应该在堆栈上。在该访问方法中,您可以将堆栈中的相应结果弹出到局部变量中,根据您拥有的运算符调用相应的FBuilder.CreateXxx方法,将这些值作为参数传递,并将结果放入堆栈。

对于TASTUnaryExpression个对象,它会是相似的,但是在accept方法中只有一个孩子需要担心,并且只有一个中间结果弹出堆栈并在访问方法中使用。

在您的客户端代码中,您创建访问者并调用顶级节点的accept方法,将访问者作为参数传递。在完成所有递归之后,堆栈应该只包含最终结果,访问者类应该提供getResult方法,允许客户端检索它。

对不起,这太冗长了 - 代码可能会更清晰,但希望这可以让你知道如何处理这个问题。

学习如何在现有代码中引入模式的重构资源的好资源是Joshua Kerievsky的书Refactoring to Patterns