Limbo Basics

Inferno Business Unit
Lucent Technologies Inc
Revised April 2000, Vita Nuova Limited

Getting Started



This chapter introduces Inferno¿s programming language, Limbo, particularly for those new to Limbo.

This section dissects a simple Limbo program that illustrates the basics of every Limbo program. That is followed by the Language Basics section which delves into the basics of the Limbo language itself. It covers the primary aspects of the language and includes many Limbo program samples.

For a formal Limbo language definition, see Appendix A, The Limbo Language Definition.

1 A Limbo Program



To illustrate the Limbo language, here is a simple example of a Limbo program. This is a simple greeting program (a twist on the venerable ``Hello, World!''). It illustrates the key parts of a basic Limbo program.

Following the line-by-line program description, use Inferno¿s Limbo compiler to compile the example and then run the program from an Inferno shell (command line).
<InfernoProgramCaption>
greet.b
<IMAGE xml:link="simple" href="limbo_basics-2.jpg" show="embed" actuate="auto"/>
 1 implement Command;
 2 
 3 include "sys.m";
 4 	sys: Sys;
 5 include "draw.m";
 6 
 7 Command: module
 8 {
 9 	init: fn(nil: ref Draw->Context, argv: list of string);
10 };
11 
12 init(nil: ref Draw->Context, argv: list of string)
13 {
14 	sys = load Sys Sys->PATH;
15 	# check for command-line arguments
16 	if(tl argv != nil)
17 		sys->print("Hello, %s!\n", hd tl argv);
18 	else
19 		sys->print("Hello, World!\n");
20 }
(The line numbers are for reference only.)

Line 1 says that this file is the implementation of the Command module. In this simple example, as is common with simple Limbo programs, the interface and implementation are in the same source file. The file name does not need to match the module name or the implementation name, although conventionally they do. In this example, implementation of Command is a special interface for modules (programs) launched at the Inferno shell.

Lines 3 and 5 include the interface declarations of two modules that the module uses. Line 3 includes the interface to the System Module (sys.m), and Line 5 includes the interface to the Draw Module (draw.m). Limbo does not have a preprocessor, as does C. Therefore, include is a keyword, not a preprocessor directive. It is analogous to C¿s #include directive, however. In Limbo, the file name following the include keyword is always enclosed in double quotes ("mod.m").

Line 4 declares the variable sys to be of type Sys (Limbo is case-sensitive). The Sys type is a module type , as declared in the sys.m file:
Sys: module
{
	...
};


The variable sys is declared to be a handle to the functions and data of the Sys module. At this point, however, its value is nil.

Lines 7 to 10 is the declaration of the Command module that we stated was implemented in this file in Line 1. One function, init, is declared for this module. This function has two arguments, a ref Draw->Context and a list of string.

The init function and the two arguments are required of all programs that are invoked by the Inferno shell command line. It is somewhat analogous to the main function in C-it specifies where execution starts in the module and captures the graphics context and command-line arguments, if any.

The ref Draw->Context is used to grab the context of the display and is defined in draw.m, hence the reason that file was included (Line 5). This argument is required even if the program does not do any graphics. If that is the case, as with our example, the argument can be named nil.

The list of string is a list of the arguments passed to this module via the command line. Conventionally this is named argv. If the program does not expect any arguments passed from the command line, it can be named nil.

Lines 12 to 20 contain the definition of the init function, or the statements that will be executed.

Earlier, in Line 4, we mentioned that the initial value of sys was nil. In Line 14, via the load keyword , we create a reference to the Sys module implementation, assigned to sys. This connects the program with the Sys library module and gives the program access to the functions and data implemented in the Sys module.

Line 15 is a comment. Comments in Limbo start with # and extend to the end of the line. Multi-line comments require a # at the beginning of each line.

Lines 16 to 19 print the message using the command line argument passed to init via argv. The second argument of the init function is a list of string. Lists in Limbo are a group of sequential, ordered elements. You move through the elements of a list using the hd (head) and tl (tail) operators. The first element, the head of the list, of argv is the program name. Since we are only interested in the remainder of the list, the tail, we check to see if this is nil, which is a keyword that means it contains nothing. If it is not nil (has some value) then we print the message and the head of the tail (the second element in the list). If there are no arguments, then we print the Hello, World! message.

2 Compiling a Limbo Program



