Chapter 26. Command-line Options

Table of Contents

26.1. Parsing Command-line Options
26.2. Adding Custom Options
26.3. Setting Description and About
26.4. I/O-less Programs

As Part II explained, the world of Stratego is one of small programs tied together using Unix pipes. The pipes carry the data, while configuration and control is passed between programs in the form of command line arguments. Incidentally, this is the same mechanism used by humans to invoke programs, and this eases understanding and debugging of XT compositions tremendously. Details about debugging is covered in Chapter 30. In this chapter, we will cover the mechanism available in Stratego for working with command line arguments.

26.1. Parsing Command-line Options

When a compiled Stratego program is first started, the initial value of the current term is a list containing the command line arguments to the program. Fhe following program, foo, is a genuine "do nothing" program.

module foo
imports libstratego-lib
strategies
  main = id

In the course of this chapter, we will extend this program with new options and even a help screen for the user to enjoy. But first, let us compile and run foo with some arguments, to get an idea of where we stand.

$ strc -i foo.str -la stratego-lib
...
$ ./foo --help
["./foo","--help"]
$ ./foo -i foo.str --extra --option -s
["./foo","-i","foo.str","--extra","--option","-s"]

From this interaction, we see that a list of the arguments provided on the command line becomes the initial term of the program. Each command line argument becomes its own string element in the list, and the first element of the list is the command used to invoke the foo itself. Clearly, this list must be interpreted somehow, for the arguments to have any meaning. The library contains a collection of robust strategies that deal with just this. The option handling strategies will parse the argument list, let you set default values for options and transparently deal with long and short forms of the same option. Even more interesting, the library provides so-called wrap strategies that abstract away all of the dreary details of this option handling, and also provide a default set of options with some basic functionality.

Perhaps the most frequently used wrap strategy is io-wrap (or its XTC equivalent, xtc-io-wrap, which is not covered here). In fact, io-wrap is a family of strategy, which includes io-wrap(s), io-wrap(opts, s) and io-wrap(opts, usage, about, s). All of these variants provide the same basic functionality. The increasing number parameters allows you to override default bevhavior, when you need to. When using one of these strategies, a standard package of command line handling will be provided to your users through your program. Let us start with the simplest case, io-wrap(s) and work our way from there. Consider a revised edition of foo, from above:

module foo
imports libstratego-lib
strategies
  main = io-wrap(id)

Here, we wrap the core logic of our program (just id in our case) inside the io-wraper. If we run foo with the --help this time around, we will get a more instructive reply than previously:

$ ./foo --help
Options:
   -i f|--input f   Read input from f
   -o f|--output f  Write output to f
   -b               Write binary output
   -S|--silent      Silent execution (same as --verbose 0)
   --verbose i      Verbosity level i (default 1)
                    ( i as a number or as a verbosity descriptor:
                      emergency, alert, critical, error,
                      warning, notice, info, debug, vomit )
   -k i | --keep i  Keep intermediates (default 0)
   --statistics i  Print statistics (default 0 = none)
   -h|-?|--help     Display usage information
   --about          Display information about this program
   --version        Same as --about

Description:

All of a sudden, our program is a well-behaved citizen in the command line world. It answers to --help, and appears to have a few other tricks up its sleeve to. For example, the input term to foo can now be specified using the -i option, and output can be stored to a file using the -o option.

So does this actually work? All for free? Let's test with putting the following term into the file term.trm:

Yes(It(Works))

Bursting with excitement, we try:

$ ./foo -i term.trm
Yes(It(Works))

And if that's not enough, there is even a bit of extra convenience provided by io-wrap: If -i is not provided, stdin is read. Similarly, stdout is written to if -o is not specified.

So, using io-wrap is all you have to do for your program to gain a minimal, but functional set of command line options. As a bonus, these options also make your program compatible with XTC; it can be composed with other XTC components.

26.2. Adding Custom Options

It is often necessary for programs to expose switches to turn functionality on and off, or to read extra configuration knowledge from external files. All these cases require additional command line options, so we need a mechanism for extending the basic io-wrap(s) strategy. The preferred way of adding new options is to use the io-wrap(opts,s) strategy, providing it with a strategy encoding the options.

