Шаблон посетителя и генерация кода компилятора, как получить дочерние атрибуты?

Я хотел бы изменить генератор кода моего компилятора, чтобы использовать шаблон посетителя, поскольку текущий подход должен использовать несколько условных операторов для проверки реального типа дочернего элемента перед созданием соответствующего кода. Однако у меня проблемы с получением атрибутов детей после их посещения. Например, в двоичном выражении я использую это:

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;

Надеюсь, вы это понимаете :)


person LeleDumbo    schedule 14.01.2011    source источник
comment
Вопрос не ясен. Какие детские атрибуты вы имеете в виду? Вы имеете в виду, что GenerateExpressionCode играет роль метода посещения?   -  person ssmir    schedule 14.01.2011
comment
@ssmir: Если я правильно понял, показанный код является фрагментом реализации без посетителей.   -  person Don Roby    schedule 14.01.2011
comment
@ssmir: из приведенного выше фрагмента кода текущий узел (Self) требует сгенерированного кода из его левого и правого операнда (которые являются дочерними элементами Self). Вот что я на самом деле имею в виду под атрибутами.   -  person LeleDumbo    schedule 14.01.2011
comment
@Don Roby: да, это Object Pascal   -  person LeleDumbo    schedule 15.01.2011


Ответы (1)


В шаблоне посетителя метод посещения обычно недействителен, поэтому я не могу получить код выражения из LHS и RHS. Сохранение общих глобальных переменных невозможно, поскольку генерация кода выражений является рекурсивной, поэтому предыдущие значения, хранящиеся в переменных, могут быть удалены.

Вам нужно получать дочерние атрибуты при их посещении, удерживать любые необходимые атрибуты и следить за тем, чтобы они у вас оставались, когда они вам нужны. Это может немного усложнить внутреннюю структуру вашего посетителя, но это, безусловно, осуществимо. Генерация кода, безусловно, является распространенным использованием шаблона посетителя.

Обычно вам не нужно удерживать атрибуты, но вам нужно удерживать промежуточные результаты и комбинировать их с другими промежуточными результатами при посещении других объектов. Я думаю, что здесь дело обстоит именно так, но взаимодействие достаточно сложное, чтобы немного сбивать с толку.

Я не эксперт по Object Pascal, поэтому вместо того, чтобы пытаться писать настоящий код, я просто опишу, как бы я с ним справился.

В этом случае я бы, вероятно, использовал стек, содержащий промежуточные результаты.

Порядок обхода может быть задан в методах accept узлов, в методах посещения посетителя или во внешнем итераторе. Для простоты я предполагаю, что это в методах accept.

В методе accept простых объектов вы бы просто сделали стандартный visitor.visit(this) (как бы вы ни говорили это в Object Pascal).

В методе посещения для более простых объектов, таких как ваш TASTBooleanConstant, вы должны вызвать соответствующий метод, в данном случае FBuilder.CreateConstant со значениями, которые вы извлекаете из объекта, и помещать результат этого метода в стек посетителя.

В методе accept более сложного объекта, такого как ваш TASTBinaryExpression, вы должны сначала вызвать методы accept для дочерних элементов, а затем выполнить стандартный visitor.visit(this), гарантируя, что дочерние элементы будут посещены в первую очередь.

Затем, поскольку дети были посещены первыми, их результаты должны быть в стеке при вызове метода посещения для сложного объекта. В этом методе посещения вы должны вытолкнуть соответствующие результаты из стека в локальные переменные, вызвать соответствующий метод FBuilder.CreateXxx в зависимости от того, какой оператор у вас есть, передать эти значения в качестве параметров и поместить результат в стек.

Для объектов TASTUnaryExpression это было бы похоже, но только с одним дочерним элементом, о котором нужно беспокоиться в методе accept, и только с одним промежуточным результатом, который нужно извлечь из стека и использовать в методе посещения.

В своем клиентском коде вы создаете посетителя и вызываете метод accept вашего верхнего узла, передавая посетителя в качестве аргумента. После завершения всей рекурсии стек должен содержать только окончательный результат, а класс посетителя должен предоставить метод getResult, позволяющий клиенту получить его.

Извините, это так многословно - это может быть понятнее в коде, но, надеюсь, это даст вам представление о том, как с этим справиться.

Хорошим ресурсом для изучения того, как проводить рефакторинг для внедрения шаблонов в существующий код, является книга Джошуа Кериевски Рефакторинг по шаблонам.

person Don Roby    schedule 14.01.2011
comment
Хм... понял. Использование стека кажется решением (в основном мое рекурсивное решение также включает неявный стек, почему я не могу об этом подумать? Дох). Спасибо. - person LeleDumbo; 15.01.2011
comment
Что бы вы сделали, если ваш генератор кода требует как минимум двух типов алгоритмов обхода, например, случай, когда > 1, затем 1, иначе 0, где вам нужно сгенерировать метки и переходы, которые вы можете сгенерировать путем обхода сверху вниз, но для > 1 вам нужен ребенок - первый подход. - person Puchacz; 27.12.2016