Table of Contents
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.
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-wrap
er. 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.
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.
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>
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
.