When adding a new option, we must decide whether this option will require a argument of its own, or not. The term ArgOption is used to construct options that take arguments and Option is the term used for on/off switches. Suppose we want to expose an option --verify that enables the user to run our transformation in a self-verifying mode. This is clearly an on/off switch, and therefore a job for Option.

Adding this option to our program foo gives us the following code:

module foo
imports libstratego-lib
signature
  constructors
    Verify : Option

strategies

  main =  io-wrap(
    Option(
        "--verify"
      , <set-config> (Verify(), "on")
      , !"--verify         Turn on verification")
    , do-foo)

  do-foo = ...

Note that we made a new term type, Verify, to serve as our switch symbol. Inside the real logic of program, given by do-foo, we would write <get-config> Verify to get the state of the Verify switch. In the case where the user had specified --verify on the command line, get-config would result in the term "on", taken from the declaration of our verify Option. If the did not add --verify to his command line arguments, <get-config> Verify will fail.

Options with arguments is provided through ArgOption. The use is pretty much identical to that of Option. Assume our transformation needs a bit of help from configurable processing tables (whatever that might be), and that we want these tables configured at runtime, using the -p option. We want to add another alternative to the opts argument of io-wrap:

  main = io-wrap(
     Option(....)
     + 
     ArgOption(
        "-p"
       , where(<extend-config> ("-p", [<id>]))
       , !"-p file   Use processing table in file")
     , id)

Note how we compose options with the choice operator (+). With this addition, our users can now specify their elusive processing tables with the -p. The arity of this option is checked automatically. That is, if the user adds -p to his argument list without specifying a file afterwards, this will be signaled as an error, and the usage screen will be printed. Once the user has read this and corrected the error of his ways, the value (the filename) provided via -p can be obtained using the get-option strategy. If no -p was specified, get-option will fail. Sometimes, this failure may be inappropriate, and a default value is desired instead. If you browse through Stratego code, you may come across the following idiom for dealing with this situation:

  get-config-p =
    <get-config> "-p" <+ ![]

This is all you need to know about basic command-line option processing. When in doubt, you are advised to refer to the detailed API documentation is available in the API reference.

26.3. Setting Description and About

If your program is primarily intended for human use, you are encouraged to complete your program's option configuration with a short description of what your tool does.

We can easily add a short description and also an about section. The description is shown as part of the help screen (displayed with --help), whereas the about section is displayed when the arguments to foo contain --about. It is customary for the about screen to contain copyright notices and credits.

  
  main = io-wrap(
        ...
      , foo-tool-usage
      , foo-tool-about
      , id)

  foo-tool-usage =
    default-system-usage(
      !["Usage: foo -p proctbl [options]"]
    , ![ "This program verifies the input against a processing table.\n"]
    )

  foo-tool-about =
     <echo> "Written by Alan Turing <alan@turing.org>"

After compiling this version of foo, invoking it with --help give the following help screen:

$ ./foo --help
Usage: foo -p proctbl [options]

Options:
   --verify         Turn on verification
   -p file          Use processing table in file
   -i f|--input f   Read input from f
   -o f|--output f  Write output to f
   -b               Write binary output
   -S|--silent      Silent execution (same as --verbose 0)
   --verbose i      Verbosity level i (default 1)
                    ( i as a number or as a verbosity descriptor:
                      emergency, alert, critical, error,
                      warning, notice, info, debug, vomit )
   -k i | --keep i  Keep intermediates (default 0)
   --statistics i  Print statistics (default 0 = none)
   -h|-?|--help     Display usage information
   --about          Display information about this program
   --version        Same as --about

Description:
This program verifies the input against a processing table.

If we invoke our splendid program foo with the --about option, we can now observe:

$ ./foo --about
Written by Alan Turing <alan@turing.org>

26.4. I/O-less Programs

Not all programs written in Stratego intended for processing ATerms. For these programs, the full I/O functionality provided by io-wrap may often turn out to be inappropriate. An alternative way of dealing with parameters is also provided by the library, centered around the option-wrap family of strategies. option-wrap works analogously to io-wrap, but does not provide the -i and -o options, nor does it read anything from stdin automatically.

The default set of options provided by option-wrap is shown below:

    
   --verify         Turn on verification
   -h|-?|--help     Display usage information
   --about          Display information about this program
   --version        Same as --about

Adding new options is identical to what we already explained for io-wrap.