Arg Parsing – The Chris Sells Challenge

A couple of days ago Chris Sells made this post about the way best to parse command line augments. Given an xml document describing the structure of a command line augment, he wants to know would it be best to use code gen or an interpreter to parse command line arguments. Here is the xml structure he was talking about.

 

<args description="Two wrongs don't make a right, but three lefts do">
  <arg name="lefts" description="Number of left turns" type="int" default=4" />
  <arg name="attitude" description="Driver attitude" required="true" type="string" />
</args>

 

F# (and the ML languages in general) offers a third way. Don’t build you command line augments out of an xml structure build a little language inside you language to describe what should be done. The idea is you have a list describing arguments, the list is made up of tuples 3 paraments and each tuple contains the name of the argument, a function describing what to do with the argument, and a help description of the agument.

 

So in stead of Chris' xml structure you have a list that looked something like:

 

let usage =

  [ "-lefts", Arg.Int(fun leftTurnsArg -> leftTurns := leftTurnsArg), "Number of left turns";

  "-attitude", Arg.String(fun attitudeArg -> attitude := attitudeArg), "Driver attitude (manditory)";]

 

The Arg.Int or Arg.String bit just tells the interpreter what to do with the function, in this case an Arg.Int tells the interpreter to pass the next argument as an integer and the Arg.String tells it to pass the next argument as a string. There are a few others, but I won’t go into them here. The complete program in F# looks something like:

 

let leftTurns = ref 4

let attitude = ref ""

 

let search pattern = raise (Arg.Bad "unexpected arguments")

let usage =

  [ "-lefts", Arg.Int(fun leftTurnsArg -> leftTurns := leftTurnsArg), "Number of left turns";

  "-attitude", Arg.String(fun attitudeArg -> attitude := attitudeArg), "Driver attitude (manditory)";]

 

let _ =

    Arg.parse usage search "TestArgParser <options>";

         if !attitude = "" then

                   Arg.usage usage "TestArgParser <options>\n\tMust specifiy attitude\n"

         else

                   begin

                            print_string "Left turns: ";

                            print_int !leftTurns;

                            print_newline();

                            print_string ("attitude: " ^ !attitude)

                   end

 

The only slightly annoying thing about doing it this way is there no good way of checking for mandatory field so this must be done afterwards. I think its interesting to note the longest part of the programing is actually the scetion to print out the retrived arguments! The result is pretty nice though as the “Arg.parse usage” line does a lot work for us, for example if we miss type one argument a help display is shown automatically and the program exits for us. For example the command line:

 

TestArgParser.exe -attitude good -lefts eight

 

Results in:

TestArgParser <options>

        -lefts <int>: Number of left turns

        -attitude <string>: Driver attitude (manditory)

        --help: display this list of options

        -help: display this list of options

 

Where as:

TestArgParser.exe -attitude good -lefts 8

 

Gives us:

Left turns: 8

attitude: good

 

What if you don’t want to build your app in F#? That’s not a problem. You could just implement an argument parsing dll in F# and place the values you require in fields to be read later by you’re application, which could be in any .Net language.

 

It is also possible to use the arg parse from C#. You have to define you list of arguments as an array and then convert it to an F# list type. Creating the function takes a little more work a you must use F# FunsFuncConvert class to create F# closures. In the end the definition of the list it self looks like:

 

Tuple<string, Arg.spec, string>[] usages

         = { new Tuple<string, Arg.spec, string>(

                            "-lefts",

                            new Arg.spec._Int(

                            FuncConvert.ToFastFunc<int, object>(

                                      delegate(int leftsArg) {_lefts = leftsArg; return new object();} )

                            ),

                            "Number of left turns"),

         new Tuple<string, Arg.spec, string>(

                            "-attitude",

                            new Arg.spec._String(

                            FuncConvert.ToFastFunc<string, object>(

                                      delegate(string attitudeArg) {_attitude = attitudeArg; return new object();} )

                            ),

                            "Driver attitude")};

 

To me all the extra meta-data you need in C# to describe your usage of types makes this look pretty ugly, but the results program functions exactly the same. Even if it is ugly it is certainly easier than had crafting a parser. The full listing for both programs are available here.

Bookmark
dotnetkicks+, digg+, reddit+, del.icio.us+, dzone+, facebook+

Print | posted @ Saturday, August 13, 2005 4:12 PM

Comments on this entry:

No comments posted yet.

Your comment:

(Note: all comments are moderated so it may take sometime to appear)

Title:
Name:
Email:
Website:
 
Italic Underline Blockquote Hyperlink
 
 
Please add 8 and 5 and type the answer here: