I tweeted I had written a build script in four different languages and wasn’t sure I liked the results. (Update: By “Like the results”, I mean that there’s still a nagging feeling that it could have been done more elegantly. For programs this simple, the main focus I have is readability and maintainability.) Since a few people asked, I might as well write a post about it.
The problem I’m solving is relatively simple: signing an Amiga floppy bootblock. Amiga bootblocks have a checksum which is stored in bytes 4-7. After my build system cranks out a bootblock I have to compute the checksum and update it or the Amiga won’t touch it. Here’s what the script must do:
- Complain if not passed input and output filenames
- Read (binary, needless to say) input from the bootblock, pad to 1024 bytes
- Compute the checksum. In English:
- 1. Sum all 32-bit big-endian words in the 1kb (Endianness is important if you want to make your own version! :))
- 2. Whenever there is a 32-bit overflow, add 1 to the sum
- 3. The result is the bitwise complement of the sum
- Store checksum in bytes 4-7
- Write the bootblock to the output filename
- Scheme: Binary I/O is terrible. You can’t portably use with-input-from-file for binary data so you have to resort to manual port juggling (Racket offers an extension for this).
- Scheme: Byte vector manipulation is built-in, which is nice. But quickly gets verbose.
- Perl: To avoid copying the whole 1k bootblock when calling a function, you use (\$) as a function prototype to pass by reference.
- Perl: IO is straightforward, but somewhat CLUNKY to look at. Somewhat scary with the “use bytes” directives. Perl gurus: is this required to get correct length() on binary strings read from :raw files?
- Ruby: Shortest update insertion code with slice assignment of strings.
- Ruby: The (0..256).each syntax doesn’t sit right with me. Why does the Fixnum class support range iteration? This is OO taken much to far IMO. Ruby gurys: any better way to do this?
- Ruby: The IO is very concise thanks to helper functions.
- Ruby, Perl and Python: String repetition operators make the 1024-padding easy.
- Scheme: The R6RS import stuff is fantastically verbose for a program this size.
- Python, Perl: Awkward to open,read,close in three lines. A stock utility to read a whole file would have saved space and made the program more readable. (Update: I now know about Python’s “with” statement, much better in this regard! Thanks to @zebrabox)