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

The Acheron Limbo Compiler

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.

 

 Lucent Technologies introduces Inferno 1.0; real-time network operating system provides powerful platform for business applications development


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.

4.Bell Labs