Saturday, November 3, 2012

Command Lookup Tables in Various Languages

Sometimes it's fun to see how an idea translates when coded in several languages. In this article, the central idea is having a table that maps a command name to a function. A runloop prompts the user for a command, looks up the command in the table, and, if it exists, executes the associated function. The python cmd module is a take on this idea. Here we code up something much more rudimentary in python, ruby, and lua.

cmd.py


!/usr/bin/env python

import sys 

def connect(host):
    print('connecting to %s...' % (host))

def download(path):
    print('downloading %s...' % (path))

def upload(path):
    print('uploading %s...' % (path))

def disconnect():
    print ('disconnecting...')
    sys.exit(0)

commands = { 
    'connect': connect,
    'download': download,
    'upload': upload,
    'disconnect' : disconnect
}

while True:
    line = raw_input('% ')
    tokens = line.split()
    if not tokens: continue
    cmd = tokens[0]
    args = tokens[1:]
    if cmd in commands:
        try: commands[cmd](*args)
        except TypeError as e:
            print(e)
    else:
        print("cmd '%s' not found" % (cmd))

cmd.rb


!/usr/bin/env ruby

def connect(host) 
    puts "connecting to #{host}..."
end

def download(path) 
    puts "downloading #{path}..."
end

def upload(path) 
    puts "uploading #{path}..."
end

def disconnect() 
    puts "disconnecting..."
    exit
end

commands = { 
    'connect' => :connect,
    'download' => :download,
    'upload' => :upload,
    'disconnect' => :disconnect,
}

while true do
    print '% '
    tokens = gets.split
    cmd = tokens[0]
    args = tokens[1..-1]
    if cmd and commands[cmd] then
        begin
            send(commands[cmd], *args)
        rescue ArgumentError => e
            puts e.message
        end 
    else
        puts "cmd '#{cmd}' not found"
    end 
end

cmd.lua


#!/usr/bin/env lua

---------------------------------------
-- utility functions
---------------------------------------
local function split(s) 
    local tokens = {}
    for t in string.gmatch(s, "%S+") do
        --print('token: ' .. t)
        tokens[#tokens + 1] = t 
    end 
    return tokens
end

local function printf(fmt, ...)
    if fmt then
        return io.write(string.format(fmt, ...))
    else
        return print()
    end 
end

local function perror(msg)
    -- strip file and line number where error occurs
    local i, j = string.find(msg, ':%d+:%s+')
    if j then
        print(string.sub(msg, j+1, -1))
    else
        print(msg)
    end 
end

---------------------------------------
-- commands
---------------------------------------
local function connect(host)
    if not host then
        error('must specify host')
    end 
    printf('connecting to %s...\n', host)
end

local function download(path)
    if not path then
        error('must specify path')
    end 
    printf('downloading %s...\n', path)
end

local function upload(path)
    if not path then
        error('must specify path')
    end
    printf('uploading %s..\n', path)
end

local function disconnect()
    printf('disconnecting...\n')
    os.exit(0)
end

---------------------------------------
-- command table
---------------------------------------
local commands = {
    connect = connect,
    upload = upload,
    download = download,
    disconnect = disconnect
}

---------------------------------------
-- runloop
---------------------------------------
while true do
    io.write('% ')
    io.flush()
    local line = io.read('*line')
    local tokens = split(line)
    if #tokens > 0 then
        local cmd = tokens[1]
        table.remove(tokens, 1)
        local args = tokens
        if commands[cmd] then
            local status, err = pcall(commands[cmd], unpack(args))
            if not status then
                perror(err)
            end
        else
            printf("cmd '%s' not found\n", cmd)
        end
    end
end