• entries
    359
  • comments
    237
  • views
    188585

No news means Old news.

Sign in to follow this  
Telastyn

104 views

Thankfully, this little journal was started late, meaning any nice code tidbits I do have are available for discussion when I have been overly lazy/busy. Like this week.

One particular piece of code that I've found to be far more valuable than I anticipated is the Command Set (cmdset). I originally wrote the Command Set class in C++ as an interpreter for a Quake-style console. The general idea was that you could add functions to the Set, and the Set would handle any parsing/type conversions to take a string and execute the function.

Since then it has undergone some modifications. Most notably, the most recent incarnation is in C#, and that cmdset is now an interface, not a solid class. I use it as a means to make strings follow the command pattern. Different concrete derivations include a method which also contains Help information, a 'fallthrough' class which matches any command and passes the string along, and a group of sets to allow additive set building from reference sets.

It's not as beefy as a full fledged interpreter or scripting solution, and (imo) tends to be a bit more generally useful because of it.


On to code. The first and foremost problem for this sort of solution is the conversion of string commandline parameters to non-string function parameters. In C++ (included for interest), I created a static template class to contain type serialization information:


#ifndef TYPE_REGISTRY
#define TYPE_REGISTRY

//#include "stringhash.h"
//#include
#include
#include
#include

using namespace std;
using namespace boost;

const char unknown_type_name[]="unknown";

template
struct def{
typedef function serialization_type;
typedef functionconst char *)> unserialization_type;
// TODO:
// clones, deletors
//

static string name;
static serialization_type serial_funk;
static unserialization_type unserial_funk;


string serialize(T *t){ if(serial_funk.empty()){return("");} return(serial_funk(t));}
T *unserialize(string s){ return(unserialize(s.c_str()));}
T *unserialize(const char *s){ if(unserial_funk.empty()){return(0);} return(unserial_funk(s));}
};


template
struct null_serialization{
string operator()(T *t){return("");}
};

template
struct null_unserialization{
T *operator()(const char *s){return(0);}
};

template
struct ios_serialization{
string operator()(T *t){
if(!t){return("");}
stringstream ss;
ss << *t;
return(ss.str());
}
};

template
struct ios_unserialization{
T *operator()(const char *s){
stringstream ss(s);
T *t=new T();
ss >> *t;
return(t);
}
};

template
struct intrusive_serialization{
string operator()(T *t){
return(t->serialize());
}
};

template
struct intrusive_unserialization{
T *operator()(const char *s){
return(T::unserialize(s));
}
};

template
struct constructor_unserialization{
T *operator()(const char *s){
return(new T(s));
}
};

template
struct string_serialization{
string operator()(T *t){
return(*t);
}
};



//
// POD types.
//

def<int>::serialization_type def<int>::serial_funk=ios_serialization<int>();
def<int>::unserialization_type def<int>::unserial_funk=ios_unserialization<int>();
string def<int>::name="int";

def<long>::serialization_type def<long>::serial_funk=ios_serialization<long>();
def<long>::unserialization_type def<long>::unserial_funk=ios_unserialization<long>();
string def<long>::name="long";

def<double>::serialization_type def<double>::serial_funk=ios_serialization<double>();
def<double>::unserialization_type def<double>::unserial_funk=ios_unserialization<double>();
string def<double>::name="double";

def<float>::serialization_type def<float>::serial_funk=ios_serialization<float>();
def<float>::unserialization_type def<float>::unserial_funk=ios_unserialization<float>();
string def<float>::name="float";

def::serialization_type def::serial_funk=string_serialization();
def::unserialization_type def::unserial_funk=constructor_unserialization();
string def::name="string";

def<bool>::serialization_type def<bool>::serial_funk=ios_serialization<bool>();
def<bool>::unserialization_type def<bool>::unserial_funk=ios_unserialization<bool>();
string def<bool>::name="bool";


#endif



The correct template could then be picked based on the ARG parameters in boost::function. The C# version naively follows the same practice even though C# has nicer serialization methods... Ignorance is inversly proportional to solution goodness. Alas.



public static class typdict {
public delegate object destr(string s);
private static Dictionary typemap = new Dictionary();
public static void Add(Type t, destr f) {
typemap.Add(t, f);
}
public static object Parse(Type t, string s) {
if (!typemap.ContainsKey(t)) {
return (null);
}
return (typemap[t](s));
}

public static void Initialize() {
Add(typeof(int), delegate(string s) { return ((object)int.Parse(s)); });
Add(typeof(float), delegate(string s) { return ((object)float.Parse(s)); });
Add(typeof(string), System.Convert.ToString);
}
}