At the Inferno shell prompt, invoke the Limbo compiler with the limbo command. For example:
inferno$ limbo greet.b
inferno$


If there are no errors, the Limbo compiler creates the executable file, called a Dis file. The compiler gives the file the .dis extension , for example greet.dis.

See Writing Limbo Appliations in this volume for more details about Limbo file name conventions and compiling Limbo programs. Also, see the Inferno Reference Manual manual page entry limbo(1) for more information about the compiler options.

3 Running a Limbo Program



To run the program, simply type the name of the executable file, excluding the .dis extension, at the Inferno shell prompt. For example:
inferno$ greet Reader
Hello, Reader!
inferno$


Any command line arguments, in the above example Reader, are passed by Inferno to the init function as elements in the list of string parameter. The program name itself is passed as the first element in the list of string parameter.

4 Language Basics



This section covers:
*
Keywords
*
Operators
*
DataTypes
*
ProgramControl
*
Functions


For a formal Limbo language definition, see The Limbo Language Definition.

5 Keywords



Limbo includes a small, yet complete, set of reserved words. The following identifiers are reserved for use as keywords and may not be used otherwise:
adt       do        len       self
alt       else      list      spawn
array     exit      load      string
big       fn        module    tagof
break     for       nil       tl
byte      hd        of        to
case      if        or        type
chan      implement pick      while
con       import    real
continue  include   ref
cyclic    int       return

6 Operators



Limbo includes several built-in operators. Limbo defines 3 classes of operators: unary, binary, and assignment.

6.1 Unary Operators



Table 1 lists Limbo¿s unary operators. Table 1. Unary operators

6.2 Binary Operators



Table 2 lists Limbo¿s binary operators, in order of precedence. Table 2. Binary operators

6.3 Assignment and Declaration Operators



Table 3 lists Limbo¿s assignment and declaration operators. Table 3. Assignment Operators

7 Data Types

There are several built-in data types in Limbo, in two categories:

*
Primitive types: byte, integer, big, real, and string
*
Reference types: array, list, and tuple


In addition to these types, you can define your own type through an abstract data type, an ADT. These can be instantiated as value or reference types.

7.1 Primitive Types

The primitive types in Limbo have fixed definitions, that is, they are of the same size for all platforms. This is a requirement for portability. Table 4 shows these data types, their range, and meaning. Table 4. Primitive data types

7.1.1 Strings

String constants in Limbo, enclosed in double quotes, represent text. For example:

s := "Inferno";



This declares s to be a string and assigns it the value Inferno.

Inferno does not use the ASCII character set, but rather the Unicode character set, so a string type in Limbo is a vector (array) of Unicode characters. Each Unicode character is 16 bits.

There is no special ``character'' type in Limbo. A single character is enclosed in single quotes and is represented by its integer value. For example:
c := 'I';


This statement delcares c to be the integer value of the letter I, which is 73.

You can extract a range from a string, called a slice. See Array Slices later in this section for more information.

7.1.2 Constants



Identifiers that have constant values of primitive type can be declared using the con keyword .

For example:

	data: con "/data/user.dat";	# string constant
	block: con 512;			# int constant
	pi: con 3.1415926535897932;	# real constant

7.1.2.1 Constant Enumerations



You can use the special iota value with the con keyword to declare a constant value one greater than the previous value to a group of identifiers. It is similar to enumerations in C.

For example:
	Sun, Mon, Tue, Wed, Thu, Fri, Sat: con iota;


This statement declares Sun to be a constant value of 0, Mon to be 1, Tue to be 2, and so on.

You can use other operators with iota to change the starting value and increment values. The most commonly used operators are addition (+), multiplication (*), and bitwise shift (<< and >>) operators.

For example:
	Ten, Eleven, Twelve: con iota+10;


This sets the starting value to 10 and increases the subsequent values by one.

You can use the multiplication operator (*) to change the increment value. For example:
	Zero, Five, Ten, Fifteen: con iota*5;


You can also use the bitwise shift operators. For example:

	Two, Four, Eight, Sixteen, ThirtyTwo: con 2<<iota;


There are many variations that can be produced using a combination of operators.

7.2 Reference Types



Limbo reference types are similar to pointers in other languages, such as C or C++.

The primary difference between Limbo reference types and pointers is that Limbo does not allow mathematical manipulation of references. This provides for ``pointer safety'' within Limbo, since a reference can never point to an illegal address. It can have a nil value, however.

7.2.1 A nil Value



