.Px Limbo Programming

Limbo Programming

Inferno Business Unit

1 Overview



This chapter introduces features that are important to Limbo programming and the programming environment for Inferno.

This chapter covers:
*
Modules
*
I/O
*
Threads
*
Channels
*
Graphics Programming

2 Modules



In Limbo, modules are the basic building blocks of all programs. Indeed, every program is a ``module''.

Limbo modules do more than divide a program into smaller parts. Limbo modules:
*
Group functions and data into logical, distinct packages
*
Separate interface and implementation
*
Have a well-defined interface that provides the only access mechanism to the module
*
Have a type defined through its interface
*
Enable dynamically loadable libraries
*
Can be rebuilt without recompiling or relinking user programs
*
Compose a Limbo application as a set of interacting components that contain data and functions

2.1 Interface and Implementation



Limbo modules can combine the interface and implementation. The Greet program shown in Program Listing 2-1 on page 2-2 is an example of this. In this case, only the init function is declared in the module declaration and is therefore exportable.

With Limbo's modular programming model, the separation of interface and implementation is an important concept. It is by means of this that Limbo programs can dynamically load and unload modules, or libraries.

The Inferno system interface illustrates the power of this in Limbo. The interface groups functionality into small loadable modules that have well-defined interfaces contained in module interface files. The executable code is contained in the implementation file.

2.2 Module Interface File



A module's public interface is contained in a module interface file, conventionally with the .m suffix. It is analogous to a C or C++ header file (.h).

The Inferno module interface files are conventionally stored in the /module directory.

A module interface file declares the public data and function members. It specifies the types and function interfaces that can be used by other programs. It is the module interface declaration.

Here is an example of a simple module interface file:
Greet: module {
	init: fn(ctxt: ref Draw->Context, argv: list of string);
};


This declares one function, init.

Here is another example, the declaration of the Inferno Rand module:
Rand: module
{
	PATH:   con "/dis/lib/rand.dis";
	# init sets a seed
	init:   fn(seed: int);
	# rand returns something in 0 .. modulus-1
	# (if 0 < modulus < 2^31)
	rand:       fn(modulus: int): int;
	# (if 0 < modulus < 2^53)
	bigrand:    fn(modulus: big): big;
};


It declares a string constant, PATH that contains the specific path to the compiled implementation file, and three functions, init, rand, and bigrand. The string constant PATH is a convention in Limbo modules.

2.3 The Implementation File



The module's implementation is contained in an implementation file, conventionally with the .b suffix. This is the executable statements of the program. It can also be called the source file. Examples of implementation files are in the /appl directory tree.

Program Listing 3-2 is the implementation file for the module interface file shown in Program Listing 3-1:
implement Greet;
include "sys.m";
include "draw.m";
include "greet.m";
sys: Sys;
init(ctxt: ref Draw->Context, argv: list of string) {
	sys = load Sys Sys->PATH;
	if ((tl argv) != nil)
		sys->print("Hello, %s!\n", hd (tl argv));
	else
		sys->print("Hello, World!\n");
}


This implementation file contains the function definition that is declared in the module interface file.

The implementation file can also contain private data and function member declarations and definitions. For example, the implementation file for the Random Module (in the /appl/lib/rand.b file):
implement Rand;
include "rand.m";
rsalt: big;
init(seed: int)
{
	rsalt = big seed;
}
MASK: con (big 1<<63)-(big 1);
rand(modulus: int): int
{
	rsalt = rsalt * big 1103515245 + big 12345;
	if(modulus <= 0)
		return 0;
	return int (((rsalt&amp;MASK)>>10) % big modulus);
}
bigrand(modulus: big): big
{
	rsalt = rsalt * big 1103515245 + big 12345;
	if(modulus <= big 0)
		return big 0;
	return ((rsalt&amp;MASK)>>10) % modulus;
}


This file contains the definitions of the three public functions: init, rand and bigrand, and private data rsalt and MASK.

The Executable File



The compiled implementation file, the Dis executable file (.dis) is conventionally stored in the /dis directory.

The executable file is the object that is loaded at run-time by the Limbo load operator. For example, to load the file rand.dis, the following could be used:
	rand := load Rand Rand->PATH;


This is discussed more in §XX, Using Modules.

2.4 Module Structure



The basic structure of a Limbo module includes the following:
*
Implementation section
*
Module interface declarations (required for all files)
*
Function definitions (only required for implementation files)

2.5 Implementation Section



The implementation section within a Limbo module states which module is being implemented. It primarily consists of the implement statement, such as:
	implement Greet;
By convention, the include statements are also within the implementation section. For example:
	include "sys.m";
	include "draw.m";

2.6 Module Interface Declaration



The module interface declaration declares the public portion of the module. This includes all public types and functions. If a module has a separate module interface file, a .m file, it contains this interface declaration. For example:
Greet: module {
	init: fn(ctxt: ref Display->Draw, args: list of string);
};


This statement says, ``Greet is a module. Its public interface is just init, declared to be a function that receives two arguments''.

2.7 Function Definitions



The definitions of functions that are declared in the module interface are in the implementation file. Whether the module interface is in a separeate module interface file or in the implementation file, the function definitions are the same.

For example, the following is the definition of the function declared in the Greet module interface:
	init(ctxt: ref Draw->Context, argv: list of string) {
		sys = load Sys Sys->PATH;
		if ((tl argv) != nil)
			sys->print("Hello, %s!\n", hd (tl argv));
		else
			sys->print("Hello, World!\n");
	}


2.8 Using Modules



Module data and function members are accessed by including the module interface file and loading the implementation file into Limbo program code.

2.8.1 The include Statement



As mentioned earlier, in Limbo include is a keyword, not a preprocessor directive. The include statement specifies the module interface file (.m) that you want to include into your program. This is required before you can load or use any of that module's data or function members.

The file name following the include keyword is always enclosed in double quotes (i.e., sys.m"" ). If an explicit path is not given for the file, Limbo searches the current working directory. If that fails, it searches directories specified with the -I option for the Limbo compiler, if any. Then, if that fails, it searches the /module directory.

2.8.2 The load Statement



Prior to calling a module's functions, an application must load the module. The application uses the resulting handle as the module qualifier. For example:
	sys = load Sys Sys->PATH;
	mymod = load MyMod "/mymodules/mymod.dis";


The general form of the load statement is:
handle = load module_name implementation_path ;

