Обновление принял ответ Ира Бакстера, поскольку он направил меня в правильном направлении: сначала я понял, что мне действительно нужно, начав реализацию этапа компиляции, и довольно скоро стало очевидно, что обход вузлы сделали невозможным подход.Не все узлы должны быть посещены, а некоторые из них в обратном порядке (например, сначала права доступа присваивания, чтобы компилятор мог проверить, совпадает ли тип с оператором rhs /).Установка обхода в посетителе делает все это очень простым.
Я поиграюсь с AST и тому подобным, прежде чем принять решение о крупной рефактории обработки мини-языка, используемого в приложении.Я построил Lexer / Parser и могу получить AST просто отлично.Также есть Visitor, и в качестве конкретной реализации я создал ASTToOriginal, который просто воссоздает исходный файл.В конце концов, есть какой-то компилятор, который также реализует Vsisitor и создает реальный код C ++ во время выполнения, поэтому я хочу убедиться, что все правильно с самого начала.Хотя теперь все работает нормально, есть некоторый подобный / дублирующий код, поскольку порядок обхода реализован в самом Visitor.
При поиске дополнительной информации кажется, что некоторые реализации предпочитают сохранять порядок обхода в посещаемых объектахсами по себе, чтобы не повторять это у каждого конкретного посетителя.Даже GoF только кратко говорит об этом, таким же образом.Поэтому я хотел попробовать и этот подход, но довольно скоро застрял .. Позвольте мне объяснить.
Пример строки исходного кода и соответствующих узлов AST:
if(t>100?x=1;sety(20,true):x=2)
Conditional
BinaryOp
left=Variable [name=t], operator=[>], right=Integer [value=100]
IfTrue
Assignment
left=Variable [name=x], operator=[=], right=Integer [value=1]
Method
MethodName [name=sety], Arguments( Integer [value=20], Boolean [value=true] )
IfFalse
Assignment
left=Variable [name=x], operator=[=], right=Integer [value=1]
Некоторый код:
class BinaryOp {
void Accept( Visitor* v ){ v->Visit( this ); }
Expr* left;
Op* op;
Expr* right;
};
class Variable {
void Accept( Visitor* v ){ v->Visit( this ); }
Name* name;
};
class Visitor { //provide basic traversal, terminal visitors are abstract
void Visit( Variable* ) = 0;
void Visit( BinaryOp* p ) {
p->left->Accept( this );
p->op->Accept( this );
p->right->Accept( this );
}
void Visit( Conditional* p ) {
p->cond->Accept( this );
VisitList( p->ifTrue ); //VisitList just iterates over the array, calling Accept on each element
VisitList( p->ifFalse );
}
};
Реализация ASTToOriginal довольно проста: все абстрактные методы Visitor просто выводят имя или значение члена терминала.Для нетерминалов это зависит;печать Назначения работает нормально с обходом посетителя по умолчанию, для Условного необходим дополнительный код:
class ASTToOriginal {
void Visit( Conditional* p ) {
str << "if(";
p->cond->Accept( this );
str << "?";
//VisitListWithPostOp is like VisitList but calls op for each *except the last* iteration
VisitListWithPostOp( p->ifTrue, AppendText( str, ";" ) );
VisitListWithPostOp( p->ifFalse, AppendText( str, ";" ) );
str << ")";
}
};
Таким образом, можно видеть, что оба метода Visit для Условного в Visitor и ASTToOriginal действительно очень похожи.Однако попытка решить эту проблему путем введения обхода в узлы не только усугубила ситуацию, но и привела к полному беспорядку.Я попробовал подход с методами PreVisit и PostVisit, который решил некоторые проблемы, но просто вводил все больше и больше кода в узлы.Это также стало выглядеть так, как будто мне нужно было отслеживать несколько состояний внутри Посетителя, чтобы иметь возможность знать, когда добавлять закрывающие скобки и т. Д.
class BinaryOp {
void Accept( Conditional* v ) {
v->Visit( this );
op->Accept( v )
VisitList( ifTrue, v );
VisitList( ifFalse, v );
};
class Vistor {
//now all methods are pure virtual
};
class ASTToOriginal {
void Visit( Conditional* p ) {
str << "if(";
//now what??? after returning here, BinaryOp will visit the op automatically so I can't insert the "?"
//If I make a PostVisit( BinaryOp* ), and call it it BinaryOp::Accept, I get the chance to insert the "?",
//but now I have to keep a state: my PostVisit method needs to know it's currently being called as part of a Conditional
//Things are even worse for the ifTrue/ifFalse statement arrays: each element needs a ";" appended, but not the last one,
//how am I ever going to do that in a clean way?
}
};
Вопрос : естьэтот подход просто не подходит для моего случая, или я пропускаю что-то важное?Есть ли общий дизайн, чтобы справиться с этими проблемами?Что если мне также понадобится обход в другом направлении?