Reference types can be assigned to the special value represented by the keyword nil. This removes the reference and its associated storage. If it is the last reference to the data, its resources are automatically and immediately reclaimed by the garbage collector .

At declaration, reference types do not have any associated storage; their value is nil. A value must be assigned to it before the reference type can be used.

7.2.2 Arrays

Arrays are made up of ordered, indexed, homogenous data. The index begins at 0.

<IMAGE xml:link="simple" href="limbo_basics-5.jpg" show="embed" actuate="auto"/>
<FigCaption>
<IMAGE xml:link="simple" href="limbo_basics-6.jpg" show="embed" actuate="auto"/>
Array Elements



A basic array declaration looks like this:
	buf : array of byte;


This declares the variable buf to be of type array of byte.

The constructor to allocate the space for the array looks like this:
	array[80] of byte


This expression creates a new byte array with 80 elements. To assign the storage of this array to a variable, use the assignment operator:
	buf = array[80] of byte;


Arrays can be declared and, if at the top level, initialized to 0 (zero), in one statement using the combined := (colon equals) operator.

	buf := array[80] of byte;


The following prints a number and its square:
	count := array[5] of int;
	i := 0;
	do {
		count[i] = i;
		sys->print("%dt%d\n", i, count[i]*i++);
	} while(i < 5);


The output looks like this:
	0		0
	1		1
	2		4
	3		9
	4		16

7.2.2.1 Array Slices

A slice is a consecutive range of elements within an array or string (in Limbo, strings are arrays of Unicode characters). The range is specified as an index within brackets. The range specifies a start value and an end value separated by a colon. It is inclusive of the start value and exclusive of the end value.



For example:

	s := "Inferno";
<IMAGE xml:link="simple" href="limbo_basics-7.jpg" show="embed" actuate="auto"/>
<FigCaption>
<IMAGE xml:link="simple" href="limbo_basics-6.jpg" show="embed" actuate="auto"/>
Slice Elements


A slice of this string, s, from the 2nd to the 6th element:
	s2 := s[2:6];


The value of s2 is the string fern.

7.2.3 Lists

Lists are made up of ordered, non-indexed, homogenous elements. The first element in the list is the head, the remaining elements make up the tail.

<IMAGE xml:link="simple" href="limbo_basics-8.jpg" show="embed" actuate="auto"/>
<FigCaption>
<IMAGE xml:link="simple" href="limbo_basics-6.jpg" show="embed" actuate="auto"/>
List Elements



For example:

	line: list of string;


This declares the identifier line to be of type list of string. The elements in line are string types.

List elements are accessed, modified, and manipulated using list operators. Table 5 defines the list operators. Table 5. List operators

7.2.3.1 List constructor (::)



To construct a list, use ::, the list constructor operator, such as:
	line = "First" :: line;


This is similar to pushing an element on to a stack. The list constructor is right associative. The right operand is a single element and must have the same type as the elements of the list. The list identifier is the left operand.

An alternative constructor list of is similar to the array constructor array of. For example:
	triple := list of {1, 2, 3};
which creates a list of the given values.

7.2.3.2 List head (hd)



The list head is a single element and has the same type as the elements of the list. To extract the head of the list, use the hd operator. This does not create a new list or change the original list.

For example, using the line list created above:
	sys->print("The list head is: %s", hd line);


This prints:
	The list head is: First

7.2.3.3 List tail (tl)



The list tail is itself a list of the same type as the original list. Using the tl operator and assigning its value back to the original list removes the first element from the list, such as:
	line = tl line;


A new list can be created by assigning the tail of one list to another, such as:
	args := tl line;

7.2.4 Tuples



A tuple is a parenthesized, sequential list of data types handled as a single object.

For example:

	retcode := (0, "Success");


This creates a tuple, retcode, that stores an integer and a string.

A tuple is unpacked through assignment. For example:
	err: int;
	msg: string;
	(err, msg) = retcode;


This assigns 0 to err and the string Success to msg.

Using declare-and-unpack shorthand, the same unpacking could be done in one statement:
	(err, msg) := retcode;


This declares err and msg and assigns them the values from retcode.

Use the nil keyword to ignore part of a tuple. For example:

	(err, nil) = retcode;


This assigns the integer value from retcode, but ignores the string value.

7.3 Abstract Data Types (ADT)



