Higher Order Macros in C++
journal.stuffwithstuff.com
journal.stuffwithstuff.com
https://en.wikipedia.org/wiki/X_Macro
https://en.wikibooks.org/wiki/C_Programming/Preprocessor#X-M...
It's very useful in some situations. It offers, in a way, a mean to do a sort of compile-time introspection on some class/enum.
class Nineties : public Node {
void accept(Visitor &v) {
cout "Visitor " << v << ", welcome to the year 1995" << endl;
}
}
A better idea would be to write all this code processing in the language that is being byte-code interpreted.
That is to say, use an existing implementation of that language to write a compiler which produces byte code. Then write the byte code interpreter for that in C++. Finally, include the compiler's byte code image in the C++ program (as a static array or whatever), so that the program is a complete implementation which can compile code and then run the bytecode.That presumes the language being compiled somehow frees you from the need to use the visitor pattern.
Personally, I really like visitor. It's not a pattern I use often (basically, only for AST visitors), but it's pretty fanstastic in the places where I do need it.
That kind of organization really works well when you don't care about the other classes which do that, like device drivers. I don't care about how a tty handles write when I'm working on a network stack's write; I don't need to see those side by side.
Thus even if you give me a language with real OO that has multiple dispatch so that Visitor disappears, I still won't do it this way. I'm going to have a single AST pretty printing function that handles all the cases, a code generating function that handles all the cases and so on. If someone adds a case, they will just have to add them to half a dozen functions.
Just to be explicit, here's the kind of code I think you're describing (using some vague Java-esque pseudo-code):
interface Expression {
String prettyPrint();
int evaluate();
}
class Plus extends Expression {
Expression left, right;
String prettyPrint() {
return left.prettyPrint() + " + " + right.prettyPrint();
}
int evaluate() {
return left.evaluate() + right.evaluate();
}
}
class Number extends Expression {
int value;
String prettyPrint() {
return value.toString();
}
int evaluate() {
return value;
}
}
This is vanilla OOP code. You have all of the operations on a class directly on that class. Like you note, pretty printing and evaluating are mixed together. With something like an AST, it doesn't scale and gets gross.Here's the AST classes supporting visitor pattern:
interface Expression {
String prettyPrint();
int evaluate();
T accept<T>(Visitor<T> visitor);
}
class Plus extends Expression {
Expression left, right;
T accept<T>(Visitor<T> visitor) { return visitor.visitPlus(this); }
}
class Number extends Expression {
int value;
T accept<T>(Visitor<T> visitor) { return visitor.visitNumber(this); }
}
interface Visitor<T> {
T visitPlus(Plus plus);
T visitNumber(Number number);
}
These are now pure data classes, like we want. The only extra bit is the visitor pattern itself.Now we can separate out our concerns. Here's pretty-printing:
class PrettyPrintVisitor extends Visitor<String> {
String visitPlus(Plus plus) {
return plus.left.accept(this) + " + " + plus.right.accept(this);
}
String visitNumber(Number number) {
return number.value.toString();
}
}
And here's evaluating: class EvalVisitor extends Visitor<int> {
int visitPlus(Plus plus) {
return plus.left.accept(this) + plus.right.accept(this);
}
int visitNumber(Number number) {
return number.value;
}
}
> I'm going to have a single AST pretty printing function that handles all the cases, a code generating function that handles all the cases and so on.This is exactly what visitor gives you except replace "function" with "class".
> If someone adds a case, they will just have to add them to half a dozen functions.
They'll have to remember to do that. The visitor pattern makes it a compile error to forget to do that.
I also find the pattern scales up better than doing all of the type tests inside a single enormous function. I like having a separate method per AST type.
The rest comes from my small experience, in college we had a project patching java bytecode on the fly, you could generate correct logic at the bytecode level, but you would lose all the benefit of high level language (typechecking, debugging). One team had this excellent idea to just write the smallest bytecode hook/wrapper that would then call normal Java code.
When I was working on Magpie, I did just that. This script[1] generates this file[2].
[1]: https://github.com/munificent/magpie/blob/master/script/gene... [2]: https://github.com/munificent/magpie/blob/master/src/Syntax/...
I got this trick from Jim Hugunin, of Jython and IronPython fame.
I've never used Chaos PP (the predecessor of Boost.Preprocessor), but IIRC, it enables even more cool stuff at the cost of reduced portability (Boost.Preprocessor contains all the hacks to work on broken preprocessors, while I think Chaos takes more of a "fix your preprocessor" approach).
http://www.boost.org/doc/libs/1_59_0/libs/preprocessor/doc/i...
Group discussions : https://groups.google.com/a/isocpp.org/forum/?fromgroups#!fo...
One proposition : http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n411...
For the AstVisitor, you could make a templated class AstVisitor<T>. You would then derive AstVisitor<BoolLiteral> in order to have a class that only visits particular node types.
template<typename T>
class AstVisitor : public GenericAstVisitor {
// Implements GenericAstVisitor::Visit
virtual void Visit(AstNode* expr) {
T* t_expr = dynamic_cast<T*>(expr);
if(t_expr){
Visit(t_expr);
}
}
virtual void Visit(T* expr) = 0;
};
If you need to visit multiple node types, then you would need to head into variadic templates. Here, you would inherit from AstVisitor<BoolLiteral, NumLiteral>, then implement a visitor for each type. template<typename... T>
class AstVisitor;
template<typename First, typename... Rest>
class AstVisitor<First, Rest...> : public AstVisitor<Rest...> {
virtual void Visit(AstNode* expr) {
First* f_expr = dynamic_cast<First*>(expr);
if(f_expr){
Visit(f_expr);
} else {
AstVisitor<Rest...>::Visit(expr);
}
}
virtual void Visit(First* expr) = 0;
};
template<>
class AstVisitor<> : GenericAstVisitor {
virtual void Visit(AstNode*) { }
};
For the enums and switch statements, I don't know of any way to get away from using macros. There have been some proposals for C++17 to include compile-time reflection, but until then, we're stuck with the macros.(Of course, these are generalizations, and only how they work without particular care.)
http://eli.thegreenplace.net/2014/06/04/using-asdl-to-descri...
To me this [1] looks way better than a bunch of C++ macros.
[1] https://hg.python.org/cpython/file/tip/Parser/Python.asdl
Or you can write code generation in C, like the sqlite lemon parser, and then you really don't need any more tools.
You literally just described macros.
I use this for dispatching, enum creation, method creation etc.
But probably a good resource for people trying to save their time by macro based code generation ;)