The AIL Input Language

Amoeba uses C as its primary programming language. C function declarations (at least in `Classic' C) don't specify the types of the parameters, let alone their transfer direction. Using this as input for a stub generator would require almost all information for the stub generator to be hidden inside comments, which would require a rather contorted scanner. Therefore we decided to design the input syntax for Amoeba's stub generator `from scratch'. This gave us the liberty to invent proper syntax not only for the transfer direction of parameters, but also for variable-length arrays.

On the other hand we decided not to abuse our freedom, and borrowed as much from C as we could. For instance, AIL runs its input through the C preprocessor, so we get macros, include files and conditional compilation for free. AIL's type declaration syntax is a superset of C's, so the user can include C header files to use the types declared there as function parameter types — which are declared using function prototypes as in C++ or Standard C. It should be clear by now that AIL's lexical conventions are also identical to C's. The same is true for its expression syntax.

Where does AIL differ from C, then? Function declarations in AIL are grouped in classes. Classes in AIL are mostly intended as a grouping mechanism: all functions implemented by a server are grouped together in a class. Inheritance is used to form new groups by adding elements to existing groups; multiple inheritance is supported to join groups together. Classes can also contain constant and type definitions, and one form of output that AIL can generate is a header file for use by C programmers who wish to use functions from a particular AIL class.

Let's have a look at some (unrealistically simple) class definitions:

#include <amoeba.h>     /* Defines `capability', etc. */

class standard_ops [1000 .. 1999] {
    /* Operations supported by most interfaces */
    std_info(*, out char buf[size:100], out int size);
    std_destroy(*);
};
This defines a class called `standard_ops' whose request codes are chosen by AIL from the range 1000-1999. Request codes are small integers used to identify remote operations. The author of the class must specify a range from which AIL chooses, and class authors must make sure they avoid conflicts, e.g. by using an `assigned number administration office'. In the example, `std_info' will be assigned request code 1000 and `std_destroy' will get code 1001. There is also an option to explicitly assign request codes, for compatibility with servers with manually written interfaces.

The class `standard_ops' defines two operations, `std_info' and `std_destroy'. The first parameter of each operation is a star (`*'); this is a placeholder for a capability that must be passed when the operation is called. The description of Amoeba below explains the meaning and usage of capabilities; for now, it is sufficient to know that a capability is a small structure that uniquely identifies an object and a server or service.

The standard operation `std_info' has two output parameters: a variable-size character buffer (which will be filled with a short descriptive string of the object to which the operation is applied) and an integer giving the length of this string. The standard operation `std_destroy' has no further parameters — it just destroys the object, if the caller has the right to do so.

The next class is called `tty':

class tty [2000 .. 2099] {
    inherit standard_ops;
    const TTY_MAXBUF = 1000;
    tty_write(*, char buf[size:TTY_MAXBUF], int size);
    tty_read(*, out char buf[size:TTY_MAXBUF], out int size);
};
The request codes for operations defined in this class lie in the range 2000-2099; inherited operations use the request codes already assigned to them. The operations defined by this class are `tty_read' and `tty_write', which pass variable-sized data buffers between client and server. Class `tty' inherits class `standard_ops', so tty objects also support the operations `std_info' and `std_destroy'.

Only the interface for `std_info' and `std_destroy' is shared between tty objects and other objects whose interface inherits `standard_ops'; the implementation may differ. Even multiple implementations of the `tty' interface may exist, e.g. a driver for a console terminal and a terminal emulator in a window. To expand on the latter example, consider:

class window [2100 .. 2199] {
    inherit standard_ops;
    win_create(*, int x, int y, int width, int height,
                  out capability win_cap);
    win_reconfigure(*, int x, int y, int width, int height);
};

class tty_emulator [2200 .. 2299] {
    inherit tty, window;
};
Here two new interface classes are defined. Class `window' could be used for creating and manipulating windows. Note that `win_create' returns a capability for the new window. This request should probably should be sent to a generic window server capability, or it might create a subwindow when applied to a window object.

Class `tty_emulator' demonstrates the essence of multiple inheritance. It is presumably the interface to a window-based terminal emulator. Inheritance is transitive, so `tty_emulator' also implicitly inherits `standard_ops'. In fact, it inherits it twice: once via `tty' and once via `window'. Since AIL class inheritance only means interface sharing, not implementation sharing, inheriting the same class multiple times is never a problem and has the same effect as inheriting it once.

Note that the power of AIL classes doesn't go as far as C++. AIL classes cannot have data members, and there is no mechanism for a server that implements a derived class to inherit the implementation of the base class — other than copying the source code. The syntax for class definitions and inheritance is also different.