Input and Output

Contents

Overview

XMF input and output is achieved using channels defined in the package IO. An input channel is a source of integers in the range 0 to 255 (typically ASCII character codes) that can be read one-by-one by an XOCL program. An output channel is the target of integers in the range 0 to 255. Usually an input channel is attached to an external source that provides the sequence of values and output channels are attached to an external source that receives the sequence of values.

XMF provides a range of different types of input and output channel. This document describes the different types and gives examples of their use.

To top.

Input Channels

Input Channels

The diagram shown above contains the main input channels. Each input channel inherits from IO::InputChannel and provides an implementation of the following interface:
// Close the channel...
close()
// Return true when the channel is closed...
eof():Boolean
// Peek at the next byte...
peek():Integer
// Read and return the next byte...
read():Integer
// Read and return the next line up to the
// the next newline character...
readLine():String
// Read all input until the vector is full
// or EOF is encountered...
read(v:Vector)
// Read the next byte which does not have a
// whitespace ASCII code...
readNonWhiteSpace():Integer
// Read and discard the input until EOF is
// encountered or a non-whitespace char is
// found...
skipWhiteSpace()
Subsequent sections describe the individual types of input channel.

To top.

Standard Input Channels

A standard input channel is connected to the standard input provided to XMF from the operating system. The global variable stdin is bound to a standard input channel. The stdin channel is used by the top-level command loop.

To top.

String Input Channels

A string input channel is used to translate a string into an input channel. For example:
import IO;

context Root

@Operation stringInputChannelExample(s:String)
let sin:StringInputChannel = StringInputChannel(s)
in @While not sin.eof() do
format(stdout,"~C",Seq{sin.read()})
end
end
end
To top.

File Input Channels

A file input channel is used to connect to a file and read the contents character-by-character. You can create a file input channel directly or use the @WithOpenFile construct to handle the appropriate exceptions and to close the channel:
import IO;

context Root

@Operation fileInputChannelExample(path:String)
let fin:FileInputChannel = FileInputChannel(path)
in try
@While not fin.eof() do
format(stdout,"~C",Seq{fin.read()})
end;
fin.close()
catch(x)
fin.close();
throw x
end
end
end
which is equivalent to:
context Root
@Operation fileInputChannelExample(path:String)
@WithOpenFile(fin <- path)
@While not fin.eof() do
format(stdout,"~C",Seq{fin.read()})
end
end
end
Check whether a file exists using String::fileExists():Boolean.

To top.

URL Input Channels

URL input channels are like file input channels, however instead of a file system path, the channel is initialised with a URL:
 context Root
@Operation urlInputChannelExample(url:String)
let uin:URLInputChannel = URLInputChannel(url)
in try
@While not uin.eof() do
format(stdout,"~C",Seq{uin.read()})
end;
uin.close()
catch(x)
uin.close();
throw x
end
end
end
To top.

Binary Input Channels

Compiled XMF code is saved in a .o file (actually in XAR format - see ElementInputChannel). Normally, a .o file is loaded using String::loadBin which reads the XAR formatted data, expects it to be a code box, transforms the code box to a n operation and loads and runs it on the VM. However, the data in a .o file may originate from a source other than a file. Binary input channels allow the data to be read from an arbitrary underlying input channel (for example a string input channel or a URL input channel). The following example shows how String::loadBin might be implemented:
import IO;

context Root
@Operation loadBin(path:String)
@WithOpenFile(fin <- path)
let bin:BinaryInputChannel = BinaryInputChannel(fin)
in bin.readBinary()
end
end
end
To top.

DOM Input Channels

A DOM input channel processes the input from an underlying input channel that is attached to an XML source (such as a text file containing XML data). The result is an instance of XML::Document, i.e. a model of the XML data that was read from the underlying channel. Here is an example of an operation that constructs a model of the XML data in a text file:
import IO;

context Root

@Operation loadXML(path:String)
@WithOpenFile(fin <- path)
let xin:DOMInputChannel = DOMInputChannel(fin)
in xin.parse()
end
end
end
To top.

SAX Input Channels

A SAX input channel processes input from an underling input channel that is attached to an XML source. Each time an XML component is encountered, the SAX input channel calls a corresponding operation with the input data. The default implementation of the operations is to do nothing. This allows sub-classes of SAXInputChannel to redefine the SAX interface to take appropriate action, for example by constructing instances of an appropriate model. The SAXInputChannel is the basis for the XMF XML parsing mechanism and allows low-level access to XML data.

The SAX interface is defined in SAXInputChannel as follows:
    
