Hello, world

The quickest way to learn the basics of a new programming language is to study a simple example. Here is a complete Limbo program that prints a familiar greeting:

	implement Hello;
	
	# Declaration of external modules used here
	include "sys.m";
		sys: Sys;
	
	include "draw.m";
	
	# Definition of the module implemented by this program
	Hello: module
	{
		init: fn(ctxt: ref Draw->Context, argl: list of string);
	};
	
	# The single function exported by the module
	init(ctxt: ref Draw->Context, argl: list of string)
	{
		sys = load Sys Sys->PATH;
	
		sys->print("hello, world\n");
	}
	

The last line of the function init produces the message, by invoking a printing function, sys->print. The rest of the program establishes the execution context for that statement and introduces many important aspects of Limbo. Compared to other languages, Limbo requires a fair bit of `boilerplate' to begin a program, a consequence of the structure of Limbo programs.

A Limbo program is constructed as a set of interacting modules that are linked together at run time, that is, when the program executes. Each module is compiled from a single Limbo source file, suffixed .b, into a Dis executable, suffixed .dis. Our program might be saved in a file hello.b that could then be compiled into a file hello.dis by the Limbo compiler, called limbo:

	minos$ limbo hello.b
	minos$ ls hello.*
	hello.b
	hello.dis
	minos$ 

The hello.dis file that results is the implementation&emdash;the executable form&emdash;of a single module defined by the implement statement that begins the program. In our example, the line

	implement Hello;

states that the program will provide the implementation of a module called Hello. Further down, the program defines the type of module Hello to contain a single function, init:

	# Definition of the module implemented by this program
	Hello: module
	{
		init: fn(ctxt: ref Draw->Context, argl: list of string);
	};
	

Commentary in Limbo is introduced by a sharp (#) and runs until the end of the line.

The Limbo compiler is case-sensitive, so hello and Hello are different names. Also, the correspondence between the source file name (hello.b) and the module name (Hello) is just a convention; the source file can be named anything convenient. It is good style, though, to follow the guidelines of this example.

When the module is loaded for execution by a program, its definition specifies what aspects of the module are visible to the invoking program. In this case, the invoker can see only the init function. The particular specification for init is used by all commands to be invoked by the Inferno shell, such as ls and the Limbo compiler itself.

A command name typed to the shell identifies a Dis file to load (with the .dis suffix elided); after loading the file, the shell calls the init function of that module. In this case, typing hello loads and runs the file hello.dis:

	$ hello
	hello, world
	$

Hello isn't the only module needed to run our program. The program also uses two other modules to provide functions and types needed for its execution.

The first, Sys, implements basic system and I/O primitives such as the formatted print routine, print. The second, Draw, is needed only to acquire the definition of the graphics context type, Context, part of the specification of a shell command. We can pass over it for now.

To use Sys, we first include a file that defines the interface to the module:

	include "sys.m";

This statement reads the definition of the module Sys from the file sys.m. Module specification files are suffixed .m and contain definitions like that of our module Hello, but often more elaborate. The `include' files for public Inferno modules reside in the directory /module in the root of the Inferno file tree; for example, Sys's definition is in /module/sys.m.

The include file specifies what the Sys module does, but not how it does it. To be able to execute code from the module we must find an implementation and load it into memory. In this example, we must load an implementation of Sys before we can call the function print.

This loading operation is a fundamental part of programming in Limbo.

Some languages that have module-like mechanisms for structuring programs load implementations automatically: when, say, print is needed, the run-time system will find the appropriate implementation and load it. Limbo is different: the programmer must explicitly ask for the implementation to be loaded. Although this may seem inconvenient, it has advantages, which we will explore in Chapter 4.

The declaration

	sys: Sys;

declares a variable, sys, of type Sys, that will hold the loaded module. Thus, sys is a module variable, which holds a piece of executable code and perhaps some associated data.

The variable sys has type Sys, but its value isn't set until the program loads the module:

		sys = load Sys Sys->PATH;

The load statement attaches a new module to the program by reading a Dis file and returning, as a module value, the `code' stored in the file. Almost always, as here, the result will be stored in a variable. The load statement takes two `arguments': the type of the module to be loaded and the name of the file containing the code. By convention, most modules define a constant string called PATH that names the file holding the standard implementation of the module. The expression

		Sys->PATH

means `the object PATH defined in module Sys', which happens to be a string that names the file holding the implementation.

In our example, then, the load statement behaves like this: the file named by the string Sys->PATH is loaded into memory. That file should implement the module defined by the module type Sys. (If it doesn't, the statement will fail; more on this in Chapter 4.) Assuming it does, the module is assigned to the variable sys, from which its services may be accessed.

Finally, we can use the module by calling a function defined by it, through the variable sys:

		sys->print("hello, world\n");

The -> operator accesses a member of a module.

String constants in Limbo are bracketed by double quotes ("). The notation \n refers to a newline character; related forms include \r for carriage return, \b for backspace, and \u1234 for the character with hexadecimal value 1234 (u stands for Unicode, the subject of the next chapter).

If all we want to do is print a string, this module business may seem cumbersome. What's going on isn't much different from what happens in other systems, though, just more explicit. As we will see as we learn more about Inferno, being explicit about modules allows programs to control their use of resources and configure themselves dynamically for their execution environment.

Our immediate goal, however, is to learn more about Limbo itself, so in this chapter we'll treat the structure of hello.b as a template and write only single-module programs. In later chapters, we'll be more adventurous.

previous next

© Rob Pike and Howard Trickey 1997. All rights reserved.