Limbo [12] is the primary language currently used with the Inferno system. It is specifically designed for the Inferno environment, with tight syntactical integration of communication and multi-tasking in the actual syntax of the language. Syntactically, it is mainly influenced by C (not surprisingly) and Pascal. In addition to the standard data types found in C and Pascal, Limbo supports many higher-level datatypes. Modular applications are supported through self-contained units called modules. This chapter presents the main features of the Limbo language, along with some simple examples.
To execute a program written in Limbo, it must be compiled into byte-codes for the Dis virtual machine. Limbo source modules use the '.b'-suffix, and include files use the '.m'-suffix.
The following simple module implements the classical Hello World program with the addition of handling arguments. The program demonstrates some of the basic elements of a Limbo module.
implement Command; include "sys.m"; # print is needed include "draw.m"; # Not really needed, gfx-related sys: Sys; # Declare variable sys of type Sys
Command: module # Declaration of 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; # Load the Sys module sys->print("hello world\n"); for (; argv != nil; argv = tl argv) # tl == tail (cdr) sys->print("%s ", hd argv); # hd == head (car) sys->print("\n"); }
As the example shows, the syntax of Limbo expressions, statements, and some conventions are influenced heavily by C, but the declarations, on the other hand, are influenced by Pascal.
The first line of the program shows what this module implements, and the following include lines work in the same way as #include in C. The name init is used like main in C, but only as a convention. The # character marks the rest of the line as a comment, and some explanations about the program can be found behind these comments.
This is not a complete list of the many aspects and features of Limbo, but it is merely intended to show some of the basic ideas, along with examples of the network-oriented features specifically designed for Inferno..
The garbage collection of Limbo is automatic, and it is normally done immediately when the last reference to a data structure is removed. To make this possible, it is not possible to make cyclic data structures, unless specified explicitely through the cyclic keyword. If this feature is used, the data structures will be garbage-collected when the only references are internal in the data structures, but not immediately.
Limbo uses lexical scoping, which means that the visibility of a variable is determined from the structure of the program. This means that the local variables of the calling function are not visible to the called function (dynamic scoping). Variables declared at the module level are global to that module (much like top-level static variables in C.)
Limbo supports higher-level datatypes such as strings, lists, tuples, dynamic arrays, and abstract data types. Type-checking is quite strict, and it is done both when compiling and at run-time. Abstract data types can be used to implement higher-level structures. One example of abstract data types is a structure to implement a tree, which requires the use of the cyclic keyword (see below.)
Tree: adt { l: cyclic ref Tree; r: cyclic ref Tree; t: ref Ntree; };
Ntree: adt { t: cyclic ref Tree; };
One speciality of Limbo is the channel communication syntax. A channel is declared through the following syntax:
ch: chan of int; # declaration, set ch to nil
ch = chan of int; # actually creates the channel and assigns it
To send data to the channel, the following syntax is used (foo is an integer):
ch <-= foo;
and data can be read from the channel using:
foo :=<- ch;
Using the alt statement several channels can be watched simultaneously for input and output. Below is an example:
outchan := chan of string; inchan := chan of int; alt { i :=<- inchan => sys->print("Received %d\n", i); outchan <-= "message" => sys->print("Message sent\n"); }
To implement non-blocking tests a * can be used as a qualifier, and if no channel is ready, the statements associated with it are executed.
True, pre-emptive multitasking is possible through the spawn statement. This statement creates an asynchronous, independent thread, that shares the same memory space. Synchronization between threads is not implemented explicitely, but it can easily be done using channels.
In principle any language that is byte-compiled could be used instead of Limbo, including Java. This is something that is worked on at the Inferno project, but it is a minor point, because Limbo is the primary language due to the fact that it has been designed from scratch with the Inferno system in mind.