Table of Contents
In Chapter 3, we explained how the Stratego/XT universe is built from small components, each a separately executable program. Many such components are provided for your reuse by the Stratego/XT distribution. We have already seen some of these in action throughout Part II. In this chapter, we will first explain how to compose existing components together, and then proceed to explain how you can make your own Stratego programs into reusable XT components.
Before we can compose XT components, we must place them in a component repository. This is referred to as registration. The registration associates each component with a name, a path and a version. The name is used later to look up components, and map their names to their actual location in the file system. This is very handy when you program with XT components. Inside our Stratego programs, we only have to specify the name of the program we want executed, and the XTC machinery will automatically figure out which program to execute, set up the piping, deal with temporary files, and even perform the execution and parameter passing for us.
In this section, we will cover the registration and lookup mechanisms in some detail.
An XTC repository is a collection of programs and data files. The programs, which we usually refer to as XT components, must be registered explicitly using the xtc tool before they can be used. This is also the case for the data files. A normal program transformation system built with Stratego/XT will contain an XTC repository, where all its components have been registered. Though this registration is done automatically for you by the Stratego build system, it will prove instructive to know what takes place behind the scenes, by the understanding the xtc command (refer to Chapter 29 for an explaination of how to configure the Stratego/XT build system to automatically register your XT components).
Suppose our project, called Ogetarts, has been installed
into .../ogetarts
(where ...
is a path of your choice).
A typical directory layout for this project would include
the following directories:
.../ogetarts/ share/ ogetarts/ bin/ libexec/ lib/
The part we are interested in now is the directory
.../ogetarts/share/ogetarts/
. This
is where the registry for the XTC repository is normally
placed, in the file XTC
. Assuming we
already have the XT component foo2bar
placed in .../ogetarts/libexec/
. The
following xtc invocation will register
it with the Ogetarts registry:
$
xtc -r .../ogetarts/share/ogetarts/XTC register -t foo2bar -l .../ogetarts/libexec -V 1.0
This command will also take care of creating a fresh
XTC
registry file if it does not already
exist. The -l
specifies the path to the component
being registered, and -t
specifies its name. This
name is used for lookup purposes later. The -V
is
used to associate a version number with a given program. It is possible
to include the version number in the lookup of a component, to find
a particular version of a particular component, but this is not
common practice, and should be avoided. However, every registration
must have include a version number.
The XTC system provides a form of inheritance, whereby one repository can refer to another. You may also think of it as a scoping mechanism on the namespaces of repositories. When searches for components fail at the local repository, the imported repositories will be searched in turn.
It is practically always necessary for your project to import the Stratego/XT repository, as most transformation systems built with Stratego/XT reuse many of the XT components. The following command adds the Stratego/XT repository to our project:
$
xtc -r .../ogetarts/share/ogetarts/XTC import /usr/share/StrategoXT/XTC
In case you wondered, the current version of XTC has no mechanism
for declaring some components as private, and others public. Once
a component c
is registered in an XTC repository
r
, all other repositories importing r
can ask for c
.
When you have registered your components or imported other
repositories, you may inspect your repository to see that everything
looks good. This is done using the query
option to xtc. Using query
,
you can either look for a particular component, or list all
registrations in a given repository.
The following command will search the Ogetarts repository for the sglr component. This component was not registered by us, but is inherited from the Stratego/XT repository.
$
xtc -r .../ogetarts/share/ogetarts/XTC query -t sglr
sglr (3.8) : /usr/bin/sglr
Alternatively, we can list all registrations, in one go:
$
xtc -r .../ogetarts/share/ogetarts/XTC query -a
foo2bar (1.0) : .../ogetarts/libexec/foo2bar
stratego-lib (0.16M1) : .../stratego-lib
...
The format of this list is name (version) : path
,
where name
, we remember, is the handle used to
look up the given component. The list follows a pre-determined
order. First, all registrations in the Ogetarts will be displayed.
Here, foo2bar
is our only component. After the
local components, the contents of each imported repository will
be displayed. In our case, we only imported Stratego/XT, and the
first component in Stratego/XT is stratego-lib
. The
other 490 registrations have been omitted, for the sake of clarity.
Much of the scalability of Stratego/XT comes from its component
model, thus it is important to know how to design your own programs
to take advantage of this infrastructure. Fortunately, the
programming interface of XTC consists of a small set of clearly
defined strategies. If you are already familiar with
io-wrap
, as introduced in
Chapter 26, this will be even easier to
comprehend.
There are three main usage scenarios for the XTC API. Either you use the API to create a new XT component of your own, or you use it to invoke an XT component from your Stratego program (which need not be an XT component), or both; you are writing an XT component which calls other XT components.
The simplest way to make an XT component is to wrap your
top level strategy with the xtc-io-wrap
wrapper. This automatically bestows your program with
basic command line option parsing capabilities, and also
basic I/O.
The following is an example of a trivial XT component which just passes through the term passed to it.
module xtcid imports liblib strategies main = xtc-io-wrap(xtcfoo) xtcfoo = id
As with the io-wrap
strategy explained in
Chapter 26, a default set of
command line options is provided by xtc-io-wrap
.
After compiling the xtcid.str
,
we can run it to inspect the default set of options.
$
./xtcid --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:
This is normally all you have to do in order to have a
working XT component. You can add additional options using
the Option
and ArgOption
, as
explained in Chapter 26, by
wrapping your toplevel strategy with
xtc-io-wrap(extra-opts, s)
instead of
xtc-io-wrap(s)
. In either case, your
program will now automatically read the input term
from the file specified with -i
, pass
this to s
, then write the result of
s
to the file specified by
-o
. When -i
or
-o
are not specified,
stdin
and stdout
will be used instead, respectively.
$
echo "My(Term)" | ./xtcfoo -o myterm.trm$
cat myterm.trm My(Term)$
In some situations, it does not make sense for your component to accept an
input term, or to generate and output term. That is, your component may be
a data generator, or a data sink.
In these cases,your component should rather use
xtc-input-wrap
, in the case of a sink, or
xtc-output-wrap
, in the case of a generator.
The following programs shows a trivial generator, which
produces the term "foo"
when invoked.
module xtcfoo imports liblib strategies main = xtc-output-wrap(xtcfoo) xtcfoo = !"foo" ; write-to
xtcfoo
strategy is our top level strategy. Note how it ends in
a call to write-to
. The argument s
to
xtc-output-wrap(s)
must result in a file, not a term. Copying
the current term to a file, is taken care of by the write-to
strategy. write-to
will create a fresh temporary file and
write the current term to it. The inverse of write-to
is
read-from
which reads a term from a file. This latter strategy
is used together with xtc-input-wrap
, analogously to the
example above.
In both cases (xtc-input-wrap
and
xtc-output-wrap
), you may add additional command
line options, by using variants of the wrappers which
accept extra options, xtc-input-wrap(extra-options, s)
and xtc-output-wrap(extra-options, s)
,
respectively.
Checking Component Dependencies.
If you write a component which depends on other components
as part of its operation, you are encouraged to add
dependency checking using the
xtc-check-dependencies
. This is a two step
procedure: First, you add the --check
option to your components command-line options,
by adding check-options(deps)
to the
extra-opts
argument of
xtc-io-wrap
. Afterwards, you should call
xtc-check-dependencies
as part of your
option-processing.
Going back to our simple xtcid program, we
would add the --check
with functionality, as
follows:
module xtcid imports liblib strategies main = xtc-io-wrap(check-options(!["sglr", "foo2bar"]), xtcid) xtcid = xtc-check-dependencies ; id
After recompiling xtcid, the user can now
ask your component to do a self-check of its dependencies,
by calling it with the --check
. You may
want to piggy-back on this option, adding other kinds of
self-checks, such as data consistency checking.
Graceful Termination.
As you have witnessed, the implementation of the XTC model
makes heavy use of temporary files. It is important that
these files are cleaned up after exectuion of your program.
In all programs we have seen so far, this was taken care of
automatically by the wrappers. But what happens if you want
to terminate your program in the middle of execution, by
calling exit
, for example? In that case, the
temporary files will not be removed properly. A safe way
of exiting XTC programs is provided by the
xtc-io-exit
strategy. This strategy is a
plugin replacement for exit
, but takes
care of cleaning any and all temporary files created by
your program before terminating.
Now that we know how to create XT components, and we also know that the Stratego/XT environment provides many reuseable XTC programs, we should take a bit of time to explain how we can invoke these inside our Stratego programs. It is important to realize that your Stratego program need not itself be an XT component before it can call out to other XT components. Both XTC programs and normal Stratego programs call XT components in the same way.
Before we proceed, we need to create a small XTC program
that we can call. The following is an XTC version of
the inc
strategy. When invoked on an
integer n
, it will return
n + 1
.
module xtcinc imports liblib strategies main = xtc-io-wrap(read-from ; <add> (<id>, 1) ; write-to)
Let us quickly whip up a client for this component, that
invokes xtcinc on the integer
1
:
module xtcclient imports liblib strategies main = !1 ; xtc-transform(!"xtcinc")
Let us a compile and run this program.
$
./xtcclient
[ identity crisis | error ] No XTC registration for xtcinc found
./xtcclient: rewriting failed
Harking back a few sections, we should now realize that we forgot
to register xtcinc in a repository. We already
know how to do this. For the sake of this demonstration, let
us create the XTC
registry in the same
directory as the source code:
$
xtc -r ./XTC r -t xtcinc -V 1.0 -l `pwd`
If we were to run our program xtcclient again
at this point, we would still get the same error. This is because
we have not told xtcclient which repository
it should use. This is done by the --xtc-repo
option to the Stratego compiler.
$
strc --xtc-repo ./XTC -i xtcclient.str
....
This should complete the necessary steps: We have created the XTC program
xtcinc, we have registered it in the XTC registry in
./XTC
, and we have told strc to
compile our client program, xtcclient against this
repository. We should be all set to run our composition. Let's try.
$
./xtcclient
2
At last, it works. We have connected two components. By changing the
internals of our components, we can change them to process any terms we
want, and we can of course also add additional components into the mix,
all using the same basic steps we have explained here. Having to remember
the --xtc-repo
option to strc all the
time is a bit annoying, but as we shall in
Chapter 29, this will be taken care of
automatically by the Stratego/XT build system.
Finding XTC Data Files.
We have just seen how to use XTC programs from a repository,
but so far, we have said nothing about any data files we have
registered. This is where the xtc-find-file
strategy
comes in handy. Let us go through the process of creating a term,
registering it, and then using it inside our program.
Suppose the file myterm.trm
contains the
following term, written in plain text:
My(Term)
Calling xtc as follows, will register it in the local repository we have already created.
$
xtc -r ./XTC r -t myterm.trm -V 1.0 -l `pwd`
This will register the file myterm.trm
under the name myterm.trm
, which we can use
to look it up from inside our programs later. Let us make a
new program that does just this.
module xtcload imports liblib strategies main = <xtc-find-file> "myterm.trm" ; read-from
xtc-find
is applied to a string, which must be
the name of an already registered data file in the
repository. As before, we have to compile our program
using strc, and remember to include
the --xtc-repo
option.
$
strc -i xtcload.str --xtc-repo ./XTC
We can now run our freshly compiled program.
$
./xtcload
My(Term)
The result is as we anticipated. Congratulations! You have now mastered the basics of the XTC mechanics.
This chapter introduced you to the mechanics of the XTC model. We saw how to maintain a registry of XT components using the xtc tool, and also how to write XTC compositions in Stratego. Additional detail about the XTC API can be found in the API reference documentation. The complete documentation for the xtc tool is given on the manual page (xtc).