Visitor Pattern Generator in C++

09 Feb 2016

The visitor pattern gets slagged on a bit too much than it deserves. It’s a tool, and it may be a substantial improvement (depending on how deep you are when you reach for it).

In the course of reworking some legacy code, I had to explain the visitor pattern to my teammates, and I thought the best way to explain it would be to write a little visitor pattern code generator.

(Apologies for the terrible style, I was in a hurry).

#include <assert.h>
#include <fstream>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>

// prints to out something like "a.o b.o ";
void objects(std::ostream& out, std::vector<std::string> enumvalues) {
  for (std::vector<std::string>::iterator i = enumvalues.begin();
       i != enumvalues.end();
       ++i) {
    out << *i << ".o ";
  }
}

// prints to out a Makefile rule for building a static library
void libraryrule(std::ostream& out, std::string enumname, std::vector<std::string> enumvalues) {
  out << enumname << ".a: ";
  objects(out, enumvalues);
  out << "\n";
  out << "\tar cr " << enumname << ".a ";
  objects(out, enumvalues);
  out << "\n";
}

// prints to out a Makefile rule for building an object
void object(std::ostream& out, std::string objectname, std::string enumname) {
  out << objectname << ".o: "
      << objectname << ".cpp "
      << objectname << ".h "
      << enumname << ".h "
      << enumname << "_visitor.h\n";
  out << "\tg++ -c " << objectname << ".cpp\n";
}

// creates a list of Makefile rules to compile objects
void objectrules(std::ostream& out, std::string enumname, std::vector<std::string> enumvalues) {
  for (std::vector<std::string>::iterator i = enumvalues.begin();
       i != enumvalues.end();
       ++i) {
    if (i != enumvalues.begin()) {
      out << "\n";
    }
    object(out, *i, enumname);
  }
}

void makefile(std::string enumname, std::vector<std::string> enumvalues) {
  std::ofstream makefile("Makefile");
  libraryrule(makefile, enumname, enumvalues);
  makefile << "\n";
  objectrules(makefile, enumname, enumvalues);
}

void a_cppfile(std::string objectname, std::string enumname) {
  std::string filename = objectname + ".cpp";
  std::ofstream out(filename.c_str());
  out << "#include \"" << objectname << ".h\"\n";
  out << "#include \"" << enumname << "_visitor.h\"\n";
  out << "\n";
  out << "void " << objectname << "::accept(" << enumname << "Visitor& v) {\n";
  out << "  v.visit" << objectname << "(*this);\n";
  out << "}\n";
}

void cppfiles(std::string enumname, std::vector<std::string> enumvalues) {
  for (std::vector<std::string>::iterator i = enumvalues.begin();
       i != enumvalues.end();
       ++i) {
    a_cppfile(*i, enumname);
  }
}

void a_hfile(std::string objectname, std::string enumname) {
  std::string filename = objectname + ".h";
  std::ofstream out(filename.c_str());
  out << "#ifndef " << objectname << "_H\n";
  out << "#define " << objectname << "_H\n";
  out << "\n";
  out << "#include \"" << enumname << ".h\"\n";
  out << "\n";
  out << "class " << objectname << " : public " << enumname << " {\n";
  out << " public:\n";
  out << "  void accept(" << enumname << "Visitor&);\n";
  out << "};\n";
  out << "\n";
  out << "#endif\n";
}

void hfiles(std::string enumname, std::vector<std::string> enumvalues) {
  for (std::vector<std::string>::iterator i = enumvalues.begin();
       i != enumvalues.end();
       ++i) {
    a_hfile(*i, enumname);
  }
}  

void interfacefile(std::string enumname) {
  std::string filename = enumname + ".h";
  std::ofstream out(filename.c_str());
  out << "#ifndef " << enumname << "_H\n";
  out << "#define " << enumname << "_H\n";
  out << "\n";
  out << "// forward declaration\n";
  out << "class " << enumname << "Visitor;\n";
  out << "\n";
  out << "class " << enumname << " {\n";
  out << " public:\n";
  out << " virtual void accept(" << enumname << "Visitor&) = 0;\n";
  out << "\n";
  out << "  // Destructor\n";
  out << "  virtual ~" << enumname << "() {}\n";
  out << " protected:\n";
  out << "  // Constructor\n";
  out << "  " << enumname << "() {}\n";
  out << "};\n";
  out << "\n";
  out << "#endif\n";
}

void visitorfile(std::string enumname, std::vector<std::string> enumvalues) {
  std::string filename = enumname + "_visitor.h";
  std::ofstream out(filename.c_str());
  out << "#ifndef " << enumname << "_VISITOR_H\n";
  out << "#define " << enumname << "_VISITOR_H\n";
  out << "\n";
  out << "// forward declarations\n";
  for (std::vector<std::string>::iterator i = enumvalues.begin();
       i != enumvalues.end();
       ++i) {
    out << "class " << *i << ";\n";
  }
  out << "\n";
  out << "class " << enumname << "Visitor {\n";
  out << " public:\n";
  for (std::vector<std::string>::iterator i = enumvalues.begin();
       i != enumvalues.end();
       ++i) {
    out << "  virtual void visit" << *i << "(" << *i << "&) = 0;\n";
  }
  out << "\n";
  out << " protected:\n";
  out << "  // Constructor\n";
  out << "  " << enumname << "Visitor() {};\n";
  out << "  // Destructor\n";
  out << "  ~" << enumname << "Visitor() {};\n";
  out << "};\n";
  out << "\n";
  out << "#endif\n";
}

std::vector<std::string> read_one_word_per_line(std::istream& in) {
  std::vector<std::string> accum;
  std::string line;
  while (std::getline(in, line)) {
    std::stringstream line_stream(line);
    std::string word;
    assert(line_stream >> word);
    accum.push_back(word);
  }
  return accum;
}

int main(int argc, char* argv[]) {
  if (argc != 2) {
    std::cout << "usage: " << argv[0] << " COLOR <colorlist.txt\n";
    return 1;
  }
  std::string enumname(argv[1]);
  std::vector<std::string> enumvalues = read_one_word_per_line(std::cin);

  makefile(enumname, enumvalues);
  cppfiles(enumname, enumvalues);
  hfiles(enumname, enumvalues);
  interfacefile(enumname);
  visitorfile(enumname, enumvalues);
  return 0;
}