In Limbo an abstract data type, ADT, is a user-defined type that encapsulates data and functions into a named type. It is similar to a C struct, but can include functions, like a C++ class. Like Limbo modules, however, an ADT is final and does not have inheritence or polymorphism.

7.3.1 Declaring an ADT



An ADT declaration has the general form:
identifier: adt {
adt-member-list
};

ADT member declarations are much the same as data and function declarations elsewhere in a Limbo program.

For example, the following is a declaration of a simple Inventory ADT:
Inventory: adt {
	id: string;
	onhand: int;
	cost: real;
	value: fn(item: Inventory): real;
};


This declaration contains three data members and one member function. Following the declaration, Inventory is a type, like int or real. Typically, ADT declarations are in a module file (.m).

ADT member function definitions typically are in the implemenation file (.b). ADT member function definitions are the same as regular Limbo function defintions, with the addition of the ADT qualifier in the name using the dot operator (.).

For example:
Inventory.value(item: Inventory): real
{
	return real(item.onhand) * item.cost;
}

7.3.2 Instantiating an ADT



Instantiating an ADT is declaring a variable to be of the ADT type. For example:
	part1: Inventory;


This declares part1 to be of type Inventory.

7.3.3 Accessing ADT Members



The members of the ADT are accessed using the dot notation ( .) and can be treated like any other variable or function.

For example:
	part1.id = "Widget";
	part1.onhand = 250;
	part1.cost = 4.23;


This assigns values to the id, onhand and cost members of part1.

Calls to ADT member functions follow a similar pattern, for example:
	sys->print("Value On Hand: $%5.2f\n", part1.value(part1));


Using the values assigned above, this statement prints:
	Value On Hand: $1057.50


In this example, in order for the member function to operate on the member data, the instance of the ADT must be explicitly passed as a argument to the function. Passing of the ADT instance as an implicit argument can be done using the keyword self.

7.3.4 Implicit Argument: self



Changing the declaration of the Inventory member function to use the keyword self passes an instance of the ADT as an implicit argument. For example, note the addition of the keyword self to the value function declaration and definition:
	Inventory: adt {
		id: string;
		onhand: int;
		cost: real;
		value: fn(item: self Inventory): real;
	};
	Inventory.value(item: self Inventory): real
	{
		return real(item.onhand) * item.cost;
	}


The addition of self to the member function declarations and definitions enables the calling of the member functions without explicitly passing the ADT instance. The following reflects the change to the statement that calls this function:
	sys->print("Value On Hand: $%5.2f\n", part1.value());


Using the values assigned earlier, the statement prints:
	Value On Hand: $1057.50


The keyword self enables similar implicit argument functionality as the this keyword used in C++ and Java.

7.3.5 Ref ADT



As stated earlier, an ADT can be instantiated as a value type or a reference type. The ADTs that we have seen thus far have been instantiated as value ADTs. If an ADT is to be a reference type, called a ref ADT, it must be instantiated with the ref keyword.

For example, to instantiate the Inventory ADT as a reference type:

	part2:= ref Inventory;


This creates a new Inventory and places a reference to it in part2. The part2 instance can be used like a regular ADT. For example, assigning values to the member data is exactly the same:
	part2.id = "Whatsit";
	part2.onhand = 1000;
	part2.cost = 0.17;


It is important to note that part2 has a different type than part. This means that part2 cannot be assigned to an Inventory variable or passed directly to member functions of the current Inventory ADT. For example, the following statements would generate compile-time errors:
	part2 = part1;	# type clash error, won¿t compile
	part2.value();	# argument type mismatch, won¿t compile


To access a value from a ref ADT, use the asterisk (*) prefix operator:
	part1 = *part2;		# correct
	(*part2).value();	# correct


ADT member functions can accept ref ADT types. This requires the addition of the ref keyword to the member function declaration:
	clear: fn(item: self ref Inventory);


The member function definition looks like this:
	Inventory.clear(item: self ref Inventory)
	{
		item.onhand = 0;
	}

7.3.6 Ref ADT vs. Value ADT



The primary advantage of ref ADT is that it can be passed between functions without copying, making it more efficient, especially for a large ADT. Member functions that accept a ref ADT can overwrite their argument¿s contents. This cannot be done by member functions receiving a value ADT.

However, the interface to a ref ADT can become more obscure, primarily with the issue of being able to overwrite values. The interface to a value ADT is more straightfoward, making it more natural for most circumstances.