The second problem is command definition. In C++ this was rather easy. boost::function objects contain their type information. Tie a string to a function, pass the args as template parameters, and there you go.

C# it's a little trickier. Generics won't do the specification nicely (void is not a valid type for genericness it seems...). So for C# I required an explicit Delegate to be passed rather than a method. Reflection then allows type deduction. Here is the most common Command Definition. (note the base class contains the command name string, as well as the tokenize() function. The tokenize function is essentially a big Regex.Split() with an ugly regex ("split on a full line on whitespace, unless that white space is between double-quotes"). In C++, boost::spirit was used, and was much cleaner).)

[and pardon the variable naming... it was my second week or so with C#]


public class cmd_del : cmddef {
protected Delegate cmd;
protected Type[] argts;
protected Type rtnt;

cmd_del(string cn, Delegate c, Type rt, params Type[] ats)
: base(cn) {
cmd = c;
rtnt = rt;
argts = ats;
}
protected override Type rtntype() { return (rtnt); }
protected override Type[] argtypes() { return (argts); }
public override object Parse(string cmdargs) {
ArrayList argz = new ArrayList();
string[] sargs = tokenize(cmdargs);
if (sargs.Length != argts.Length) {
return (null);
}
if (argts.Length == 0) {
return (cmd.DynamicInvoke());
}
for (int x = 0; x < sargs.Length; ++x) {
argz.Add(typdict.Parse(argts[x], sargs[x]));
}
object[] oargs = argz.ToArray();
return (cmd.DynamicInvoke(oargs));
}
public cmd_del(string cn, Delegate d)
: base(cn) {
cmd = d;
rtnt = d.Method.ReturnType;
List alargs = new List();
foreach (ParameterInfo p in d.Method.GetParameters()) {
alargs.Add(p.ParameterType);
}
argts = alargs.ToArray();
}
}




Once there, it's fairly simple to create the basic cmdset class:



public abstract class cmd_interface {
public abstract object Parse(string cmdline);
public abstract string[] cmd_names();
public abstract string[] cmd_usages();
public abstract bool Has(string cmd);
public abstract int Count { get;}
}
public class cmdset :cmd_interface {
protected Hashtable cmdlist = new Hashtable();
public logging_strategy logstrat = new logging_strategy();
// TODO: logging, events, usage lists, cmdlists.
public override object Parse(string cmdline) {
//
// Parse quake-style console command.
//
// Returns null on cmdlack.
//
string cmd;
string argz;
Regex cmdrx = new Regex(@"^(\w+)(?:[\s$]*)(.*)$");
Match rxmc = cmdrx.Match(cmdline);
if (!rxmc.Success) {
logstrat.log("cmdset: failed to parse: \"" + cmdline + "\"\n");
return (null);
}
cmd = rxmc.Groups[1].Value;
if (!cmdlist.ContainsKey(cmd)) {
logstrat.log("cmdset: Unknown command \"" + cmd + "\"\n");
return (null);
}
if (rxmc.Groups.Count == 1) {
argz = "";
} else {
argz = rxmc.Groups[2].Value;
}
return ((cmdlist[cmd] as cmddef).Parse(argz));
}
public virtual void Add(cmddef cmdf) {
cmdlist.Add(cmdf.cmdname, cmdf);
}
public override bool Has(string cmdname){
return(cmdlist.ContainsKey(cmdname));
}
public override string[] cmd_usages() {
List rtn = new List(cmdlist.Count);
foreach (DictionaryEntry de in cmdlist) {
rtn.Add((de.Value as cmddef).usage());
}
return (rtn.ToArray());
}
public override string[] cmd_names() {
List rtn = new List(cmdlist.Count);
foreach (DictionaryEntry de in cmdlist) {
rtn.Add((de.Value as cmddef).cmdname);
}
return (rtn.ToArray());
}
public override int Count {
get { return (cmdlist.Count); }
}
}



Thus you have a relatively simple class to provide plugable 'commands' for string input handling. While this sort of thing was originally meant for quake-style console input, and server console input one of the more interesting places I've found for it is in threading.

Combined with some code to facilitate inter-thread communication, the cmdset provides a common abstraction between thread communication and whatever methods the thread impliments.

From my threaded sound class:

cs.Add(new cmd_del("play",new SoundEventType(this.PlaySound)));
cs.Add(new cmd_del("stop",new SoundEventType(this.StopSound)));
cs.Add(new cmd_del("pause",new SoundEventType(this.PauseSound)));


With the common communication, thread management and command set classes, all that needs done to thread-ify a class is to add the command definitions to an initialization method.

And there you go. A little tidbit of stuff to promote thought discussion and threats against my wellbeing.
Sign in to follow this  


0 Comments


Recommended Comments

There are no comments to display.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now