3

我想修改我的编译器的代码生成器以使用访问者模式,因为当前的方法必须使用多个条件语句来检查孩子的真实类型,然后再生成相应的代码。但是,在访问孩子后,我无法获取孩子的属性。例如,在二进制表达式中,我使用这个:

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;

希望你能理解:)

4

1 回答 1

4

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

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

通常您不必保留属性,但您确实需要保留中间结果并将它们组合成其他中间结果以访问其他对象。我认为这里就是这种情况,但是交互足够复杂以至于有点混乱。

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

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

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

在简单对象的接受方法中,您只需执行标准visitor.visit(this)(但是您在 Object Pascal 中这么说)。

在像您这样的简单对象的访问方法中,TASTBooleanConstant您将调用适当的方法,在这种情况下FBuilder.CreateConstant,使用从对象中提取的值并将该方法的结果推送到访问者的堆栈中。

在像您这样更复杂的对象的 accept 方法中TASTBinaryExpression,您将首先调用孩子的接受方法,然后执行标准visitor.visit(this),确保首先访问孩子。

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

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

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

抱歉,这太啰嗦了——在代码中可能会更清晰,但希望这能让您了解如何处理这个问题。

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

于 2011-01-14T12:36:56.003 回答