7.3.7 Pick ADT



A Limbo pick ADT is similar to a union in C. It enables separate instances of an ADT to hold data elements of different types. In this way, a single ADT declaration can instantiate ADTs of different types. Pick ADTs can have common data and function members and data members that are specific to the ADT instance.

There are two parts to a pick ADT: the pick ADT declaration, and the pick statement where you access the elements in the pick ADT.

7.3.7.1 The pick Declaration



The general form of the pick ADT declaration is:
	identifier: adt {
		...
		pick {
		tag-identifier =>
			declaration;
			...
		tag-identifier =>
			declaration;
			...
		}
	};


Each variant has a tag-identifier, local to the adt, which introduces a set of declarations. For example:
	Apick: adt {
		str: string;
		pick {
		String =>
			val: string;	# string element
		Int =>
			val: int;	# int element
		Real =>
			val: real;	# real element
		Nothing =>
			;		# no elements
		}
	};


The common member of this ADT is str of type string. The pick elements are String, Int, and Real. These specify three variations of the Apick ADT.

If a pick ADT has common data members, the pick block must be the last data declaration in the adt. Member functions can follow the pick block, however.

A pick ADT must be instantiated as a ref ADT. The specific element of the pick must be specified. For example:
	r := ref Apick.Real;


When you assign values to the data members of the ADT, use the variables declared via the pick structure:
	r.str = "Pi";
	r.val = 3.141593;


You can also declare and initialize an instance of the ADT. For example:
	s := ref Apick.String("Greeting", "Hello, World!");

7.3.7.2 The tagof Operator



The tagof operator returns the index of the pick ADT element in a particular instance of the ADT. The elements are indexed starting at 0. For example:

	tagof r


This returns the index of the r instance of the Apick ADT created above, which is 2 (the third element).

7.3.7.3 The pick Statement



The pick statement enables statement selection based on the variant type of the pick ADT. The general form of the pick statement is:
	pick localinst := refadtinst {
	element1 =>
		statements
	element2 =>
		statements
		...
	}


The pick statement uses a local copy, localinst, of a reference to the ADT, refadtinst. The element (s) can be one or more of the elements declared in the pick ADT.

For example:
	t: ref Apick = s;
	pick u := t {
	String =>
		sys->print("%s is %s\n", u.str, u.val);
	Int =>
		sys->print("%s is %d\n", u.str, u.val);
	Real =>
		sys->print("%s is %f\n", u.str, u.val);
	Nothing =>
		sys->print("%s is Nothing\n", u.str);
	}


First, prior to the pick statement, a ref ADT of the s instance is initialized. Then, the pick statement selects the statement to execute based on the variant type of the ADT t.

Another way typically used to accomplish this is to place the pick statement in a function that accepts as an argument the ref ADT. For example:
	printval(a: ref Apick) {
		pick t := a {
		String =>
			sys->print("%d: %s\n", tagof t, t.val);
		Int =>
			sys->print("%d: %d\n", tagof t, t.val);
		Real =>
			sys->print("%d: %f\n", tagof t, t.val);
		Nothing =>
			sys->print("%d: Nothing\n", tagof t);
		}
	}


To call this function, the instance of the ADT that specifies the element of the pick is given as the argument:
	printval(s);


The function can also be a member of the ADT. In this way, the ADT can be passed as an implicit argument . For example:
	Apick.printval(v: self ref Apick)
	{
		pick t := v {
		String =>
			sys->print("%d: %s\n", tagof t, t.val);
		Int =>
			sys->print("%d: %d\n", tagof t, t.val);
		Real =>
			sys->print("%d: %f\n", tagof t, t.val);
		}
	}


The call to the function then would look like this:
	s.printval();


Program 2 is an simple program that uses a pick ADT.
implement Command;
include "sys.m";
	sys: Sys;
include "draw.m";

Apick: adt {
	pick {
	String =>
		val: string;
	Int =>
		val: int;
	Real =>
		val: real;
	}
	printval: fn(this: self ref Apick);
};

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;
	r := ref Apick.Real;
	r.val = 3.141593;
	sys->print("%d\n", tagof r);
	s := ref Apick.String("Hello, World!");
	
	s.printval();
	r.printval();
}

Apick.printval(v: self ref Apick)
{
	pick t := v {
	String =>
		sys->print("%s: %s\n", tag(t), t.val);
	Int =>
		sys->print("%s: %d\n", tag(t), t.val);
	Real =>
		sys->print("%s: %f\n", tag(t), t.val);
	}
}

