Disclaimer to protect my CV: this is not something I do professionally!
I often need to generate pitch or sample lookup tables for my AVR projects. I usually generate these C++ headers with little ruby scripts. An evil thought struck me during a long drive… I don’t like these separate little scripts, I want the script inside the header, so if you #include it you get the data, but if you run it with ruby it rebuilds itself.
The thought preoccupied me for the journey. Preprocessor macros start with a hash, and ruby uses hashes as comments, so you can hide ruby code in a #if 0 block.
The problem was then how to stop ruby from parsing the C++ the file contains. That’s simple enough, you can hide =begin and =end in #if blocks.
The file reading and writing itself is handled using gsub() and some careful regexing. It searches for the tailing end of the table declaration and the hash of the #if that trails it, and the replaces that block with generated values.
Just to be a swine I’ve condensed the Ruby to be borderline unreadable. Save this as ‘notes.h’ and run it through ruby:
#if 0
# Run 'ruby notes.h' and this rebuilds itself.
i=IO.read($0);IO.write($0,i.gsub(/(\]\s=\s{).+?#/m,"\\1 \n #{1024.times.collect{|n|(((44100.0*64.0)/(2.0**(n.to_f/(12*16))*110.0))).to_i}.join(",\n ")}\n};\n#"))
=begin
#endif
uint16_t freq[1024] = {
25658,
25565,
// ... the rest here will be regenerated
641,
638
};
#if 0
=end
#endif
Note the regex can’t just search for the ‘] = {‘ verbatim because it would pick up the first instance of that expression in the gsub itself; this code is reading its own text and it would replace the regex! I bypass this by swapping spaces for \s ‘]\s=\s‘.
Here the evil ends. #include "notes.h" works fine and you can rebuild any time – and risk trashing everything if you have made the tiniest typo :) Not a good way to approach development but a fun puzzle. The next challenge is to do this with compile-time table generation using variadic templates.