A Serpentine Compiler

This package directory contains the modules that implement a compiler for a Python-like language, described briefly in the Introduction to Serpentine guide.

The Include directory contains a subset of the class definitions needed for programs to use the Android and Java APIs.

See the Examples/Serpentine directory, located from the root directory of this distribution, for examples that can be built for devices running the Dalvik virtual machine. The build scripts for these examples perform the work of invoking the compiler and other tools, creating packages and signing them.

To create a key and certificate for signing packages see the Creating Keys and Certificates documents, also located from the root of this distribution, for instructions.

Limitations

The Serpentine language is not the Python language. The compiler uses the Python compiler module to parse source files, so the syntax of the language is a subset of that found in Python, but the semantics of the code are different in many places. This means that programs written in Python, even if they can be compiled, may well behave differently. Some obvious limitations of the language are listed below.

Only methods are supported. Top-level functions in a module are not supported.

Classes can only have one base class each. Since we follow closely the features of the Dalvik virtual machine, classes may implement interfaces provided by additional classes.

The language is statically-typed in that a given name cannot be rebound to a different type. It may be possible to make this a little more flexible under certain circumstances, such as when rebinding occurs unconditionally after a certain point in a method and the new type has the same size as the old one.

Methods need to be decorated with the types of their arguments and return value unless they are reimplementations of existing methods with the same signatures. This is required for interoperability with the virtual machine and platform APIs. In theory, "internal" methods within an application written in this language could use some kind of marshalling scheme that takes care of the values passed between them, but this would add overhead and make the implementation more complex. The use of decorators allows us to implement method overloading, so we can write multiple __init__ methods, for example.

Integer and floating point numbers are implemented using primitive types with fixed sizes. By default, integer constants are stored as signed 32-bit integers (ints) and floating point constants are stored as signed, 64-bit, double precision, floating point values (doubles). These can be explicitly cast to other types when the API requires specific types, such as floats, but some degree of automatic casting is performed by the compiler. For example, operations such as addition and subtraction, are implemented in a way that causes operands to be automatically cast to the type with the highest precision that is used in the operation.

Lists and tuples are represented using Java's LinkedList class, dictionaries using HashMap and sets using HashSet. As a result, the types of values used in those collection must be homogeneous. So, for example, it is possible to create a list of View objects, but that list cannot contain instances of other types unless they are subclasses of the View class. It is possible to create a collection of generic Objects, but these would require careful casting when they are extracted from the collection.

Variables that are initially defined within loops (while or for) or conditional control flow structures (if, elif, else) are local to the suite (scope) they are defined in. This means that, for example, it isn't possible to define a variable for the first time in a loop and access its value after leaving the loop.

Only try...except handling is supported. Support for try...finally is being considered.

There is little support for Python types or built-in functions. Most applications will use Java types because they need to access the platform APIs, and will be expected to handle and process values of those types. Some built-in functions are understood by the compiler, and these will be replaced by code that does something similar to what the equivalent Python built-in would do.

Keyword arguments are not supported. A similar feature of the Java language, variable-length argument lists, might replace some use cases for keyword arguments, but these are not supported either. In some cases, using method overloading might be a suitable alternative.

Bytecodes Used by the Compiler

It may be useful to keep track of which bytecodes are generated by the compiler. This might allow us to create an alternative backend that defines the same bytecodes, but targets a different virtual machine. We do this by examining the expand methods of the classes in the macros module. Some of the macros are parameterised using bytecode classes passed in by the compiler, using the dictionaries later in the macros module, so we also need to include those in the list below.

Alternatively, since the bytecodes are really an implementation detail of the current Dalvik backend, we could use the macros themselves as a starting point for an alternative backend.