tag(t: ref Apick): string
{
	case tagof t {
	tagof Apick.String => return "String";
	tagof Apick.Int => return "Int";
	tagof Apick.Real => return "Real";
	tagof Apick.Nothing => return "Nothing";
	* => return "?";	# can't happen
	}
}
Program 2.pick.b

This program produces the following output:
	2
	String: Hello, World!
	Real: 3.141593

7.4 Scope of Data



The scope of data depends on where it is declared:
*
If data is declared in the module declaration, it is available to all functions within the module. This is referred to as module data and is global in the context of the module. Module data is initialized to 0 (zero) or null. If it is declared in a module interface file, it is public, available to other programs that load the module.
*
If data is declared outside of functions and not in the module declaration, it is global in the module, but is private.
*
If data is declared or initialized within a function, it is only available within that function. Function data can be passed between functions. It is not available to other programs, unless explicitly passed.
*
If data is declared or initialized within a statement block, it is only available within that block.


Figure 4 illustrates the dependency of the point of declaration and the scope of data.
<IMAGE xml:link="simple" href="limbo_basics-9.jpg" show="embed" actuate="auto"/>
<FigCaption>
<IMAGE xml:link="simple" href="limbo_basics-6.jpg" show="embed" actuate="auto"/>
Scope of Data</FigCaption>

8 Program Control



Limbo has several program control statements that can be categorized into these groups:
*
Selection
*
Iteration
*
Jump
*
Exit

8.1 Selection Statements



The general types of selection statements in Limbo are: if, case, and alt. See the section on Channels in the Limbo Language Definition for information about alt.

8.1.1 if



The if statement selects statements to execute based on the evaluation of an expression. The two general formats of the if statement are:
	if(expression)
		statement
	if(expression)
		statement
	else
		statement


The expression must produce an int result. If the result is true (non-zero), then the first statement (or block) is executed. Otherwise, the statement (or block) following the else is executed, if the else clause is present.

For example:
	if(guess == randnum)
		sys->print("Correct!\n");
	else
		sys->print("Oh, sorry.\n");


if statements can be nested. In Limbo, an else statement always refers to the nearest if statement that is within the same block as the else and that is not already associated with an else. For example:
	if(i == j) {
		if(k == l) {		...		}
		if(m == n) {
			...
		} else {	# this else is associated with
			...	# 'if(m == n)'
		}
	} else {		# this else is associated with
		...		# ¿if(i ==j)¿
	}

8.1.2 case



The case statement selects from multiple statements to execute based on the evaluation of an expression. The general form of the case statement is:
	case expression {
	qualifier =>
		statement
		...
	}


The expression must produce an int or a string result. The qualifier(s) can be simple constants, multiple constants, and ranges. They must evaluate uniquely, none of the ranges or constants may overlap. If there is no matching qualifier and a default qualifer, *, is present, then the default is selected. Each qualifer and the statements that follow up to the next qualifier form a block.

