Oberon || Library || Module Index || Search Engine || Definition || Module

Ulm's Oberon Library:


Args - general abstraction for arguments


CONST nameLen = 256;
TYPE Name = ARRAY nameLen OF CHAR; (* the name of an argument *)
TYPE Value = POINTER TO ValueRec; (* the value of an argument *)
TYPE ValueRec = RECORD (PersistentDisciplines.ObjectRec) END;
TYPE Arguments = POINTER TO ArgumentsRec; (* collection of arguments *)
TYPE ArgumentsRec = RECORD (PersistentDisciplines.ObjectRec) END;

TYPE ReadProc = PROCEDURE (s: Streams.Stream; VAR value: Value) : BOOLEAN; TYPE PrintProc = PROCEDURE (s: Streams.Stream; value: Value); TYPE HelpProc = PROCEDURE (s: Streams.Stream); TYPE TypeInterface = POINTER TO TypeInterfaceRec; TYPE TypeInterfaceRec = RECORD (Objects.ObjectRec) read: ReadProc; print: PrintProc; help: HelpProc; (* may be NIL *) END; TYPE Type = POINTER TO TypeRec; TYPE TypeRec = RECORD (Disciplines.ObjectRec) name: Name; moduleName: Name; END;

TYPE ScanProc = PROCEDURE (args: Arguments); TYPE ScannerList = POINTER TO ScannerListRec; TYPE ScannerListRec = RECORD (Disciplines.ObjectRec) END;

TYPE Argument = POINTER TO ArgumentRec; TYPE ArgumentRec = RECORD (Disciplines.ObjectRec) name: Name; short: CHAR; type: Type; description: Name; END;

TYPE ChangeNotification = POINTER TO ChangeNotificationRec; TYPE ChangeNotificationRec = RECORD (Events.EventRec) args: Arguments; arg: Argument; END;

CONST lowPriority = 0; CONST middlePriority = 1; CONST highPriority = 2; TYPE Priority = INTEGER;

VAR systemScanners: ScannerList; (* predefined scanners *)

CONST cannotReadArgs = 0; (* failed to read list of arguments *) CONST unknownTypeName = 1; (* unknown type name encountered *) CONST twiceDefined = 2; (* an argument name has been defined twice *) CONST cannotWriteArgs = 3; (* failed to write list of arguments *) CONST unknownArgName = 4; (* an unknown argument name was given *) CONST namedErrors = {unknownTypeName, twiceDefined, unknownArgName}; CONST errorcodes = 5; (* number of error codes *) TYPE ErrorEvent = POINTER TO ErrorEventRec; TYPE ErrorEventRec = RECORD (Events.EventRec) errorcode: SHORTINT; name: Name; (* defined if errorcode IN namedErrors *) END; VAR errormsg: ARRAY errorcodes OF Events.Message; VAR error: Events.EventType;

(* client side *) PROCEDURE Create(VAR args: Arguments); PROCEDURE CreateCopyOf(VAR args: Arguments; orig: Arguments); PROCEDURE Define(args: Arguments; name: ARRAY OF CHAR; short: CHAR; type: Type; description: ARRAY OF CHAR); PROCEDURE Exists(args: Arguments; name: ARRAY OF CHAR) : BOOLEAN; PROCEDURE Scan(args: Arguments; scanners: ScannerList); PROCEDURE GetValue(args: Arguments; name: ARRAY OF CHAR; VAR value: Value); PROCEDURE SetValue(args: Arguments; name: ARRAY OF CHAR; value: Value); PROCEDURE GetNotification(args: Arguments; name: ARRAY OF CHAR; VAR eventType: Events.EventType); PROCEDURE Print(args: Arguments; name: ARRAY OF CHAR; s: Streams.Stream); PROCEDURE PrintValue(s: Streams.Stream; value: Value); PROCEDURE ReadValue(s: Streams.Stream; type: Type; VAR value: Value) : BOOLEAN; PROCEDURE IterateArgs(args: Arguments; VAR it: Iterators.Iterator); PROCEDURE Seek(args: Arguments; name: ARRAY OF CHAR; VAR arg: Argument); PROCEDURE String2Type(string: ARRAY OF CHAR) : Type; PROCEDURE TypeHelp(args: Arguments; type: Type; s: Streams.Stream); PROCEDURE IterateTypes(VAR it: Iterators.Iterator); PROCEDURE AssignPrintProc(s: Streams.Stream; type: Type; print: PrintProc);

