Introduce Code Generator

This is a simple step that you use to enable meta-level refactoring as a build-time process. I do a lot of work using C, in a Unix environment, so my examples use this environment. Feel free to add instructions for other IDEs.

Scenario 1: you have a file (foo.c) with a repeating structure (could be a case statement; a lot of similar functions, whatever)

Add this to your Makefile:

  foo.c: foo.pl
    perl -w foo.pl > $@.new
    cp $@.new $@.gold
    mv $@.new $@
Yes, the "cp" line seems a bit strange, but we'll change it later.

Now create the code generator:

  % mv foo.c foo.pl
  % vi foo.pl # or use your favorite editor
To the top of the file, add

  print <<'HERE';
to the bottom of the file, add

  HERE
You now have a working code generator! But you'll want to refactor it (after all, the print doesn't simplify anything). For refactoring, you need a set of tests for the generator. Fortunately, these are easy to create:

  % make foo.c
When you run make, the makefile will create foo.c.gold. You will use this as your reference during refactoring of the code generator. Refactoring does not change function, so the output of the code generator must remain identical to the .gold file (during refactoring).

  % vi Makefile
Finally, you edit the Makefile to change the "cp" command (which created the .gold file) into a diff command. So you now have

  foo.c: foo.pl
    perl -w foo.pl > $@.new
    diff $@.new $@.gold
    mv $@.new $@
This is an ideal test for refactoring the code generator. When you decide you've done enough meta-refactoring, and that you actually want to change the output of the generator, then you should change the "diff" back into a "cp". You can toggle this as often as you want!

It is likely that, during refactoring, I'll make minor changes to the .gold file to make things more consistent (e.g. whitespace differences). So there will be times when you want to change the function of the code generator. So you still need tests for the generated code.

Scenario 2: You have a lot of files (foo1.c, foo2.c, foo3.c) that are similar. The differences could be that they encapsulate a generic ADT with a different types (C has neither templates nor virtual types, though you could have used macros instead).

For this, you proceed similarly to the previous example, but there are some important differences:

The Makefile:

  foo1.c foo2.c: foo.pl
    perl -w foo.pl $@ > $@.new
    cp $@.new $@.gold
    mv $@.new $@
The important difference is that I pass the target filename to the generator:

  my $target = shift;
  $target =~ /foo(.*).c/ or die "unexpected target filename";
  my $variant = $1;

if ($variant eq "1") { print <<HERE; <contents of original foo1.c> HERE } elsif ($variant eq "2") { print <<HERE; <contents of original foo2.c> HERE } else { die "target file, $target, not yet unified in code generator\n" }
Note that I've only unified 2 of my 3 files. This is deliberate. After running make to generate my gold files, I'll change it into "diff" mode and work at unifying (refactoring) the 2 print statements into something that is well-factored. Only then will I attempt to merge foo3.c into the same structure.

Scenario 3: You have a number of files that cover the same topic, but in different languages/formats.

The introduction step is identical to scenario 2.

-- DaveWhipp


EditText of this page (last edited October 10, 2013) or FindPage with title or text search