@Operation characters(chars:Buffer)

// Called when the XML input provides text.
// Note the buffer is immediately reused...

format(stdout,"chars '~S'~%",Seq{chars})
end

@Operation endElement(tag:Buffer)

// Called when a tag is closed: </TAG>.
// Note the buffer is immediately reused...

format(stdout,"end element ~S~%",Seq{tag})
end

@Operation parse()

// Read the XML input and call the operations
// in the interface...

end


@Operation startElement(tag:Buffer,atts:Buffer)

// Called when a <TAG ATTS> is encountered in the input.
// The two buffers are immediately reused. The attribute
// buffer contains instances of IO::SAXAttribute with slots
// name:String and Value:String. The attributes are
// also reused...

format(stdout,"start element ~S ~S~%",Seq{tag,atts->asSeq})
end
To top.

Element Input Channels

XMF uses a binary format for data called XAR. Data is encoded in XAR format using an ElementOutputChannel and read back in using an ElementInputChannel. The Element input channel takes the XAR encoded bytes from an underlying input channel and returns the encoded data. The following is an example where the XAR data is taken from a file:
import IO;

context Root

@Operation loadXAR(path:String)
@WithOpenFile(fin <- path)
let ein:ElementInputChannel= ElementInputChannel(fin)
in ein.read()
end
end
end
To top.

GZIP Input Channels

Uncompresses the input that was compressed using a GZipOutputChannel.

To top.

Output Channels

Output Channels

The diagram above shows the main output channels. Each output channel implements the following interface:
// Write a value (usually an integer 0 - 255)
// to the channel...
write(value)
// Write a string to the channel...
writeString(string:String)
// Close the channel...
close()
// Flush any buffered output...
flush()
Subsequent sections define the different types of output channel.

To top.

Standard Output Channels

There is a single instance of StandardOutputChannel which is bound to the global variable stdout. Sending output to stdout will print the information on the console:
format(stdout,"This is a message~%")
To top.

String Output Channels

A string output channel saves all the output in a string which you can request during the life-time of the channel:
import IO;

context Root
@Operation getContents(path:String):String
@WithOpenFile(fin <- path)
let sout:StringOutputChannel = StringOutputChannel()
in @While not fin.eof() do
sout.write(fin.read())
end;
sout.getString()
end
end
end
To top.

File Output Channels

A file output channel writes to a named file. The preferred way of using files is WithOpenFile. Here is a file transfer program:
context Root
@Operation transferContents(source:String,destination:String)
@WithOpenFile(fin <- source)
@WithOpenFile(fout -> destination)
fout.write(fin.read())
end
end
end
When writing to a file, it is created if it does not exist.

To top.

Temporary Files

Sometimes it is useful to create a file whose name is irrelevant but whose uniqueness is important. This can be done using IO::File::tempFile as follows:
import IO;

context Root
@Operation fileOutputChannelExample()
let prefix:String = "myFile";
type:String = ".tmp";
dir = null then
path = File::tempFile(prefix,type,dir)
in @WithOpenFile( <- path)
@While not fin.eof() do
format(stdout,"~C",Seq{fin.read()})
end
end
end
end
To top.

Directories

Use String::isDir():Boolean and String::mkDir(path:String):Boolean to test and create directories.

To top.

Element Output Channels

XMF can serialize any data into a binary XAR format that can be subsequently read back into a different XMF session. This is achieved by writing an element to an element output channel. In addition to supplying the element to be serialized, the output channel must be supplied with a sequence of name-spaces. These are name-spaces that will be assumed to be present when the element is read back in.

The effect of supplying a sequence of name-spaces Seq{P,Q,R} is that any instances of classes in P, Q and R are serialized on the assumption that those classes do not need to be serialized and will be present on loading. If P, Q and R are not supplied to the element output channel, then any instances of a class P::C (for example) will cause P::C to be serialized. Almost always, this is not what was intended. The default is to supply all name-spaces to an element output channel:
import IO;

context Root
@Operation serialize(element,path:String)
@WithOpenFile(fout -> path)
let eout:ElementOutputChannel = ElementOutputChannel(fout);
nameSpaces = Root.allContentsOf(NameSpace)->asSeq->including(Root)
in eout.write(element,nameSpaces)
end
end
end
this is equivalent to:
import IO;

context Root
@Operation serialize(element,path:String)
@WithOpenFile(fout -> path)
let eout:ElementOutputChannel = ElementOutputChannel(fout)
in eout.write(element)
end
end
end
To top.