(* scanner providers *) PROCEDURE CreateScannerList(VAR list: ScannerList); PROCEDURE RegisterScanner(scanners: ScannerList; scan: ScanProc; priority: Priority); PROCEDURE ScanValue(args: Arguments; name: ARRAY OF CHAR; stream: Streams.Stream) : BOOLEAN;

(* type providers *) PROCEDURE DefineType(type: Type; if: TypeInterface; valtype: Services.Type);


Args offers a general abstraction of arguments which come from the environment or the user interface. Examples are the traditional UNIX command line (see UnixCommandLine), environment variables of the shell which are passed through exec(2), configuration files (so called rc files), X resources, or the text after the selected command in the Zürich Oberon System.

Arguments are name/value pairs with some additional components which are organized in argument collections. Argument values are typed and the types are associated with a set of interface procedures which allow to read and print them. A scanner knows how to retrieve arguments from a specific area (e.g. from the command line). Scan allows to fire a set of scanners for a given argument collection.

Defining a Set of Arguments

Each module which is interested in retrieving arguments from the environment should create a list of arguments (or get one from a calling module). Create creates an empty argument list which may be filled with Define. Note that all newly added arguments have initially an undefined value. Each argument has a type which determines how the value is represented, how it may be read in, and how it may be printed. Some basic types are offered by BoolArgs, IntArgs, StringArgs, and StrListArgs.

The following example shows the creation of an argument list with three arguments:

VAR args: Args.Arguments; (* argument list *)

(* ... *)

Args.Create(args); Args.Define(args, "copies", "#", IntArgs.type, "number of copies"); Args.Define(args, "file", "f", StringArgs.type, "file to be printed"); Args.Define(args, "printer", "p", StringArgs.type, "name of the printer");
The name of an argument must be unique for the given argument list. Optionally, a short name may be given (one character long). The last parameter is a short help text which may be later retrieved by IterateArgs.

It is possible to give argument default values, or to modify them at a later time by SetValue. In the example above, the number of copies should probably default to 1:

VAR value: Args.Value;

(* ... *) IntArgs.Create(value, 1); Args.SetValue(args, "copies", value);

CreateCopyOf allows to create a new argument list as clone of the already existing list orig.

Scan allows to call all scanners of a scanner list for a given argument list. All indirectly invoked scanners try to locate settings for the arguments in their area and to assign them. Usually, systemScanners should be given as scanners to Scan. UnixCommandLine, for example, (which is member of systemScanners) would assign letter to file and hp to printer for following command line:

cmdname -p hp -file letter

After calling Scan, it is useful to retrieve values by GetValue. Note that GetValue returns NIL for undefined values. In the example above, the file which is to be printed may be retrieved by:

VAR file: StringArgs.Value; filename: ARRAY 80 OF CHAR;

(* ... *)

Args.GetValue(args, "file", file); IF file = NIL THEN (* a file name was not given anywhere *) ELSE COPY(file.string, filename); END;

Offering help

Args supports the generation of help texts. Usually, help texts are printed either on request (by a special help option) or in case of errors (see diagnostics section below). Given a set of arguments, a comprehensive help text for them may be generated by iterating through them:
PROCEDURE PrintHelpText(s: Streams.Stream; args: Args.Arguments);
   VAR it: Iterators.Iterator; arg: Args.Argument;
   Args.IterateArgs(args, it);
   WHILE Iterators.Yield(it, arg) DO
      Print.S4(s, "%1s %-20s %-8s  %s\n",
         arg.short, arg.name, arg.type.name, arg.description);
END PrintHelpText;

Print and PrintValue allow to print arbitrary values to the given stream. Following example shows how the values of all defined arguments may be printed:

PROCEDURE PrintValues(s: Streams.Stream; args: Args.Arguments);
   VAR it: Iterators.Iterator; arg: Args.Argument;
   Args.IterateArgs(args, it);
   WHILE Iterators.Yield(it, arg) DO
      Print.S2(s, "%-20s: %-8s = ", arg.name, arg.type.name);
      Args.Print(args, arg.name, s);
      Print.S(s, "\n");
END PrintValues;
Note that it is possible to override for a given stream the original type-specific printing procedure for a specific type by AssignPrintProc.

Informations about the supported argument types may be retrieved by TypeHelp, and IterateTypes allows to iterate through all supported types. String2Type allows to convert a (probably user-supplied) type name into a type. NIL is returned if the type name is not known.

Creating a Scanner

Specific scanners scan through their area and look for possible argument names (either the long or short form). They have only to provide one interface procedure to RegisterScanner. The priority determines the calling order of scanners of a scanner list. Scanners with high priority are called last, that means that they are able to override value settings of scanners with lower priority.

