Flow Control

Todo

This section is not finished yet. Provides more examples and explanations.

Variables

Variables should contain values of at most one type at each control flow point. For example, this means that joining control paths using the same variable to contain both a string and a int must be avoided.

Let’s look at this example code.

def compiler_error(arg):
    v = 0              # v is an integer
    if arg:
        v = 1          # assign integer 1 to v
    else:
        v = ""         # assign string "" to v
    return v

def entry_point(argv): compiler_error(argv[1]); return 0
def target(*args): return entry_point

The control flow graph of the compiler_error function is shown in the following figure.

../_images/union_error.png

Merge point of a control flow with different types.

As you can see, at the merge point (highlighted in green), v2 can be either integer 1 or an empty string ''. this violate the RPython’s restriction.

It is allowed to mix None with many other types: wrapped objects, class instances, lists, dicts, strings, etc., but not with int, floats or tuples.

class Obj():
    pass

def flow_control_variables_unsupported(arg):
    """
    int, floats or tuples cannot be mixed with None.
    """
    i = None    # integer
    f = None    # float
    t = None    # tuple

    if arg:
        i = None
        f = None
        t = None
    else:
        i = 0
        f = 0.0
        t = (1, 2, 3)

    return i, f, t

def flow_control_variables(arg):
    """
    It is allowed to mix None with many other types: wrapped objects, class
    instances, lists, dicts, strings, etc.
    """
    o = None    # object instance
    l = None    # list
    d = None    # dict
    s = None    # string

    if arg:
        o = None
        l = None
        d = None
        s = None
    else:
        o = Obj()
        l = [1, 2, 3]
        d = {1: "123"}
        s = "hello"

    if o and l and d and s:
        print o, l, d[1], s

    return o, l, d, s

def entry_point(argv): flow_control_variables(int(argv[1])); return 0
def target(*args): return entry_point

Constants

Note

All module globals are considered constants. Their binding must not be changed at run-time. Moreover, global (i.e. prebuilt) lists and dictionaries are supposed to be immutable: modifying e.g. a global list will give inconsistent results. However, global instances don’t have this restriction, so if you need mutable global state, store it in the attributes of some prebuilt singleton instance.

Control structures

Note

All allowed, for loops restricted to builtin types, generators very restricted.

Range

Note

Range and xrange are identical. range does not necessarily create an array, only if the result is modified. It is allowed everywhere and completely implemented. The only visible difference to CPython is the inaccessibility of the xrange fields start, stop and step.

Definitions

Note

Run-time definition of classes or functions is not allowed.

Generators

Note

Generators are supported, but their exact scope is very limited. you can’t merge two different generator in one control point.

Exceptions

Python exceptions are fully supported. For example, you can catch exceptions by the except keyword following a specific exception class.

def exceptions(x, y):
    try:
        n = x / y
    except ZeroDivisionError:
        print "division by zero"

def entry_point(argv):
    exceptions(int(argv[1]), int(argv[2]))
    return 0

def target(*args): return entry_point
if __name__ == "__main__": import sys; entry_point(sys.argv)

You can also use the raise keyword to raise exceptions. The finally keyword is used to do some cleanup actions.

class B(Exception):
    pass

class C(B):
    pass

class D(C):
    pass

def exceptions():
    for cls in [B, C, D]:
        try:
            raise cls()
        except D:
            print("exception D")
        except C:
            print("exception C")
        except B:
            print("exception B")
        finally:
            print("put clean-up actions here")

def entry_point(argv):
    exceptions()
    return 0

def target(*args): return entry_point
if __name__ == "__main__": import sys; entry_point(sys.argv)

Attention

There is one special difference in the exception handling on “simple cases”. In RPython document, it says by default, code with no exception handlers does not raise exceptions. By supplying an exception handler, you ask for error checking. Without, you assure the system that the operation cannot fail. This rule does not apply to function calls: any called function is assumed to be allowed to raise any exception.

Todo

MesaPy added mandatory IndexError checks. Give some details here.