LIMBO PROGRAMMING
NETB131 Programming Project
Atanas Todorov, F38900
History and Special Features
It was designed at Bell Labs by Sean Dorward, Phil
Winterbottom, and Rob Pike. Limbo is a programming language intended for
applications running distributed systems on small computers. It supports modular
programming, strong type checking at compile- and run-time, interprocess
communication over typed channels, automatic garbage collection, and simple
abstract data types. It is designed for safe execution even on small machines
without hardware memory protection.
In its initial implementation for the Inferno operating system, object
programs generated by the Limbo compiler run using an interpreter for a fixed
virtual machine. Inferno and its accompanying virtual machine run either
stand-alone on bare hardware or as an application under conventional operating
systems like Unix, Windows 95, Windows NT, and Plan 9. For several architectures,
including Intel x86 and MIPS, Limbo object programs are transformed on-the-fly
into instructions for the underlying hardware. Limbo's approach to concurrency
was inspired by Hoare's Communicating Sequential Processes (CSP).
"Hello World" Program
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 # The canonical "Hello world" program, enhanced 9 init(ctxt: ref Draw->Context, argv: list of string) 10 { 11 sys = load Sys Sys->PATH; 12 sys->print("hello world\n"); 13 for (; argv!=nil; argv = tl argv) 14 sys->print("%s ", hd argv); 15 sys->print("\n"); 16 }
Fundamental Data Types
(integer, floating point, string)
and Assignment Operator
The five basic data types are denoted by byte, int,
big, real, and string.
Bytes are unsigned 8-bit quantities.
Integers (int) are 32-bit signed quantities represented in two's complement
notation. Large integers (big) are 64-bit signed quantities represented in two's
complement notation.
Real numbers (real) are 64-bit quantities represented in the IEEE long floating
notation.
The byte, int, big, and real types are collectively called arithmetic types.
Strings are rows of Unicode characters. They may be concatenated and extended
character-by-character. When a string is indexed with a single subscript, it
yields an integer with the Unicode encoding of the character; when it is indexed
by a range, it yields another string.
Tuple type
The tuple type, is a type consisting of an ordered collection of two or more objects, each having its own data type. For each tuple type, the types of the members are fixed, but need not be identical; for example, a function might return a tuple containing an integer and a string. Each tuple type is characterized solely by the the order and identity of the types it contains. Objects of tuple type may be assigned to a list of identifiers (to pick out the components), and a parenthesized, comma-separated list of expressions denotes a tuple.
Abstract data types (adt)
An abstract data type or adt is an object that can contain data objects of several different types and declare functions that operate on them. The syntax for declaring an adt is given later. Once an adt has been declared, the identifier associated with it becomes a data-type name.
Assignment Operator
In general, the types of the left and right
operands must be the same; this type must be a data type. The value of an
assignment is its new left operand. All the assignment operators associate
right-to-left.
In the ordinary assignment with =, the value of the right side is assigned to
the object on the left. For simple assignment only, the left operand may be a
parenthesized list of lvalues and the right operand either a tuple or an adt
whose data members correspond in number and type to the lvalues in the list. The
members of the tuple, or the data members of the adt, are assigned in sequence
to lvalues in the list. For example,
p: Point;
x, y: int; (like int x, y; in C++)
(x, y) = p;
splits out the coordinates of the point into x and y. These rules apply
recursively, so that if one of the components of the left side is a
parenthesized list of lvalues, it is assigned from a corresponding adt or tuple
on the right.
If the left operand of a simple assignment is an adt and the right side is a
tuple, then the assignment assigns the members of the tuple to the adt data
members; these must correspond in number and type with the members of the tuple.
The constant nil may be assigned to an lvalue of any reference type. This lvalue
will compare equal to nil until it is subsequently reassigned. In the Inferno
implementation of Limbo, such an assignment also triggers the removal of the
object referred to unless other references to it remain.
The left operand of an assignment may be the constant nil to indicate that a
value is discarded. This applies in particular to any of the lvalues in a tuple
appearing on the left; to extend the examples above,
(x, nil) = p;
assigns the x member of the Point p to the variable x.
A special consideration applies to strings. If an int containing a Unicode
character is assigned to a subscripted string, the subscript is normally
required to lie within the string. As a special case, the subscript's value may
be equal to the length of the string (that is, just beyond its end); in this
case, the character is appended to the string, and the string's length increases
by 1.
A final special case applies to array slices in the form e1[e2:]. Such
expressions may lie on the left of =. The right side must be an array of the
same type as e1, and its length must be less than or equal to (len e1)-e2. In
this case, the elements in the array on the right replace the elements of e1
starting at position e2. The length of the array is unchanged.
Basic Control Flow (conditional and loop statements)
The conditional statement takes two
forms:
if ( expression ) statement
if ( expression ) statement else statement
The expression is evaluated; it must have type int. If it is non-zero, then the
first statement is executed. In the second form, the second statement is
executed if the expression is 0. The statement after else is connected to the
nearest else-less if.
Simple looping statements:
The simple looping statements are
while ( expressionopt ) statement
do statement while ( expressionopt ) ;
In both cases the expression must be of type int. In the first form, the
expression is first tested against 0; while it is not equal, the statement is
repeatedly executed. In the second form, the statement is executed, and then,
while the expression is not 0, the statement is repeatedly executed. If the
expression is missing, it is understood to be non-zero.
The for statement has the
form
for ( expression-1opt ; expression-2opt ; expression-3opt ) statement
It is equivalent to
expression-1 ;
while ( expression-2 ) {
statement
expression-3 ;
}
in the absence of continue or break statements. Thus (just as in C), the first
expression is an initialization, the second a test for starting and continuing
the loop, and the third a re-initialization for subsequent travels around the
loop.
Task: Input an integer number n and output the sum: 1+22+32+...+n2. Use input validation for n to be positive:
implement Command;
include "sys.m";
include "draw.m";
sys : Sys;
Command: module
{
init: fn(nil: ref Draw->Context, nil: list of string);
};
init(*,*)
{
sys = load Sys Sys->PATH;
outfile := "outputfile";
outfd: ref Sys->FD;
sys->create(outfile, sys->ORDWR, 8r755);
sys->print("ENTER NUMBER:");
outfd = sys->open(outfile, sys->ORDWR);
buf := array[1] of byte;
sys->write(outfd, buf, len buf);
n := sys->read(outfd,buf,len buf);
if ( n <= 0 )
break;
else
{
sum:int = 0;
for (i:int = 1; i <= n; i++) sum = sum + pow(i,2);
}
sys->print("The number is: %d\n",sum);
return;
}
Functions - syntax, writing and using functions, example
Function types
A function type characterizes the arguments and return value of a function.
The syntax is
function-type:
fn function-arg-ret
function-arg-ret:
( formal-arg-listopt )
( formal-arg-listopt ) : data-type
formal-arg-list:
formal-arg
formal-arg-list , formal-arg
formal-arg:
nil-or-D-list : type
nil-or-D : self refopt identifier
nil-or-D : self identifier
*
nil-or-D-list:
nil-or-D
nil-or-D-list , nil-or-D
nil-or-D:
identifier
nil
That is, the denotation of a function type has the keyword fn followed by a
comma-separated list of its arguments enclosed in parentheses, and perhaps
followed by the type the function returns. Absence of a return value means that
the function returns no value: it is a procedure. The names and types of
arguments are specified. However, the name of an argument may be replaced by nil;
in this case it is nameless. For example,
fn (nil: int, nil: int): int
fn (radius: int, angle: int): int
fn (radius, angle: int): int
all denote exactly the same type, namely a function of two integers that returns
an integer. As another example,
fn (nil: string)
is the type of a function that takes a string argument and returns no value.
The self keyword has a specialized use within adt declarations. It may be
used only for the first argument of a function declared within an adt; its
meaning is discussed in §6.3 below.
The star character * may be given as the last argument in a function type.
It declares that the function is variadic; during a call, actual arguments at
its position and following are passed in a manner unspecified by the language.
For example, the type of the print function of the Sys module is
fn (s: string, *): int
This means that the first argument of print is a string and that other
arguments may be given when the function is called. The Limbo language itself
has no way of accessing these arguments; the notation is an artifice for
describing facilities built into the runtime system, such as the Sys module.
Arrays
The array type describes a
dynamically-sized row of objects, all of the same type; it is indexed starting
from 0. An array type is denoted by
array of data-type
The size of an array is not part of its type; instead it is part of the
value. The data-type may itself be an array, to achieve a multidimensional
array.
In the expressions
array [ expression ] of data-type
array [ expressionopt ] of { init-list ,opt }
the value is a new array of the specified type. In both forms, the expression
must be of type int, and it supplies the size of the array. In the first form,
the type is given, and the values in the array are initialized as appropriate to
the underlying type. In the second form, a comma-separated list of values to
initialize the array is given, optionally followed by a trailing comma. The type
of the array is taken from the types of the initializers, which must all be the
same. The list of initializers has the syntax
init-list:
element
init-list , element
element:
expression
expression => expression
* => expression
In an init-list of plain expressions (without =>), the members of the array
are successively initialized with the corresponding elements of the init-list.
An element of the form e1=>e2 initializes the member of the array at subscript
e1 with the expression e2. After such an element has been given, subsequent
simple elements (without =>) begin initializing at position e1+1 and so on. Each
of the first expressions must be of type int and must evaluate to a constant .
If an element of the form * =>e2 is present, all members of the array not
otherwise initialized are set to the value e2. The expression e2 is evaluated
for each subscript position, but in an undefined order. For example,
arr := array[3] of { * => array[3] of { * => 1 } };
yields a 2-dimensional array (actually an array of arrays) filled with 1's.
If the expression giving the size of the array is omitted, its size is taken
from the largest subscript of a member explicitly initialized. It is erroneous
to initialize a member twice.
The line
buf := array[1] of byte;
declares buf to be a one-element array of bytes. Arrays are indexed from zero,
so buf[0] is the only element. Arrays in Limbo are dynamic, so this array is
created at the point of the declaration.
Compilers
This is a port of the Dis virtual machine system to POSIX-compatible operating systems, based on the original Inferno sources, including a Limbo compiler. The port is intended to make the virtual machine and runtime environment more general and easily ex.
Projects and Software in LIMBO
All Inferno applications are written in Limbo. Life is made easier for the programmer with features such as automatic garbage collection, compile and runtime type checking and simple creation of multiple processes (threads) and communication between them. Inferno also comes with a graphical debugger, allowing the user to step into the program at any point and browse through the current state.
References
1.Limbo programming from www.wikipedia.com
2.A Descent into Limbo by Brian Kernighan
3.The Limbo Programming Language Copyright © 1996, 1997 Lucent Technologies Inc. All rights reserved.