Scanners may check the existence of argument names by Exist. It depends usually on the scanner whether missing argument names are considered as an error (of the invoker / user) or not. UnixCommandLine, for example, generates error events for each option named on the command line which is not part of the argument list. Other scanners, however, are less restrictive.

If an argument has been found, it may be read in by ScanValue which, in turn, calls the type-specific read procedure of the argument. Note that these read procedures are expected to interpret field separators and line terminators (see StreamDisciplines) as delimiters. Alternatively, Seek may be called to access the argument directly.

Following example shows a simple implementation of a scanner which takes arguments out of a rc file whose lines have two fields which are separated by a colon. The first field is the name of the parameter and the second the associated value:

PROCEDURE ScanFile(args: Args.Arguments);
      s: Streams.Stream;
      fieldseps: Sets.CharSet;
      name: Args.Name;
   (* open the rc file and let s point to it *)
   RelatedEvents.Forward(s, args);
   Sets.InitSet(fieldseps); Sets.InclChar(fieldseps, ":");
   StreamDisciplines.SetFieldSepSet(s, fieldseps);
   WHILE Read.FieldS(s, name) DO
      IF ~Args.Exists(args, name) OR ~Args.ScanValue(args, name, s) THEN
         (* raise an appropriate error event and relate it to args *)
END ScanFile;

Offering an Argument Type

Argument types are to be created and initialized (components name and moduleName) by the type provider and then passed to DefineType. The parameter valtype is the type of Services.Type for the associated extension of Args.Value which has been returned by PersistentObjects.RegisterType. This type is used as guard to assure that values which are declared to be of type type by Define are extensions of valtype. The interface is expected to meet following specification:

read: PROCEDURE(s: Streams.Stream; VAR value: Value) : BOOLEAN;
read a value from the given stream. Exactly one input field in the sense of StreamDisciplines and Read.FieldS is to be consumed from s. That means that line terminators may not be skipped over and field separators work as delimiters but are to be consumed. This field must be read in completely even if the input does not syntactically conform to the type. But read has to terminate immediately on end of file or in case of errors returned from Streams. TRUE is to be returned if a value has been read successfully.
Note that read is free to update value instead of creating it if value is non-NIL. This could be of interest if value represents a list or set where subsequent read operations add new elements.

print: PROCEDURE(s: Streams.Stream; value: Value);
print the given value (which is guaranteed to be non-NIL and an extension of valtype) in ``readable'' form on the given stream. If multiple lines are needed, Write.IndentS is to be called on the beginning of each new line. Note that the last line must not be terminated.

help: PROCEDURE(s: Streams.Stream);
print some useful help information about the type to s. Note that multiple lines should be avoided here. If they are nevertheless necessary, the conventions of print should be used. This interface procedure is optional and may be set to NIL.

Additionally, type providers should export a simple constructor to allow default values to be set.

ReadValue allows to create a value of the given type and to scan it from s. This may be useful for type constructors whose read interface procedures want to read values of their subtypes.

Internal Links on base of IndirectDisciplines

Args sets up links of IndirectDisciplines from each argument to its argument list, and from each stream which is passed to the interface procedures read and print to the argument which is to be read or printed. Thus, stream disciplines (see StreamDisciplines) or other disciplines (see, for example, BoolDisciplines and BoolArgs) may be given for a specific argument or for an argument list. The links from streams to individual arguments are removed when the associated operation finishes.


Some errors result in events which are related to the args parameter. Following error codes are implemented:
An unknown argument name was given to ScanValue, GetValue, SetValue, GetNotification, or PrintValue.
An argument name was given to Define which has been already given earlier for the same argument list.
Args was not able to read an argument list (via PersistentObjects.Read).
Is returned on write failures during PersistentObjects.Write for argument lists.
An unknown argument value type name was encountered during PersistentObjects.Read for an argument list.

Some errors are caught by assertions:


arguments of type BOOLEAN
arguments of type INTEGER
input and output of persistent objects
parameterized definition of an input field
arguments of type ARRAY OF CHAR
arguments of type ARRAY OF ARRAY OF CHAR
scanner for the UNIX command line


The original version of this module was written by Jan Oliver Stibane, all revisions and this manual page are due to Andreas Franz Borchert.
Edited by: borchert, last change: 2004/06/02, revision: 1.3, converted to HTML: 2004/06/02

Oberon || Library || Module Index || Search Engine || Definition || Module