Comparing four scripting languages

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
Let’s see the programs:
Here are my comments, in random order:
  • 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)
Feel free to leave thoughts and better ways to write this program in the comments, in these four languages or others. I’m certainly no huge expert in any of these languages so there may be big improvements you can make in terms of readability.
Advertisements

11 thoughts on “Comparing four scripting languages

  1. Using C#, safe and unsafe versions, not as compact and flexible as scripting but fine enough:

    (Snipped to save space, see updated code below)

    • Thanks. These don’t run out of the box though (with shebang) which is a requirement for scripts such as this as otherwise I would have to compile and check in binaries.

      • Oh, if shebang is a requirement, I guess that with Mono, you can use #!/usr/bin/env csharp to run this kind of script , like this (not tested). But I agree that c# scripting is not yet really practical as-is, as class declaration are not really possible and function declaration workaround is ugly…

        #!/usr/bin/env csharp
        using System.IO;
        var args = Environment.GetCommandLineArgs();
        if (args.Length != 3)
        {
        Console.WriteLine(“two arguments needed”);
        Environment.Exit(1);
        }

        var ComputeChecksum = new Func<byte[], int>(
        buffer =>
        {
        long sum = 0;
        var reader = new BinaryReader(new MemoryStream(buffer));
        for (int i = 0; i < 256; i++)
        {
        sum += reader.ReadInt32();
        if (sum > 0xFFFFFFFF)
        sum = (sum + 1) & 0xFFFFFFFF;
        }
        return (int)((~sum) & 0xFFFFFFFF);
        });
        var data = new byte[1024];
        var input = new FileStream(args[1],FileMode.Open,FileAccess.Read);
        input.Read(data, 0, 1024);
        var output = new BinaryWriter(new FileStream(args[2], FileMode.Create));
        output.Write(data, 0, 4);
        output.Write(ComputeChecksum(data));
        output.Write(data, 8, 1016);

  2. About the Python code…

    f = open(fn, ‘rb’)
    data = f.read()
    f.close()

    Why not simply do

    data = open(fn, ‘rb’).read()

    ?

  3. About the Python code…

    f = open(fn, ‘rb’)
    data = f.read()
    f.close()

    Why not simply do “data = open(fn, ‘rb’).read()”?

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s