Command lines and little languages

Everyone has to deal with the command line now and then. Not just on Linux! Some people use it all the time, or even prefer it, for various reasons. But even the most skilled of us often have to deal with the fact that some programs have a bewildering amount of options, flags and other arguments. Like ffmpeg (thanks, M.!) or my favorite example, gcc. Hint: manual pages don’t help when they’re literally the size of a novel. And for what? I mean, look at this:

example --foo off --bar 35 --baz "whatever, man"

It’s ridiculous! Internally you’re setting some variables anyway. Might as well admit it:

example --set foo off --set bar 35 --set baz "whatever, man"

You’re going to argue that’s more to type. Sure, because we’re stuck with GNU-style verbosity. But it means only one option to mention in the manual and handle in the code, instead of three.

Now imagine you could do this through a uniform language designed on purpose instead of ad-hoc options:

walk set distance sqr add mul 3 3 mul 4 4

(That’s prefix notation, by the way, also known as Polish notation. It’s a good fit due to having the same structure as conventional command lines in general.)

To do this, first I needed a unique command-line parser. That done, the next step was to write a little extension:

local builtins = {}

function parse_expression(source)
    if source:match_number() then
        return source.value
    elseif source:match_boolean() then
        return source.value
    elseif source:match_string() then
        if builtins[source.token] then
            local fn = builtins[source.token]
            return fn(source)
        else
            return source.token
        end
    else
        error "Reading past the end of input"
    end
end

There we go. Literally a sixteen-line function, and one table. Now to populate it:

function builtins.add(source)
    local t1 = parse_expression(source)
    local t2 = parse_expression(source)
    return t1 + t2
end

function builtins.mul(source)
    local f1 = parse_expression(source)
    local f2 = parse_expression(source)
    return f1 * f2
end

function builtins.sqr(source)
    local n = parse_expression(source)
    return math.sqrt(n)
end

In the above code, source is an instance of ArgMatch from the previous article. Simply pass one to parse_expression and you can already handle arbitrary math. Once you add more built-ins, anyway; set is left as an exercise to the reader.

After a week of on-and-off coding for fun, my new toy has grown into a proper scripting language, complete with control structures and user-defined functions. The hard part is not overdoing it.

So, why not simply use bc or a similar tool?

For one thing, because it’s messy. I mean, look at the equivalent invocation:

walk --distance $(echo 'sqrt(3 * 3 + 4 * 4)' | bc)

And then, you might be on Windows where it’s not available.

Why not simply embed Lua then?

Because it’s an additional dependency, and frankly might take more code.