new object $has_commands: $root;

var $root inited = 1;
var $has_commands shortcuts = #[];
var $has_commands commands = #[];

public method .init_has_commands() {
  var i;

  shortcuts = [];
  commands = [];
  for i in (this().parents()) {
    (| shortcuts += i.shortcuts().to_list() |);
    (| commands += i.commands().to_list() |);
  }
  shortcuts = shortcuts.to_dict();
  commands = commands.to_dict();
};

public method .shortcuts() {
  return shortcuts || #[];
};

public method .commands() {
  return commands || #[];
};

public method .match_command() {
  arg line;
  var word;

  // This method will return a list of length three.
  // 1. command as matched (may be a shortcut character)
  // 2. associated symbol (which is probably a method name)
  // 3. the rest of the line, (minus command)
  
  // First try to match the shortcuts (quick)
  catch ~keynf {
    return [.shortcuts()[line[1]], line[1], substr(line, 2)];
  }

  // Now try to match an actual command
  catch ~keynf {
    word = (| substr(line, 1, stridx(line, " ", 1)-1) |) || line;
    return [.commands()[word], word, (| substr(line, word.length() + 2) |) || ""];
  }

  // No match
  return 0;
};

public method .parse_line() {
  arg line;
  var cmd, ret;

  if ((cmd = .match_command(line))) {
    catch any {
      ret = .(cmd[1])(line, cmd[2], cmd[3]);
    } with {
      switch (error()) {
	case ~numargs:
	  return "Wrong number of arguments.";
	default:
	  rethrow(error());
      }
    }
    return ret;
  } else {
    return "No such command.";
  }
};