Table of Contents
This chapter explains the strategies available in the library for controlling file and console I/O.
The need for traditionally file I/O is somewhat diminished
for typical applications of Stratego. Normally, Stratego
programs are designed to worktogether connected by Unix
pipes. The programs employ io-wrap
(or similar
strategies) that automatically take care of the input and
output. See Chapter 26 for details.
The primitive layer of Stratego I/O inherits its
characteristics from Unix. The basic I/O strategies
recognize the special files stdout
,
stdin
and stderr
. Streams are
opened by fopen
and closed with
fclose
On top of this, a collection of
more convient strategies have been built.
The basic strategies for console I/O print
and printnl
are used to write terms to
stdout
or stderr
(or any
other opened file). They both take a tuple. The first
element of the tuple is the file to write to, the
second is a list of terms. Each term in the list be
converted to a string, and and these strings will
be concatenated together to form the resulting output.
The printnl
will also append a newline to
the end of the resulting string.
The following module should be compiled with strc, as usual.
module example imports liblib strategies main = <print> (stdout, ["baz"]) ; <printnl> (stdout, [ "foo", 0, "bar" ])
After compiling this file, running it will give the following result:
$
./example bazfoo0bar$
Notice how the string baz
will be written
without a newline (or other space). Also, notice how the
terms in the list argument were concatenated.
When using these strategies in the Stratego Shell, some care must
be taken when using the std*
files, as the following
example shows.
stratego>
<printnl> (stdout(), [ "foo", 0, "bar" ])
foo0bar
The shell requires that you put an extra parenthesis after the
stdout
.
The debug
and error
are convenience
wrappers around printnl
. They will always write
their result to stderr
. The error
strategy is defined as:
error = where(<printnl> (stderr, <id>))
It is used similarly to the printnl
strategy:
stratego>
<error> ["foo", 0, "bar"]
foo0bar
The debug
strategy accepts any term, i.e.
not only lists of terms. The term will be written
verbatim:
stratego>
<debug> [ "foo", 0, "bar" ]
["foo",0,"bar"]
The library provides a small set of simple file and directory
manipulation operations. Assume the directory
/tmp
only contains the files
foo
, bar
,
baz
. Elementary directory operations
can be done as illustrated below:
stratego>
<readdir> "/tmp" ["foo","bar","baz"]stratego>
<rename-file> ("/tmp/foo", "/tmp/bax") "/tmp/bax"stratego>
<remove-file> "/tmp/baz" []stratego>
<link-file> ("/tmp/bar", "/tmp/foo") "/tmp/foo"stratego>
<link-file> ("/tmp/bar", "/tmp/foo") "/tmp/foo"stratego>
<new-temp-dir> "/tmp" "/tmp/StrategoXTnsGplS"
The library contains a family of strategies which must be applied
to a File
, and will return information about it.
these include isdir
, isatty
,
isfifo
and islnk
which are predicates
checking if a file is a directory, TTY, FIFO or a symbolic
link, respectively. To obtain a File
object in the
first place, we should call file-exists
followed
by filemode
. Thus, checking if /etc
is a directory is done as follows:
stratego>
<file-exists ; filemode ; isdir> "/etc"
The library also has another family of strategies for getting
information about files. These must be applied to a string
containing the filename. The family includes
is-executable
, is-readable
and
is-writeable
.
stratego>
<is-executable> "/bin/bash"
"/bin/bash"
Finally, the directory strategies also include the usual suspects for dealing with paths.
stratego>
<is-abspath> "../foo" command failedstratego>
<dirname> "/foo/bar/baz" "/foo/bar"stratego>
<base-filename> "/foo/bar/baz" "baz"stratego>
<get-extension "/tmp/foo.trm" "trm"stratego>
<abspath> "../foo" /home/karltk/source/oss/stratego/strategoxt-manual/trunk/../foo
There are also a few strategies for finding files. We shall
describe find-file(s)
. The other variants of
find-file
are described in the library
documentation. The strategy find-file(s)
finds one
file with a specific file extension in a list of directories. It
takes a two-element tuple. The first element is a file name as a
string, then second element is a list of paths, i.e. (f,
[d*])
. The extension of f
will be
replaced by what is produced by s
, and the
directories given in [d*]
. Consider the code below.
stratego>
<find-file(!"rtree")> ("file.str", ["."])
This snippet will consider the filename file.str
,
replace its extension with rtree
and look through
the directories in the list ["."]
. Effectively, it will
search for file.rtree
in the current directory.
Opening a file is done with the fopen
strategy. It takes
a two-element tuple, the first element is the filename as a string,
the second is the open mode, which is also a string. The most important
modes are read (r
); write ("w") which opens and empty file
for writing, truncating any existing file with the same name; and
append (a
) which appends to the file if it already exists.
After all file operations stream have been finished, it should be closed
with fclose
, which will flush and close the file. Explicit
flushing can also be done with fflush
.
It should be pointed out that reading and writing text files with Stratego
is rather rare. Normally, text files are read with a parser generated from
an SDF description and written using a pretty-printer defined in the
Box formalism. In rare cases, this may turn out be too heavy handed,
especially if the file format is simplistic and line-based. In this
instance, we can come up with an easier solution using
read-text-file
and read-text-line
.
Assume the file /tmp/foo
contains the following
lines:
one two three
We can read this file in one big chunk into a string with the
read-text-file
strategy, which must be applied to
a filename:
stratego> <read-text-file> "/tmp/foo" "one\ntwo\nthree\n"
Alternatively, for example if the file is large, we can read it line by line. In this scenario, we must open the file and get a handle to a stream.
stratego>
<fopen> ("foo.txt", "r") => inp Stream(136788400)stratego>
<read-text-line> inp "one"
The primary form of file I/O you will be using in Stratego is
reading and writing terms. As explained earlier, the terms
are stored on disk as either binary, compressed text or
plain text ATerms. Reading a term, no matter which storage
format, is done with the ReadFromFile
strategy.
It is applied to a filename.
stratego>
<ReadFromFile> "/tmp/foo.trm"
Foo(Bar)
To write a term to file, you can use WriteToTextFile
or WriteToBinaryFile
. The binary format is
approximately eight times more space-efficient on average. Both
strategies take a two-element tuple where the first element is
the filename and second is the term to write. Writing the current
term requires a minor twist, which is shown here:
stratego>
<WriteToBinaryFile> ("/tmp/bar.trm", <id>)
Foo(Bar)
It is also possible to read and write terms from and to strings,
using read-from-string
and write-to-string
.
Chapter 23 contains explanation of how these
strategies work.
The strategies for logging are used pervasively throughout the Stratego
toolchain. They are easy to use in your own applications, too. The
logging system is built on top of the
log(|severity, msg)
and log(|severity, msg, term)
strategies. It is possible to use these directory, as the following
example demonstrates.
stratego>
import util/logstratego>
log(|Error(), "my error")
However, it is preferrable to use the high-level wrapper strategies
fatal-err-msg(|msg)
, err-msg(|msg)
,
warn-msg(|msg)
and notice-msg(|msg)
.
Except for fatal-err-msg
, these strategies will
return with the current term untouched, and write the message
as a side effect. The fatal-err-msg
strategy will
also terminate the program with error code 1
, after
writing the message.