This version of a shell program reads from a keyboard and executes commands typed by the user. Its own interface has the type of a Command
module, and that is the type of the things it executes. In particular, it can call modules like the hello
example at the beginning of this chapter.
1 implement Command; 2 include "sys.m"; 3 include "draw.m"; 4 sys : Sys; 5 stdin : ref Sys->FD; 6 Command: module 7 { 8 init : fn(nil: ref Draw->Context, nil : list of string); 9 };After the boilerplate on lines 1-3, the variables
sys
and stdin
are declared on lines 4 and 5. The I/O operations of the Sys
module use the ref FD
type to refer to open files.
10 init(ctx : ref Draw->Context, nil: list of string) 11 { 12 n, nw : int; 13 arg : list of string; 14 buf := array[256] of byte; 15 sys = load Sys Sys->PATH; 16 stdin = sys->fildes(0); 17 for(;;) { 18 sys->print("$ "); 19 n = sys->read(stdin, buf, len buf); 20 if(n <= 0) 21 break; 22 (nw, arg) = sys->tokenize(string buf[0:n], " \t\n"); 23 if(nw != 0) 24 exec(ctx, arg); 25 } 26 }Line 10: conventionally, stand-alone modules are started by calling their
init
functions. The Command
module follows this convention. The arguments are presented as a list of strings. In this simple example, the command interpreter itself ignores its second argument, so it need not be given a name.
Local variables are declared on lines 12-14; line 15 loads the Sys
module and stores a handle for it in the variable sys
. Line 16 creates an FD
for the standard input by calling the fildes
function of the Sys
module using the ->
operator; the notation modhandle->func(...)
specifies a call to the function named func
in the module currently referred to by modhandle
. (In general there can be several modules of the same type and name active, and there also can be unrelated modules containing identically named functions. The import
declaration, described in Declarations with Import, can be used to abbreviate the references when names do not clash.)
The loop on lines 17-25 prints a prompt (line 18), reads a line from the standard input (line 19), parses it into tokens (line 22), and executes the command.
The function call sys->tokenize
is worth discussing as an example of style. It takes two strings as arguments. The characters in the second string are interpreted as separators of tokens in the first string. It returns a tuple whose first member is the number of tokens found, and whose second is a list of strings containing the tokens found: its declaration is:
tokenize: fn (c : string, sep : string) : (int, list of string);In the example, the second argument is
" \t\n"
, so that the routine returns the number of, and a list of, "words" separated by blanks, tabs, and new-lines. The free use of strings, lists, and tuple-returning functions is common in Limbo.
The sys->read
routine gathers an array of bytes into buf
. Thus the expression for the first argument of sys->tokenize
converts this array to a string by slicing the array with [0:n]
, using the actual number of bytes gathered by the read
, and using a cast.
At lines 23-24, if there were any words found, exec
is called:
27 exec(ctx : ref Draw->Context, args : list of string) 28 { 29 c : Command; 30 cmd, file : string; 31 cmd = hd args; 32 file = cmd + ".dis"; 33 c = load Command file; 34 if (c == nil) 35 c = load Command "/dis/"+file; 36 if (c == nil) { 37 sys->print("%s: not found\n", cmd); 38 return; 39 } 40 c->init(ctx, args); 41 }On lines 31 and 32 of
exec
, cmd
is set to the first of the words in the argument list, and the string .dis
is concatenated to it (to account for the fact that Limbo object program files are conventionally named using this suffix). On line 33 an attempt is made to load the named module from the derived file name; it will fail if the file does not exist. The attempt will succeed, and a non-nil handle assigned to c
, if the file is found, and if the module stored in that file does in fact implement the Command
module type. In case this fails, lines 34-35 make another attempt, after prefixing /dis/
to the file name.
If either attempt to get a handle to the named module succeeds, c
will contain a valid handle to it; line 40 calls its init
function, passing it the whole argument list. When it returns, the exec
function returns, and the main loop resumes.