Go to the previous, next section.

Using ILU with Python

Introduction

This document is for the Python programmer who wishes to use ILU. The following sections will show how ILU is mapped into Python constructs and how both Python clients and servers are generated and built.

The ISL Mapping to Python

Names

In general, ILU constructs Python symbols from ISL names by replacing hyphens with underscores. For example, an ISL object type T-1 would correspond to the Python class T_1. Any place an ISL name appears as part or all of a Python identifier, this translation occurs.

Interface

Each ISL interface I generates two Python modules: one named I containing common definitions, and another named I__skel containing skeletons (server stubs). For example, INTERFACE map-test; generates the Python modules map_test and map_test__skel, contained in the files `map_test.py' and `map_test__skel.py', respectively.

Constant

ISL constants translate to Python variables initialized to the specified value. For example,
CONSTANT pi : real = 3.14159265358979323846;
maps to
pi = 3.14159265358979323846e0

Exception

An ISL exception translates to a Python variable initialized with a string representing the exception. These variables are used in Python raise statements in object implementation code, and in try ... except statements in client code. For example, the declaration
EXCEPTION division-by-zero;
in the interface map-test maps to the following statement in `map_test.py':
division = 'map-test: division-by-zero'

Types

Basic Types

The basic ISL types have the following mapping to Python types:

  1. BYTE, BOOLEAN, SHORT CHARACTER, CHARACTER, SHORT INTEGER, INTEGER, and SHORT CARDINAL all map to Python int.
  2. LONG INTEGER, CARDINAL, and LONG CARDINAL all map to Python long int.
  3. SHORT REAL and REAL map to Python float.
  4. LONG REAL maps to the Python type ilu_longreal, a type implemented by the ILU Python runtime. This type has limited functionality, but can be passed around without loss of precision, converted to float or int, and compared. A value of this type may be constructed by calling ilu.LongReal().

Enumeration

The names of Python variables for enumeration values are formed by prepending the enumeration type name and two underscores ("__") to the enumeration value name. There is also a dictionary for each enumeration type that maps an enumeration value to a string corresponding to its Python enumeration value name. The name of the dictionary is "imageOf" followed by three underscores ("___") followed by the enumeration type name.

For example,

TYPE color = ENUMERATION red, dark-blue END;
maps to
color__red = 0
color__dark_blue = 1
imageOf___color = {
  color__red: 'color__red',
  color__dark_blue: 'color__dark_blue'}

Array

An ISL array maps into a Python list with the specified number of elements. Tuples as well as lists are accepted as input, but lists are always produced as output from ILU. For an array of short character or byte, strings are also accepted, in which case the ord() of each character in the string is sent. Be careful using these alternate forms on input, since they will go across as is for objects in the same address space, but will be changed to lists for remote objects.

Sequence

An ISL sequence of short character maps into a Python string.

All other ISL sequence types map into Python lists. Tuples as well as lists are accepted as input, but lists are always produced as output from ILU. For a sequence of byte, strings are also accepted, in which case the ord() of each character in the string is sent. Be careful using these alternate forms on input, since they will go across as is for objects in the same address space, but will be changed to lists for remote objects.

Record

ISL records map into Python dictionaries, with the field names as string keys, and the field values as the corresponding values.

For example, a record value of the ISL type

TYPE segment = RECORD left-limit : integer, right-limit : integer END;
with a left-limit of -3 and a right-limit of 7 would map to
{'left_limit': -3, 'right_limit': 7}

Union

An ISL union maps into a Python tuple with two components: an integer discriminator, and the discriminated value. There are three possibilities:
  1. If the discriminator matches one of the union case values of an arm, the second component is of the type specified by that arm.
  2. If the discriminator matches no union case values and there is a default arm, the second component is of the type specified by the default arm.
  3. If the discriminator matches no union case values and there is no default arm but the union has the OTHERS attribute, the second component is None.

Object

Each ISL object type is mapped into a Python class. These classes have the methods specified in the ISL, as well as some built-ins.

Optional

A value corresponding to the ISL type OPTIONAL T may be None (indicating the null case) in addition to the values of the type T.

Methods and Parameters

ISL methods of an object type map to Python methods of the corresponding class. IN and INOUT parameters appear in the Python method signature in the same order as they do in ISL.

Let us define a result value to be either a return value (corresponding to a method's return type) or an INOUT or OUT parameter. Result values are returned by the Python method as a tuple, with the return value (if present) appearing before any parameters. If the method has only one result value, then it is simply returned (i.e., a tuple of length one is not constructed to hold this value). If the method has no result values, then None is returned.

Using an ILU module from Python

The ILU runtime interface is in the Python module ilu. Python definitions for ISL INTERFACE I are in the Python module I. As with any other modules in Python, these modules are imported using the import statement.

A client program may create an ILU object in one of three ways:

  1. Knowing the string binding handle sbh and class cl of an object, call ilu.ObjectOfSBH(cl, sbh) which returns an instance of that class. For example, to obtain an instance of ISL type square from INTERFACE shapes whose string binding handle is sbh, one would call ilu.ObjectOfSBH(shapes.square, sbh).
  2. Knowing an object ID oid and class cl of an object that has been published using the simple binding service, call ilu.Lookup(oid, cl) which returns an instance of that class (or None if the lookup fails).
  3. Receive an instance as a result value from a method call that returns an object type or has an object type as an INOUT or OUT parameter.

Implementing an ILU module in Python

A Python module that implements ILU objects of types defined in INTERFACE I also imports from I__skel. This gives access to the skeleton classes from which implementation classes inherit.

Implementation Inheritance

An implementation of object type T from interface I needs to inherit from the class I__skel.T. If there is inheritance in the ISL, and an implementation of a subtype wants to inherit from an implementation of a supertype, the skeleton class must be appear in the list of base types before the implementation class.

For example, objects for the ISL

INTERFACE j;

TYPE c1 = OBJECT METHODS one() END;
TYPE c2 = OBJECT METHODS two() END;
TYPE c3 = OBJECT SUPERTYPES c1, c2 END METHODS three() END;
could be implemented in Python by
import ilu, j, j__skel

class c1(j__skel.c1):
    def one(self):
        ...

class c2(j__skel.c2):
    def two(self):
        ...

class c3(j__skel.c3, c1, c2):
    def three(self):
        ...
In this case c3's method one is implemented by c1.one and c3's method two is implemented by c2.two.

True Servers

Each object exported by an implementation must belong to a true server, an instance of the Python type ilu_Server which is implemented by the ILU runtime. An ilu_Server can be created by calling the function ilu.CreateServer(serverID = None, transport = None, protocol = None), which returns a value of type ilu_Server. If serverID is a string, it specifies the server ID; if it is None, one will be invented automatically. The transport and protocol arguments are strings to choose a specific transport or protocol, or None to let them default.

The first time a true server is created, it becomes the default server. The default server is used for an exported object if a server is not otherwise specified. If an object is exported before any servers have been created, one will be created automatically using default parameters and a message to that effect will be written to stderr.

An object of type ilu_Server has a method id() that returns its server ID.

Exporting Objects

An object can be exported in one of three ways:
  1. The object's string binding handle may be obtained by calling its method IluSBH() and communicating this somehow to a client, who then turns the handle back into an object by calling ilu.ObjectOfSBH(cl, sbh).
  2. The object may be published using the simple binding service by calling its method IluPublish(). In order for this to be effective, the object must have a well-known object ID, or the object ID must be communicated to clients, so clients can know what to pass to ilu.Lookup. The object ID is a function of the object's instance handle and its server's server ID.
  3. The object may be returned by a method or passed back in a method's INOUT or OUT parameter.

An object's instance handle can be controlled by setting the instance variable IluInstHandle before the object is first exported. If this instance variable is not set, and instance handle will be invented automatically.

An object's server can be controlled by setting the instance or class variable IluServer to a value of type ilu_Server. The value of this variable at the time an object is first exported will be used as the server for that object. If such a variable is not set, the default server is used.

Holding References to Exported Objects

Currently, an implementation must cause at least one reference to be held to an exported object in order for it not to be garbage collected by Python. This may change in later releases. One way to hold a reference to an exported object is to append it to a global list.

Animating Servers

Running the ILU main loop by calling ilu.RunMainLoop() brings the true servers to life. This function does not return until ilu.ExitMainLoop() is called.

Using Alarms

In order to schedule a Python function to be called at a certain time in the future when executing the ILU main loop, an ilu_Alarm may be used. Objects of this type are created by calling ilu.CreateAlarm(). An ilu_Alarm must be set to have any effect.

The alarm's method set(time, proc, args) is used to set the alarm. The int, float, or ilu_FineTime time argument is the time at which the alarm will fire; the proc argument is the Python function that will be called when the alarm fires; and the args argument is a tuple of arguments to be passed to proc. The tuple args must match proc's signature. For example, if proc is declared def P(a, b): then args must be a two-tuple. Likewise, if proc takes only one argument then args must be a one-tuple, or if no arguments then a zero-tuple.

The function ilu.FineTime_Now() may be called to obtain ILU's idea of the current time. A value sec of type int or float in units of seconds may be converted to type ilu_FineTime by calling ilu.FineTime(sec). Values of type ilu_FineTime may be compared, added, and subtracted. These operations may be used to construct values representing any relative time (subject to precision and range limitations), which is what is needed by an alarm's set method.

The alarm may be set multiple times with different arguments, in which case the parameters of the most recent call to set are in effect. Thus, once an alarm fires, it may be reused by calling set again.

An alarm may be unset by calling its method unset().

Using the Simple Binding Service

An object may be published using the simple binding service by calling its method IluPublish(). An object may be unpublished by calling its method IluWithdraw().

A published ILU object may be obtained by calling ilu.Lookup(oid, cl), where oid is its object ID and cl is its class. The function ilu.FormOID(instHandle, serverID) may be called, knowing the instance handle and server id of the object in question, to obtain an oid to pass to ilu.Lookup.

Summary of the ILU Python Runtime

Exported from module ilu:

Built-in methods of ILU objects:

Special attributes of ILU true objects: One or more of the following attributes may be set in a true (implementation) object of an ISL object type to control certain aspects of that object.

Stub Generation

To generate Python stubs from an ISL file, use the program python-stubber. Two files are generated from each ISL INTERFACE name:

Go to the previous, next section.