The general syntax of a Limbo case statement is similar to that of the C/C++ switch statement. For example:
	case direction {
	"north" or "east" =>
		...
	"south" =>
		...
	* =>
		...


Unlike C or Java switch statements, execution of one alternative in a Limbo case statement does not continue (``fall through'') to the next. No explicit break statement is needed, but break can be used to end execution of an alternative conditionally.

case statements can be nested. For example:
	case x {
	1 =>
		case y {
		0 =>
			...
		1 =>
			...

		}
	2 =>
		...
	}

8.2 Iteration Statements



Limbo has three types of iteration statements that repeat execution of a statement or block of statements until a certain condition is reached: while, do, and for loops.

Each of the loop statements can be preceeded by a label that is used in conjunction with the jump statements break and continue. See Labels later in this section for more information.

8.2.1 The for Loop



The for loop in Limbo, like C and other languages, is flexible and powerful.

The general format of the for loop is:
	for(exp1; exp2; exp3)
		statement;


Inside the parentheses there are three expressions. These can be any valid Limbo expression. The following are the most commonly used expressions:
*
exp1 is an assignment or initialization for an index variable
*
exp2 is the test for loop continuation
*
exp3 is modification to the index variable, such as increment or decrement, for each loop iteration


The statement can be an empty statement, a single statement, or a statement block enclosed in braces ({...}).

For example, the following uses a for loop to print the numbers 1 to 100:
	i: int;
	for(i = 1; i <= 100; i++)
		sys->print("%d ", i);


Notice the declaration of the integer variable, i, before entering the for loop. The declaration and assignment (initialization) can also be done in the first expression of the for statement:
	for(i := 1; i <= 100; i++)
		sys->print("%d ", i);

8.2.1.1 Variations of the for Loop



None of the three expressions in the for statement are required. For example, you can create infinite loops, time delay loops, multiple control variable loops, and many others with the flexibility of the for loop.

For example, the for loop is the iteration statement conventionally used to create an infinite loop. The following example continuously reads one character from standard input until the user enters a newline (presses the Return or Enter key):
	stdin := sys->fildes(0);	#get FD for stdin
	buf := array[128] of byte;
	for(;;) {
		n := sys->read(stdin, buf, 1);
		#check for new line or end of file
		if(int buf[0] == '\n' || n <= 0)
			break;
	}


For information about break used to terminate the loop, see The break Statement in §8.3.1.

8.2.2 The while Loop



The while loop is a general iteration statement. The general format of the while loop is:
	while(condition)
		statement;


If the condition is true, then the statement is executed. The statement can be an empty statement, a single statement, or a statement block enclosed in braces ({...}).

For example, the following program fragment prints 1 to 100:
	n := 1;
	while(n <= 100)
		sys->print("%d ", n++);

8.2.3 The do...while Loop



Unlike for and while loops, the do... while loop tests the condition at the end of the iteration. This means that a do... while loop executes at least once.

The general form of the do... while loop is:
	do
		statement;
	while(condition);


The statement can be an empty statement, a single statement, or a statement block enclosed in braces ({...}). The statement is repeated if the condition is true.

For example, the following shows the do...while version of the counter:
	n := 1;
	do {
		sys->print("%d ", n++);
	} while(n <= 100);


Although the braces are not required when there is only one statement, they are generally used to improve readability.

8.3 Jumps



Limbo has two statements for peforming unconditional jumps: break and continue.

8.3.1 The break Statement



The break statement has two uses. It can terminate case and alt statements, and it can force termination of a loop ( for, while, and do), bypassing the loop conditional test. (See Labels in §8.4 for information about using labels with break for goto-like program control.)

If the loop is nested, the break statement only terminates the loop where the break is encountered. For example:
	for(t:=1; t<=5; t++) {
		i := 1;
		for(;;) {
			sys->print("%d ", i++);
			if(i == 10)
				break;
		}
	}


This prints the numbers 1 to 9 five times. The break statement only terminates the inner infinite for loop.

8.3.2 The continue Statement



The continue statement is similiar to the break. Instead of forcing termination, however, it forces the next iteration of the loop. (See Labels for information about using labels with continue to transfer control to the end of outer loops.)

For example, the following continuously reads bytes from a file until end-of-file:
	for(;;) {
		n := sys->read(fd, buf, len buf);
		if(n <= 0)
			break;
		if(int buf[0] != 'm' || n != 37)
			continue;
	}

8.4 Labels



Limbo labels give names to for, while and do... while loops , and case and alt statements . The labels can be used in break and continue statements within the labelled statements, to transfer control to the end of the labelled block. There is no go to statement.

There are two typical uses of labels in Limbo. One is to allow a statement in an inner loop to break or continue an outer loop. Another is to break to an outer statement (including a loop) from within a case or alt statement.

Consider the following example. One or more options can be passed to a command and parsed:
Args:
	for(i := 0; i < len in; i++){
		case in[i] {
		'v' =>
			opt |= Verbose;
		's' =>
			opt |= Suppress;
		'o' =>
			opt |= OutFile;
		* =>
			break Args;
		}
	}


As long as the i th element in the string in is one of the valid options (v, s, or o), procesing continues; the for loop starts the next iteration. At the point that any other character is encountered, processing is termintated by the break statement.

If the label is not used with the break, the for loop simply starts the next iteration.

The next example shows how the label can be used to break out of a multi-nested loop:
	u := 0;
	line := "<A HREF=
	URL := "";
Outer:
	for(i := 0; i < len line; i++)
		if(line[i] == '<') {
			while(line[++i] != '>') {
				if(line[i:i+6] == "A HREF") {
					i += 6;
					if(line[i] == '=') {
						i += 2;
						while(line[i] != '"')
							URL[u++] = line[i++];
					}
				}
				break Outer;
			}
		}

8.5 Exit



The exit statement terminates a thread and frees any resources that belong exclusively to it. Unlike the C exit() function, however, the Limbo exit does not return a value to the system.



9 Functions



Functions in Limbo serve the same purpose as they do in other structured, procedural languages-they contain the statements that are executed when the program runs.

The general form of a Limbo function is:
	function_name(arguments): return_type
	{
		statements
	}


The arguments is a comma-separated list of variable names and their associated types that are passed to the function when it is called. A function is not required to have parameters, but it still must have the parentheses.

The return_type specifies the type of data that the function returns. A function may return any type. A function is not required to return data, and the Limbo compiler does not assume a return result.

9.1 The init Function



When a program is executed from the Inferno shell, the shell looks for a special function called init. This function tells Inferno where execution of the program starts. It is somewhat analogous to the main function in C.

That init function must have two arguments: ref Draw->Context and list of string.

9.1.1 ref Draw->Context



The ref Draw->Context is used to grab the context of the display and is defined in draw.m. This argument is required even if the program does not do any graphics. If it is not used, it can be named nil.

9.1.2 list of string



The list of string is a list of the arguments passed to this module via the command line. If the program does not expect any arguments passed from the command line, it can be named nil.

This is functionally equivalent to the argv variable in C, and is conventionally named argv in Limbo.

9.2 Declaring Functions



All of a module¿s public functions must be declared. A function declaration in Limbo is similar to a function prototype in C and C++. It provides type information about the arguments and the function¿s return values. This allows the compiler to perform type-checking that ensures type safety at run-time.

The general form of a Limbo function declaration is:
	function_name: fn(arguments): return_type;


Function declarations are similar to data declarations. Use the keyword fn to specify that the object is a ``function'' type.

Functions that are not declared by the module are private. They cannot be accessed by external modules. The compiler still performs type-checking against the function call(s) and the function definition.

The following example is a module that contains two functions, the public init function and a private function.
 1 implement Command;
 2 include "sys.m";
 3 	sys: Sys;
 4 include "draw.m";

 5 Command: module
 6 {
 7 	init: fn(nil: ref Draw->Context, argv: list of string);
 8 };

 9 init(nil: ref Draw->Context, argv: list of string)
10 {
11 	sys = load Sys Sys->PATH;
12 	for(i := 1; i <= 10; i++) {
13 		sys->print(" %2d %4d\n", i, sqr(i));
14 }

15 sqr(n: int): int
16 {
17 	return n*n;
18 }
Program 3. sqr.b

The declaration of the public init function on Line 7 is within the module declaration, Lines 5 to 8, making it visible to any Limbo program that loads this module.

The function definition of sqr in Lines 15 to 18 is private to the module because it is not declared in the module declaration.

The use of public and private functions is of greater consequence with respect to modules that are intended to be loaded by other modules. This is discussed more in Modules in Chapter 3.

9.3 Function Arguments



If a function is to use arguments, it must declare variables that accept the values of the arguments. Arguments behave like other local variables inside the function and are created upon entry into the function and destroyed on exit.

Since Limbo is strongly typed, the compiler checks the argument types in the function declaration, in the function definition, and in the function calls. If any of these do not match, the compiler will complain.

9.3.1 Pass By Value, Pass By Reference



Due to Limbo¿s restricted use of pointers, there is no general call/pass by reference mechanism. Reference types are passed by reference, all other types are passed by value.

When data is passed by value, the receiving function creates an entirely new variable to hold the value of the argument passed to it. Modifications to the variable have no effect on the value in the calling function.

When data is passed by reference, the receiving function receives a reference, a pointer, to the value of the argument passed to it. Modifications made to the data also affect the value in the calling function.

9.4 Function Return Values



As stated earlier, a function may (but need not) return a value of some type.

9.4.1 The return Statement



The return statement returns from a function. It passes control from a function back to the calling location. Program control returns to the location where the function was called when control ``falls through'' the end of the function (reaches the end brace). If a function is to return a value, it must use a return statement.

A return statement can include an expression which becomes the return value of the function. The expression must be of the same type as the function's declared return value.

Function sqr in the Program Listing 3 on page 2-67 (Lines 16 to 19) uses the return statement to return the square of the number that was passed to it.
sqr(n: int): int
{
	return n*n;
}


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