As is shown by the Inferno Reference API modules, it is a convention to declare a constant named PATH in the module that contains the explicit path to the implementation file. (See Inferno Reference Manual for more information about the Inferno Reference API.)

Use the Module Member operator (->) to access module data and function members. For example:
	sys := load Sys Sys->PATH;
	sys->print("Hello, World!\n");


In the first line, sys is declared and assigned to be the handle to the Sys module. In the second line, the handle sys is used to access the print function with the -> operator.

The Sys Module uses a special PATH constant:
	Sys: module
	{
		PATH:	con "$Sys";
		...
	}


A path value beginning `$' is handled specially when used as the parameter to load operator. It accesses a module built-in to Inferno, instead of a file. Built-in modules' functions are C code built into the system, not implemented in Limbo and compiled to a .dis file. Thus "$Sys" accesses a built-in module that provides Inferno system calls for Limbo. Other built-in modules include: Draw, Keyring, Loader, Math, Prefab, and Tk.

2.8.3 The import Statement



It is necessary to qualify module names with the appropriate module handle. To make Limbo code less verbose, you can use the import statement to bring the specified types and functions into the current scope. For example, using import would change the above code to look like:
	sys := load Sys Sys->PATH;
	print: import sys;
	print("Hello, World!\n");


The import statement conventionally follows the include statement or the handle declaration statement. This gives the imported data or function global scope within the module (accessible to all functions within the module). For example, we could change the Greet program to import the print function:
greetimport.b
 1 implement Command;
 2 include "sys.m";
 3 include "draw.m";
 4 sys: Sys;
 5 print: import sys;
 6 Command: module {
 7 	init: fn(ctxt: ref Draw->Context, argv: list of string);
 8 };
 9 init(ctxt: ref Draw->Context, argv: list of string) {
10 	sys = load Sys Sys->PATH;
11 	if (tl argv != nil)
12 		print("Hello, %s!\n", hd tl argv);
13 	else
14 		print("Hello, World!\n");
15 }


On Line 7, the import statement follows the Sys module handle declaration (Line 6). The function can then be accessed like a local function, without the module handle qualifier (Lines 16 and 18).

3 I/O



Input and output (I/O) functionality is an important aspect of virtually all Limbo programs.

Limbo I/O functions are provided through the Inferno Reference API. The Inferno Reference API is discussed in detail in the Inferno Reference Manual.

Basic Limbo I/O is contained in the System module (sys.m). These functions are like the basic unbuffered UNIX C functions. Most of the examples thus far have used the I/O functions in the Sys module.

Another module, the Buffered I/O module (bufio.m), provides buffered I/O fucntions that are like the ANSI C standard funtions.

Other modules in the Inferno Reference API provide more advanced and specialized input and output functions, such as the Device Pointer module (devpointer.m), Infrared Remote Control module (ir.m), and Lock module (lock.m). Limbo programs can make use of any combination of the functions provided by these modules.

3.1 Basic I/O



Basic I/O is the standard means of I/O in Limbo. There are eight basic functions in the Sys module are shown in Table 3-1.

3.2 File Descriptors



In Inferno files are identified by a file desciptor, or fd. A reference to a file descriptor is required as a parameter of the read, write, fprint, and stream functions. The file descriptor is obtained through the open, create, and fildes functions. These I/O functions return a reference to an ADT in the Sys module, called FD, that contains the integer-valued fd.

When a file is opened, a Sys->FD is allocated and returned. When the Sys->FD is deallocated, the file is closed. Limbo does not have an explicit close function. Inferno's garbage collector immediate and automatically deallocates an Sys->FD when all references have gone out of scope. If there are no other references to the Sys->FD, the file can be explicitly closed setting the reference to the Sys->FD to nil.

3.3 Standard I/O



There are three standard file descriptors available to every Limbo program: standard input, standard output, and standard error. The table below lists the numbers associated with these standard file descriptors.

3.3.1 Obtaining a Sys->FD For Standard I/O



The fildes function in the Sys module takes as an argument an integer file descriptor and creates a reference to a Sys->FD. This function is used to obtain a reference to a Sys->FD for the standard I/O file descriptors.

For example:
	stderr := sys->fildes(2);
This places a reference to a Sys->FD in the identifier stderr. Then to print to standard error, that reference is passed to the fprint function of the Sys module:
	sys->fprint(stderr, "error: %s\n", msg);

3.4 The open Function



The open function returns a reference to a Sys->FD that is required for the Sys module's other I/O functions.

The general format of a call to sys->open is:
fd := sys->open(" filename ", omode );

The filename parameter is a string containing the complete file specification. The omode paramter specifies the open mode. Table 3-3 lists open modes specified in the Sys module.

The mode parameter can be logically OR'd against two additional values for special file handling. Table 3-4 lists these values specified in the Sys module.

For example, the following opens for reading a file in the current directory named program.cfg:
	cfgfd := sys->open("program.cfg", Sys->OREAD);

3.5 The create Function



The create function, like the open function, returns a reference to a Sys->FD that is required for the Sys module's other I/O functions.

The general format of the create function call includes the same first two arguments of the open function, and an additional argument:
fd := create(" filename ", omode , permissions );

The additional argument for the create function specifies the access permissions that the file is created with. Inferno permissions are similar to Unix. There are nine bits of permission information associated with each file. These nine bits are divided into three sections: the first section specifies permissions for the file owner, the second is for the file group, and the third is for everyone else. Each section contains three bits that stand for read, write, and execute (or search for directories) permissions (0 is off, 1 is on).
Pretty picture of file permissions
File Permissions


A three-digit octal number is convenient for specifying permissions. In Limbo, octal numbers are given the 8r (``radix 8'') prefix. For example, 8r666 gives read and write permissions to the owner, group, and everyone.

Of course, the permissions of the directory where the file is created affect the overall permissions of the file. The permissions specified by create are AND'd with the directory permissions.

The following creates a file named file.tmp for reading and writing in the /tmp directory. It will be removed when it is closed, and the owner has read and write permissions.
	tmp := sys->create("/tmp/file.tmp", Sys->ORDWR | Sys->ORCLOSE, 8r600);

3.6 Read and Write



After an FD is obtained, you can read and write to the file according to the mode that was used to open it.

For example, to read from the program.cfg file opened above, you can use the read function .
	tmpbuf := array[] of byte;
	n = sys->read(cfgfd, tmpbuf, 80);
To write the bytes read to the file.tmp created above, you can use the write function:
	write(tmp, tmpbuf, 80);

3.7 Formatted Output



The function for writing to standard output is the print function in the System module. Like C's printf function, it provides formatted output through the use of fomat verbs.

For example:
	sys->print("Hello, %s!\n", hd (tl argv));
This uses the %s format verb to print the data contained in the head of the argv list (which is a string), in addition to printing the literal string. For example:
	Hello, Inferno!


To direct the output to a device other than standard output, use the fprint function. This requires an additional parameter, an open FD. For example, to print to standard error, use the fildes function to obtain:
	stderr := sys->fildes(2);	# get the FD for stderr

	...
	sys->fprint(stderr, "error: %s\n'', msg);

3.8 Format Verbs



The general form for fomat verbs is:
	%[flags]verb


The table below lists the format verbs available for the print function:

3.8.1 Flags



The flags for the format verb specify additional formatting control. The flags provide control for the minimum field width, precision, justification, and decimal and hexadecimal numbers.

Minimum Field Width



The minimum field width flag pads the output with spaces to ensure that it is at least the specified minimum length. If the string or number is longer than the minimum, it is still printed in full.

For example:
	num := 123;
	for (i := 1; i <= 5; i++) {
		sys->print("%8d %8d %8d %8d %8d\n",
			i, i*i, i*i*i, i*i*i*i, i*i*i*i*i);
	}
	sys->print("%d\n", num);
	sys->print("%10d\n\n", num);


This prints:
			1        1        1        1        1
			2        4        8       16       32
			3        9       27       81      243
			4       16       64      256     1024
			5       25      125      625     3125
	123
			123

Precision



The precision flag follows the minimum field width flag, if there is one. The precision is specified by a period followed by an integer. Its exact meaning depends on the data type.

If you use the precision flag with a real (floating-point) using the %f, %e or %E verbs, it specifies the number of decimal places printed. If you use the precision flag with a real using the %g or %G verbs, it specifies the number of significant digits.

For example:
	decnum := 2345.678901;
	sys->print("%.3f\n", decnum);


This prints:
	2345.679


If you use the precision flag with a string using the %s verb, it specifies the maximum field length. For example, %3.6 prints a string at least three but not more than six characters in length. If the string is longer than the maximum field, the string is truncated.

For example:
	str := "Hello, Inferno!";
	sys->print("%5.10s\n\n", str);


This prints:
	Hello, Inf
If you use the precision flag with an int using the %d verb, it specifies the minimum number of digits to be printed. If the number of digits is less than what is specified, leading zeros are added.

For example:
	num := 123;
	sys->print("%2.5d\n", num);


This prints:
	00123


Justification



By default, output is right-justified. You can force output to be left-justified using the justification flag, the minus sign (-). Placing the justification flag immediately after the %, prints the data left justified.

For example:
	num := 123;
	decnum := 2345.678901;
	sys->print("Right-justified: %5d\n", num);
	sys->print("Left-justified: %-5d\n", num);
	sys->print("$%9.2f\n\n", decnum);


This prints:
	Right-justified:   123
	Left-justified: 123
	$  2345.68

Decimal and Hexidecimal Numbers



If you use the pound sign (#) with the e, E, f, g, or G verbs prints a decimal point even if there are no decimal digits.

If you use the pound sign (#) with the x or X verbs, the hexadecimal number prints with the 0x or 0X prefix.

For example:
	num := 123;
	sys->print("%#d\n", num);
	sys->print("%#e\n", real num);
	sys->print("%x\n", num);
	sys->print("%#X\n", num);


This prints:
123
123.000000
7b
0X7B

3.9 Examples



The program below illustrates the use of format verbs and flags with sys->print: function:
include "sys.m";
include "draw.m";
sys: Sys;
Command: module {
	init: fn(ctxt: ref Draw->Context, argv: list of string);
};
init(ctxt: ref Draw->Context, argv: list of string) {
	sys = load Sys Sys->PATH;
	num := 123;
	decnum := 2345.678901;
	scinum := 0.0000987;
	str := "Hello, Inferno!";
	# field width
	for (i := 1; i <= 5; i++) {
		sys->print("%8d %8d %8d %8d %8d\n",
			i, i*i, i*i*i, i*i*i*i, i*i*i*i*i);
	}
	sys->print("%d\n", num);
	sys->print("%10d\n\n", num);
	
	# precision
	sys->print("%.3f\n", decnum);
	sys->print("%2.5d\n", num);
	sys->print("%5.10s\n\n", str);
	# scientific notation
	sys->print("%e\n", scinum);
	sys->print("%E\n", decnum);
	sys->print("%g\n", decnum);
	sys->print("%g\n\n", scinum);
	# justification
	sys->print("Right-justified: %5d\n", num);
	sys->print("Left-justified: %-5d\n", num);
	sys->print("$%9.2f\n\n", decnum);
	# Decimal and Hexadecimal
	sys->print("%#d\n", num);
	sys->print("%x\n", num);
	sys->print("%#X\n", num);
}


This program produces the following output:
		1        1        1        1        1
		2        4        8       16       32
		3        9       27       81      243
		4       16       64      256     1024
		5       25      125      625     3125
123
123
2345.679
00123
Hello, Inf
9.870000e-05
2.345679E+03
2345.678901
9.87e-05
Right-justified:   123
Left-justified: 123
$  2345.68
123.0
7b
0X7B


4 Threads



In Limbo, threads are built into the language and provide a powerful means of controlling program execution. A Limbo program consists of one or more processes each of which contains one or more threads. Limbo threads of execution that are preemptively scheduled by the system and may execute in parallel on a multi-processor machine. Threads are controlled by the Limbo run-time and execute cooperatively.

4.1 Scheduling



When a program creates a new thread, the virtual machine places it onto a run queue where all threads are executed in a round-robin manner. In general, Limbo threads execute in priority-less order. Each thread is a given a quantum (a specified number of instructions) to complete before it is moved from the virtual machine and placed on the thread ready queue. The ready queue is a linked list of threads that are waiting for execution within the virtual machine.

The virtual machine ensures that Limbo threads are safe from execution concurrency issues. Neither the Limbo program, nor the programmer, need to be concerned with re-entrant code.

4.2 Using Threads



Threads are easily created in a Limbo program. Each Limbo program starts as a single thread. The first thread of each program is created when the program starts; it begins execution at the function named init. The keyword spawn allows you to create a new independently scheduled thread of execution from a Limbo function.

4.3 The spawn Statement



The spawn keyword creates a thread that begins execution at the function supplied as its operand. For example:
	spawn func(1, 2);


This statement starts a thread at the function named func and passes it two arguments. The current thread continues execution at the statement following the spawn statement.

4.4 Example



The program threadx.b shown below illustrates Limbo threads:
 1 implement Command;
 2 include "sys.m";
 3 include "draw.m";
 4 sys: Sys;
 5 Command: module {
 6 	init: fn(ctxt: ref Draw->Context, argv: list of string);
 7 };
 8 init(ctxt: ref Draw->Context, argv: list of string) {
 9 	sys = load Sys Sys->PATH;
10 	for (i:=1; i<=5; i++) {
11 		spawn func(i);
12 		sys->print("%2d\n", i);
13 	}
14 }
15 func(n: int) {
16 	sys->print("%2.2d\n", n);
17 }


The init function, Lines 11 through 18, creates a for loop that repeats five times, spawning the function func each time and printing the current iteration number. The function func also prints the iteration number passed to it by init. (The format flags added to the verb %d, 2 and 2.2, only serve to make it easier to distinguish which thread printed the number.)

The output from this program may look like this:
divine$ threadx
1
1
2
2
3
4
5
3
4
divine$    5


The order of the output is indeterminate, depending on such external factors as system load, the number of processors, and system scheduling policy. If run this program multiple times, the order of the output would likely be different each time. The last line in the sample output shows that, in this case, the first thread, created when the program started in init, has exited and the prompt has returned before the last thread finishes.

The next section shows how threads can use channels to communicate, and synchronize their activity when required.

5 Channels



A Limbo channel is a language-level mechanism for passing typed-data between threads. A channel is somewhatlike an array; it itself is a type, and it has a type.

Channels are a common control model for Limbo programs: an executive process (e.g., init) starts other threads and communicates with them via channels.

5.1 Using Channels



Channels are somewhat like UNIX pipes. Limbo channels are simple to program. All that is required is that two (or more) threads be able to communicate on a common channel. One thread sends data on the channel and another receives it.

If there is no thread ready to receive, the send will block (suspend) until a receiver is ready. If a receiver is ready, but there is no thread ready to send, the receiver blocks until a sender is ready.

5.2 Declaring a Channel



The following statement declares a channel:
	c: chan of int;


This declares a channel named c that transports int values. Channels are a reference type, so there is no associated storage following the declaration. Its value is nil.

To initialize a channel and make it ready to send or receive data, you must use the assignment statement:
	c = chan of int;

5.3 Channel Operator



After a channel is declared and initialized, it can send and receive data of the type specified in its declaration. Using the channel operator, <-, a channel sends or receives data, depending on which side of the operator the channel identifier is on.

5.3.1 Send Operator



As a postfix operator (channel identifier on the left), the channel operator acts as a send operator. It transmits the result of the expression supplied as its right operand on the channel specified as its left operand. For example:
	c <-= 4;	# send int on 'c'

5.3.2 Receive Operator



As a prefix operator (channel identifier on the right), the channel operator acts as a receive operator. The channel receives the data supplied as the operand. The received value may then be manipulated by other operators. For example, it can be assigned to a variable or used as an operand in an expression:
	i := <-c;	# assign value received on c to i</ProgramText>
	func(<-c);	# pass value received on c to func</ProgramText>

5.4 Channel Selection



It is likely that in an application there are multiple threads each possibly sending and receiving on multiple channels.

5.4.1 The alt Statement



The alt statement selects statements to execute based on the readiness of channels. The alt statement is like a case statement for channels.

The qualifiers in the alt statement must be expressions containing the channel communication operator, <-.

If there is no channel ready to send or receive, and a default qualifer, *, is present, then the default is selected. If the default qualifier is not present, then the statement blocks until a channel is ready.

Each qualifer and the statements that follow up to the next qualifier form a block. Like case statements, each block breaks by default; no explicit break statement is needed.

Its syntax is similar to the case statement. For example:
	outchan := chan of string;
	inchan := chan of int;
	i := int;
	alt {
		i = <- inchan =>
			...
		outchan <- = "sent" =>
			...
	}


In this example, the selection is based on two channels, inchan and outchan. If inchan is ready to receive, it is selected. If outchan is ready to send, it is selected. If both are ready at the same time, it is indeterministic which one will be selected. The order of the qualifiers does not affect the selection.

5.5 Examples



To illustrate the use of channels, here are a three examples that use channels to communicate between threads.

5.6 Simple Synchronization



The first example uses channels for simple synchronization:
 1 implement Command;
 2 include "sys.m";
 3 include "draw.m";
 4 sys: Sys;
 5 print: import sys;
 6 sc: chan of int;
 7 rc: chan of int;
 8 Command: module {
 9 	init: fn(ctxt: ref Draw->Context, argv: list of string);
10 };
11 init(ctxt: ref Draw->Context, argv: list of string) {
12 	sys = load Sys Sys->PATH;
13 	sc = chan of int;
14 	rc = chan of int;
15 	for (i:=1; i<=5; i++) {
16 		spawn func();
17 		print("%2d\n", i);
18 		sc<- = i;
19 		<-rc;
20 	}
21 }
22 func() {
23 	i := <-sc;
24 	print("  %2d\n", i);
25 	rc<- = i;
26 }


The init thread initializes two channels that transport an integer. It then spawns a new thread with the function func. The new thread begins execution in func, where it blocks on the channel sc until a message arrives (Line 29). Meanwhile, the init thread continues to execute, printing the integer (Line 22) and sending the integer as a message on channel sc, before it blocks on the channel rc waiting for a return message (Line 24).

When func receives the message on channel sc, it continues to execute, printing the integer (Line 30) and sending the integer as a message on the channel rc, before it terminates.

When init receives the message on channel rc (it has blocked waiting for it on Line 24) it starts the next iteration of the for loop (Line 20) and the sequence starts over.

5.7 Communication With External Function



The next example is comprised of 3 files. The main program spawns a new thread with an external function (from a loaded module) and uses the same channel mechanism for communication between the threads.

File chanx2a.b contains the following main program:
implement Command;
include "sys.m";
include "draw.m";
include "chanx.m";
sys: Sys;
print: import sys;
cx: ChanX;
sc: chan of int;
rc: chan of int;
Command: module {
	init: fn(ctxt: ref Draw->Context, argv: list of string);
};
init(ctxt: ref Draw->Context, argv: list of string) {
	sys = load Sys Sys->PATH;
	cx = load ChanX "./chanx2b.dis";
	sc = chan of int;
	rc = chan of int;
	for (i:=1; i<=5; i++) {
		spawn cx->func(sc, rc);
		print("%2d\n", i);
		sc<- = i;
		func(<-rc);
	}
}
func(i: int) {
	print("%6d\n", i);
}


The file chanx2b.m, shown below, is the module interface file for chanx2b.b.
chanx2b.m
ChanX: module {
	func: fn(sc: chan of int, rc: chan of int);
};
chanx2b.b
implement ChanX;
include "sys.m";
include "draw.m";
include "chanx.m";
sys: Sys;
print: import sys;
func(sc: chan of int, rc: chan of int) {
	sys = load Sys Sys->PATH;
	i: int;
	i = <-sc;
	print("%4d\n", i);
	rc <- = i;
}

5.8 User Interface Events Example



The following example illustrates a common use of the alt statement. In the event model of the Limbo/Tk graphics environment, channels are used to send commands from graphic elements (widgets). Using the alt statement enables a program to respond to commands from several elements. (For more information about graphics programming in Limbo, see §XX, Graphics Programming).

Although Program Listing 3-13 is longer than most examples, the use of the alt statement starts at Line 50.
 1 implement WmAltX;
 2 include "sys.m";
 3 include "draw.m";
 4 include "tk.m";
 5 include	"wmlib.m";
 6 sys: Sys;
 7 draw: Draw;
 8 Display, Image: import draw;
 9 tk: Tk;
10 wmlib: Wmlib;
11 WmAltX: module {
12 	init: fn(ctxt: ref Draw->Context, argv: list of string);
13 };
14 win_cfg := array[] of {
15 	"frame .ft",
16 	"text .ft.t -yscrollcommand {.ft.s set} -width 40w
17 				-height 15h",
18 	"scrollbar .ft.s -command {.ft.t yview}",
19 	"focus .ft.t",
20 	"pack .ft.s -side right -fill y",
21 	"frame .fb.a -bd 2 -relief sunken",
22 	"pack .ft.t -side left -fill y",
23 	"button .fb.a.b -height 2h -text {Button 1}
24 				-command {send cmd 1}",
25 	"pack .fb.a.b -padx 1 -pady 1",
26 	"button .fb.c -height 2h -text {Button 2}
27 				-command {send cmd 2}",
28 	"button .fb.q -height 2h -text {Quit}
29 				-command {send cmd Q}",
30 	"frame .fb",
31 	"pack .fb.a .fb.c .fb.q -padx 5 -pady 5 -side left",
32 	"pack .ft .fb",
33 	"pack propagate . 0",
34 	"update",
35 };
36 init(ctxt: ref Draw->Context, argv: list of string) {
37 	sys = load Sys Sys->PATH;
38 	draw = load Draw Draw->PATH;
39 	tk = load Tk Tk->PATH;
40 	wmlib = load Wmlib Wmlib->PATH;
41 	wmlib->init();
42 	(win, menubut) := wmlib->titlebar(ctxt.screen,
43 				"-x 5 -y 5", "WmAltX", 0);
44 	cmd := chan of string;
45 	tk->namechan(win, cmd, "cmd");
46 	wmlib->tkcmds(win, win_cfg);
47 	for(;;) alt {
48 	menu := <-menubut =>
49 		if(menu[0] == 'e') {
50 			tk->cmd(win, ".ft.t insert end '"+
51 						"Close button pressed.\n");
52 			tk->cmd(win, "update");
53 			wmlib->dialog(win, "warning -fg red",
54 						``Close Button", "Close Button pressed!", 0,
55 						"Close"::nil);
56 			exit;
57 		}
58 		wmlib->titlectl(win, menu);
59 	s := <-cmd =>
60 		case s[0] {
61 		'1' =>
62 			tk->cmd(win, ".ft.t insert end '"+
63 						"Button 1 pressed.\n");
64 			tk->cmd(win, "update");
65 			break;
66 		'2' =>
67 			tk->cmd(win, ".ft.t insert end '"+
68 						"Button 2 pressed.\n");
69 			tk->cmd(win, "update");
70 			break;
71 		'Q' =>
72 			tk->cmd(win, ".ft.t insert end '"+
73 						"Quit button pressed.\n");
74 			tk->cmd(win, "update");
75 			wmlib->dialog(win, "info", "Quit Button",
76 						"Quit button pressed!", 0,
77 						"Quit"::nil);
78 			exit;
79 		}
80 	}
81 }
Line 50 starts the alt statement. This alternates between two channels: menubut and cmd. The menubut channel is used to notify the application of events from the titlebar, such as move and close. The cmd channel notifies the application of events from other widgets, such as the buttons at the bottom, Button 1, Button 2, and Quit.

6 Graphics Programming



Graphics are not built into the Limbo language. Graphics in Limbo are handled through the Inferno Reference API. Inferno graphics functions are packaged in three distinct interface modules, the Draw module, the Prefab module, and the Limbo/Tk modules.
*
The Draw module (draw.m) provides basic raster graphics and windowing functions. These functions provide primitives for graphics functions in the Prefab module (prefab.m) and the Limbo/Tk modules
*
The Prefab module (prefab.m) provides graphics functions specifically designed for use with interactive TV applications. It includes support for IR (infrared) remote control devices.
*
The Limbo/Tk modules (tk.m and wmlib.m) provides graphics functions for desktop-like GUI applications.


This section introduces each of these and gives three simple examples and basic explanation to provide a flavor of using each type of interface module. The examples are based on the simple console Greet program from Limbo Basics elsewhere in this volume.

For detailed description of the data and functions in the graphics modules, see the Inferno Reference Manual.

6.1 Draw Module



Inferno's Draw module, draw.m, provides basic graphics facilities, defining drawing contexts, images, character fonts, and rectangular geometric operations. The Limbo Prefab module and Limbo/Tk modules provide the higher level operations, such as windows and menu handling.

6.2 Concepts and Terminology



Since the Draw module provides primitive graphics, its functions and data are at a lower level than is typical of a graphics API. Hence, there are some concepts and terms that would be helpful to introduce first.

6.2.1 The Graphics Context



As discussed in Chapter 3, the graphics Context that is captured by a command line program through an argument to the init function is defined in the Draw module. The Context argument holds the references of the graphics resources.

Two important elements of Context are the display and screen types.

Display



The type display of type ref Draw->Display represents a physical display, corresponding to a single connection to a draw device (/dev/draw ). Besides the image of the display itself, the Display type also stores references to off-screen images , fonts, and so on. The contents of such images are stored in the display device, not in the client of the display, which affects how they are allocated and used.

Objects of type Display must be allocated by its member functions. If a Display object is created with a regular Limbo definition, it will not behave properly and may generate run-time errors.

Screen



The type screen of type ref Draw->Screen is used to manage a set of windows on an image, typically but not necessarily that of a display. Screens, and hence windows, may be built recursively upon windows for subwindowing or even on off-screen images.

Objects of type Screen must be allocated by its member functions. If a Screen object is created with a regular Limbo definition, it will not behave properly and may generate run-time errors.

6.2.2 Pixels



Images are defined on a rectangular region of an integer plane with a picture element, or pixel, at each grid point. Pixel values are integers with 1, 2, 4, or 8 bits per pixel, and all pixels in a given image have the same size, or depth. Some operations allow images with different depths to be combined, for example to do masking.

When an image is displayed, the value of each pixel determines the color of the display. For color displays, Inferno uses a fixed color map for each display depth, and the application is responsible for mapping its desired colors to the values available. There are functions to convert from red, green, blue triplets to pixel values.

6.2.3 Point



The type Point defines a coordinate position. The graphics plane is defined on an integer grid, with each (x, y) coordinate identifying the upper left corner of the corresponding pixel. The plane's origin, (0, 0), resides at the upper left corner of the screen; x and y coordinates increase to the right and down.

6.2.4 Rect



The type Rect defines a rectangular region of the plane. It comprises two Points, min and max, and specifies the region defined by pixels with coordinates greater than or equal to min and strictly less than max, in both x and y. This half-open property allows rectangles that share an edge to have equal coordinates on the edge.

6.2.5 Image



The type Image provides basic operations on groups of pixels. Through a few simple operations, the Image type provides the building blocks for Display, Screen, and Font. Objects of type Image must be allocated by its member functions. If an Image object is created with a regular Limbo definition, it will not behave properly and may generate run-time errors.

An image occupies a rectangle, Image.r, of the graphics plane. A second rectangle, Image.clipr, defines a clipping region for the image. Typically, the clipping rectangle is the same as the basic image, but they may differ. For example, the clipping region may be made smaller and centered on the basic image to define a protected border.

The pixel depth of an Image is stored as a logarithm called Image.ldepth. Pixels with 1, 2, 4, and 8 bits correspond to ldepth values 0, 1, 2, and 3.

An image may be marked for replication. When set, the boolean Image.repl causes the image to behave as if replicated across the entire integer plane, thus tiling the destination graphics area with copies of the source image. When replication is turned on, the clipping rectangle limits the extent of the replication and may even usefully be disjoint from Image.r.

The Image member functions provide facilities for drawing text and geometric objects, manipulating windows, and so on.

6.2.6 Font



A Font type defines which character image to draw for each character code value. Although all character drawing operations ultimately use the draw primitive on the underlying images, Fonts provide convenient and efficient management of the display text. Inferno uses the 16-bit Unicode character encoding, so Fonts are managed hierarchically to control their size and to make common subsets such as ASCII or Greek efficient in practice.

Objects of type Font must be allocated by its member functions. If a Font object is created with a regular Limbo definition, it will not behave properly and may generate run-time errors.

6.2.7 Pointer



The Pointer type conveys information for pointing devices, such as mice or trackballs.

6.2.8 Return Values



Most drawing operations operate asynchronously, so they have no error return. Functions that allocate objects return nil for failure. In such cases the system error string may be interrogated (such as by the %r print format) for more information.

6.2.9 Freeing Graphic Objects



There are no functions to free graphics objects. Instead Inferno's garbage collection frees them automatically. In Limbo, references can be eliminated by assigning nil to reference variables, returning from functions whose local variables hold references, and so on.

6.3 Draw Example



The following is the version of Greet program that simply illustrates the use of the Draw module:
drawgreet.b:
 1 implement DrawGreet;
 2 include "sys.m";
 3 include "draw.m";
 4 sys: Sys;
 5 draw: Draw;
 6 Display, Font, Rect, Point, Image, Screen: import draw;
 7 display: ref Display;
 8 disp, img: ref Image;
 9 font: Font;
10 DrawGreet : module {
11 	init: fn(ctxt: ref Draw->Context, argv: list of string);
12 };
13 init(ctxt: ref Draw->Context, argv: list of string) {
14 	sys = load Sys Sys->PATH;
15 	draw = load Draw Draw->PATH;
16 	display = Display.allocate(nil);
17 	disp = display.image;
18 	spawn refresh(display);
19 	white := display.color(Draw->White);	# color predefined by Draw
20 	yellow := display.color(Draw->Yellow);
21 	black := display.color(Draw->Black);
22 	ones := display.ones;			# pixel mask
23 	font = Font.open(display, "/fonts/lucidasans/unicode.9x24.font");
24 	if (tl argv != nil)
25 		str := "Hello, " + (hd tl argv) + "!";
26 	else
27 		str = "Hello, World!";
28 	strctr := (font.width(str) / 2);
29 	img := display.open("/icons/logon.bit");
30 	imgxctr := img.r.max.x / 2;
31 	dispctr := disp.r.max.x / 2;
32 	disp.draw(disp.r, white, ones, (0,0));
33 	disp.draw(disp.r, img, ones, (-(dispctr-imgxctr),-10));
34 	disp.text(((dispctr - strctr), img.r.max.y+20), black,
35 				(0,0), font, str);
36 	sys->sleep(10000);
37 	disp.draw(disp.r, white, ones, (0,0));
38 }
39 refresh(display: ref Display) {
40 	display.startrefresh();
41 }


Line 7 declares the handle to the Draw module and Line 8 imports the Draw data structures into the current scope. Of particular note are the Display, Image, and Font types.

The Display type represents the physical display represented by the draw device mounted in /dev/draw. Line 21 allocates this display using the Display.allocate function (the default is specified by the empty string argument nil).

The Image type provides the basic operations for a group of pixels and the building blocks for higher-level objects such as pictures (graphic image files), windows, and fonts. Line 22 assigns the visible contents (the Draw canvas) to disp. Line 37 opens a bitmap image, logon.bit, in the /icons/ directory and assigns it to img.

The Font type defines the appearance of characters drawn with the Image.font function. Fonts are read from files that describe their size, style, and the portion of the Unicode character set they represent. Line 27 opens a font file, unicode.9x24.font, in the /fonts/lucidasans/ directory and assigns it to font.

Line 23 refreshes the display just allocated using the recommended technique of spawning a local function, refresh (Lines 49 through 51) that calls the Display.startrefresh function in the Draw module.

Line 42 draws the display area (the rectangle specified by disp.r) using the color white. Line 43 draws the bitmap image, img, on the display. Line 44 draws the text in str using font. Line 45 pauses before the display is cleared, Line 46.

After compiling this program, you could run it from the Inferno console (not from a shell under Window Manager):
	inferno% drawgreet Inferno
which produces the image shown in Figure XX.
<IMAGE xml:link="simple" href="img/hellodraw.gif">


Figure XX. Output of drawgreet.b

6.4 Prefab Module



The Prefab module, prefab.m, contains components for building higher-level graphics objects. It is intended to work on devices that are not necessarily traditional computers with keyboards and mice. It is especially designed for Interactive Television (ITV, or set-top box) applications using infrared remote controls.

Using the Draw module's operations for simple text and images, the Prefab module can group individual items, treat those groups as units, and then activate the items on command.

6.5 Concepts and Terminology



The Prefab module, as a graphics ``toolkit'', provides higher-level graphics capabilities compared to the Draw module. There are still concepts and terms that should be introduced first.

6.5.1 Compound



The type Compound defines boxes drawn on the screen. Each appears in a new window, Compound.image, and holds a (possibly nil) title and contents. It occupies the space on the screen defined by Compound.r. Allocating a Compound creates a window but does not draw it.

After the Compound is built, Compound.draw must be called to make it visible. Compounds have a border around them, drawn in Style.edgecolor and contain, from top-to-bottom:
*
the title (if any)
*
a horizontal line (if there is a title)
*
the contents


Applications should allocate a Compound only through the appropriate member functions. Moreover, except where indicated, applications should not modify the data members directly. Although the type definitions make data members visible, the members should be treated as read-only data.

6.5.2 Elements



The Prefab module defines elements as the graphic components. There are six types of Prefab elements, as described in Table 3-6.

The objects on the screen are of type Compound, each of which occupies a unique window on the display and contains objects of type Element. An Element may be a single object or a list of Elements that can be used to build structured components.

Applications should allocate an Element only through the appropriate member functions. Moreover, except where indicated, applications should not modify the data members directly. Although the type definitions make data members visible, the members should be treated as read-only data.

6.5.3 Environ



The Environ type specifies the screen and style types for the element.

The screen, of type ref Draw->Screen, specifies where the elements are displayed.

The style, of type Style, specifies how the elements are drawn.

6.5.4 Style



The Style type collects the font and color information for an application or a set of items within an application. Except when using Layout, the members of Style are the only way to control the appearance of Prefab elements.

The color members, elemcolor, edgecolor, titlecolor, textcolor and highlightcolor typically refer to a literal color (a single replicated pixel of color). They are of type ref Draw->Image, so they can be any image.

Styles are allocated by regular Limbo definitions. There are no allocation functions. All the members of a Style must be defined. Although it will not cause errors to modify the members of a Style after it has been created and passed to a Prefab function, the results may be unpredictable.

6.5.5 Layout



The Layout type defines a more general form of text and image display. It provides fine control over the font and color in which to display text and the inclusion of images as textual elements. It allows setting of the tag for each component of the resulting element or list of elements.

6.6 Prefab Example



The following is the version of Greet program that simply illustrates the use of the Prefab module:
prefabgreet.b
 1 implement PrefabGreet;
 2 include "sys.m";
 3 include "draw.m";
 4 include "prefab.m";
 5 sys: Sys;
 6 draw: Draw;
 7 Display, Font, Rect, Point, Image, Screen: import draw;
 8 prefab: Prefab;
 9 Style, Element, Compound, Environ: import prefab;
10 PrefabGreet : module {
11 	init: fn(ctxt: ref Draw->Context, argv: list of string);
12 };
13 init(ctxt: ref Draw->Context, argv: list of string) {
14 	sys = load Sys Sys->PATH;
15 	draw = load Draw Draw->PATH;
16 	prefab = load Prefab Prefab->PATH;
17 	display := Display.allocate(nil);
18 	disp := display.image;
19 	spawn refresh(display);
20 	white := display.color(Draw->White);
21 	yellow := display.color(Draw->Yellow);
22 	black := display.color(Draw->Black);
23 	grey := display.rgb(160,160,160);
24 	ones := display.ones;
25 	screen := Screen.allocate(disp, white, 1);
26 	textfont := Font.open(display, "/fonts/lucidasans/unicode.13.font");
27 	titlefont := Font.open(display, "/fonts/lucidasans/italiclatin1.10.font");
28 	win_style := ref Style(
29 		titlefont,			# title font
30 		textfont,			# text font
31 		grey,				# element color
32 		black,				# edge color
33 		yellow,				# title color	
34 		black,				# text color
35 		white);				# highlight color
36 	win := ref Environ(screen, win_style);
37 	if ((tl argv) != nil)
38 	    msg := "Hello, " + (hd(tl argv)) + "!";
39 	else
40 		msg = "Hello, World!";
41 	icon := display.open("/icons/lucent.bit");
42 	dy := (icon.r.dy()-textfont.height)/2;
43 	wintitle := Element.text(win, "Inferno Prefab Example",
44 				((0,0),(0,0)), Prefab->ETitle);
45 	ie := Element.icon(win, icon.r, icon, ones);
46 	te := Element.text(win, msg, ((0,dy),(0,dy)),
47 				Prefab->EText);
48 	se:= Element.separator(win, ((0,0), (10,0)),
49 				display.zeros, display.zeros);
50 	le := Element.elist(win, ie, Prefab->EHorizontal);
51 	le.append(se);				# add space between icon and text
52 	le.append(te);
53 	le.append(se);				# add space between text and border
54 	le.adjust(Prefab->Adjpack, Prefab->Adjleft);
55 	c:= Compound.box(win, (20,20), wintitle, le);
56 	c.draw();
57 	sys->sleep(10000);			# Prefab elements are gc'd
58 }
59 refresh(display: ref Display) {
60 	display.startrefresh();
61 }


Comparing this example to the Draw example, you see that much of the initialization code is essentially the same.

In addition to the the Display, Image, and Font types, this example uses a Screen object. Line 30 creates a new Screen object and stores it in screen. This becomes the foundation upon which elements, such as windows, are drawn.

Lines 34 through 41 defines the font and color information for the window that will be displayed.

Line 43 creates the window environment, based on the screen and style that have been defined.

Line 52 defines the title of the window. Lines 53 and 54 define the icon and text that will be displayed. Line 55 defines the space that will be used to pad the elements.

Line 56 sets layout of the elements to be a horizontal list. Lines 57 through 59 add the elements to the list in the appropriate order. Line 60 formats the elements.

Line 62 defines the box, or the window, that will be drawn. It contains the title and contents. Line 63 actually draws the window.

After compiling this program, you could run it from the Inferno console:
	inferno% prefabgreet Inferno
which produces the display shown in Figure XX.
<IMAGE xml:link="simple" href="img/helloprefab.gif">


Figure XX. Output of prefabgreet.b

6.7 Limbo/Tk Modules



The Limbo/Tk modules contain common GUI components, similar to theTk toolkit defined by John Ousterhout, such as menus, buttons, and other widgets. It is used to construct graphical user interfaces without directly using the Draw module primitives.

6.8 Concepts and Terminology



Those that are familiar with Ousterhout's Tcl and the Tk Toolkit, commonly Tcl/Tk , will find Limbo/Tk to be very similar. Limbo/Tk does not, however, implement all the features of Tk 4.0. For more information about the differences between Limbo/Tk and Tk 4.0, see the Inferno Reference Manual.

Importantly, though, for Limbo/Tk applications, Limbo is the language used to create and control Tk components (widgets), not Tcl.

6.8.1 The toplevel Function



The toplevel function creates a window. It returns the Toplevel type that can be passed to other Limbo/Tk functions.

6.8.2 The cmd Function



The cmd function receives the Limbo/Tk commands as strings to create and control Tk graphic objects.

The command string contains a command and its arguments. These look like a very simple version of Tcl. See the Inferno Reference Manual for more information about the Limbo/Tk commands.

6.8.3 The namechan Function



The namechan function of the Limbo/Tk module is used to set up communication between the Limbo/Tk widgets and Limbo programs. The send command results in a string being sent on a Limbo channel. The namechan function is used to associate a Limbo chan of string with a name that can be used inside Limbo/Tk.

6.8.4 Wmlib



The standard Inferno distribution includes the window manager prototype, wm, and the Wmlib module (wmlib.m ). The Wmlib module simplifies both the processing of Limbo/Tk commands and the construction of windowed applications.

See the Inferno User's Guide for more information about wm. See the Inferno Reference Manual for more information about the Wmlib module.

6.9 Limbo/Tk Example



The following is the version of Greet program that simply illustrates the use of the Limbo/Tk modules:
tkgreet.b:
 1 implement TkGreet;
 2 include "sys.m";
 3 include "draw.m";
 4 include "tk.m";
 5 include "wmlib.m";
 6 sys: Sys;
 7 tk: Tk;
 8 wmlib: Wmlib;
 9 msg: string;
10 TkGreet : module {
11 	init: fn(ctxt: ref Draw->Context, argv: list of string);
12 };
13 init(ctxt: ref Draw->Context, argv: list of string) {
14 	sys = load Sys Sys->PATH;
15 	tk = load Tk Tk->PATH;
16 	wmlib = load Wmlib Wmlib->PATH;
17 	wmlib->init();
18 	if (tl argv != nil)
19 		msg = "Hello, " + (hd tl argv) + "!";
20 	else
21 		msg = "Hello, World!";
22 	win_cfg := array [] of {
23 		"label .i -bitmap @/icons/logon.bit",
24 		"label .t -text {" + msg + "\n" + "}",
25 		"focus .t",
26 		"button .b -text {OK} -width 10h
27 					-command {send cmd O}",
28 		"pack .i .t .b -padx 5 -pady 5",
29 		"pack propagate . 0",
30 		"update",
31 	};
32 	(title, menubut) := wmlib->titlebar(ctxt.screen,
33 				"-x 50 -y 25", "Limbo/Tk Example", wmlib->Hide);
34 	cmd := chan of string;
35 	tk->namechan(title, cmd, "cmd");
36 	wmlib->tkcmds(title, win_cfg);
37 	for(;;) alt {
38 	click := <- menubut =>
39 		if (click[0] == 'e')
40 			return;
41 		wmlib->titlectl(title, click);
42 		if (click[0] == 't')
43 			tk->cmd(title, "focus .t");
44 	s := <- cmd =>
45 		case s[0] {
46 			'O' => exit;
47 		}
48 	}
49 }


Line 21 initializes the display in preparation for drawing to it.

Lines 28 through 36 define the window configuration that will be displayed. This is an array of string elements that resemble Tcl commands. Creating the array is a convenient way to compile the widgets that make up a window, or some portion of the display.

Line 38 defines the text and buttons that display in the titlebar, as well as the starting coordinates (as a command string).

The Wmlib.titlebar function returns a tuple that contains a reference to the top-level widget and a channel that is used to notify the program of mouse events on the titlebar.

Lines 40 and 41 set up the channel that is used to communicate between the widgets and the program. This allows Limbo programs to receive event notification.

Line 43 creates the window using the array of string that defined the window as the commands.

Lines 45 through 56 is an infinite loop that processes widget events. The alt statement alternates between two channels: menubut for titlebar button events, and cmd for commands from other widgets (in this case, the OK button).

After compiling this program, you could run it from the Inferno shell in the Window Manager (wm):
	inferno% tkgreet Inferno
which produces the display shown in Figure XX.
<IMAGE xml:link="simple" href="img/hellotk.gif">


Figure XX. Output of tkgreet.b


Portions copyright © 1995-1999 Lucent Technologies Inc. All rights reserved.
Portions copyright © 2000 Vita Nuova Holdings Limited. All rights reserved.