An Introduction to Serpent

Serpent is a programming language inspired by Python. Like Python, it has a simple, minimal syntax, dynamic typing, and support for object-oriented programming. Serpent also draws inspiration from XLisp, Squeak, SmallTalk, Ruby, and Basic. Why another language? Serpent is designed for use in real-time systems, especially interactive multimedia systems. Serpent is unique in providing the following combination of features:

I recommend Python to anyone who does not need these features. Serpent has some other differences from Python that reflect both the designer's personal tastes and limited resources:

Types

Serpent has the following primitive types:

and the following structured types:

and the following types that users ordinarily don't think of as types:

There are several other types used internally that are not visible to programmers. There is no Boolean type. Instead, the special value nil represents false, and the symbol t represents true. The global variables true and false are bound to t and nil and should be used for readability.

Syntax

Serpent syntax, like that of Python, uses indentation to indicate structure. Statements are grouped, not by brackets, but by indenting them to the same level, each statement on a separate line. If a statement "group" has just one statement, it does not need to go on a new line, but this is not yet implemented.

Expressions

Serpent expressions use the following operators, listed in order of precedence:

., [], **

(unary) +, (unary) -, ~, not

*, /, %, &

+, -, |, ^, <<, >>

<, <=, =, !=, >, >=, is, is not, in, not in

and

or

Other expressions are formed by calling methods and functions. Parameters are passed by value, and functions may have optional, keyword, and arbitrary numbers of parameters. Keyword parameters are passed by writing the keyword followed by "=", as in:

foo(size = 23)

Only parameters declared as keyword or dictionary parameters can be passed using the keyword notation. All other parameters are positional.

Any expression can be used as a statement.

Statements

Statements should be familiar to programmers, so they will be described here by example and at most a few comments. Note that because of the indentation-based grouping, semicolons are not used (except in the print statement for formatting).

Load

The "load" statement loads a file, executing each statement and compiling each function and class sequentially in the order read from the file. The file may contain its own "load" statements, which may be immediate commands at the top level or run-time load commands excuted within a function or method. The standard file extension is appended to the filename if no file is found without the extension.

load "myfile"

Assignment

identifier = expression
an_array[index] = expression
an_object.field_name = expression

Conditional

if condition1:
   
stmt1
   
stmt2
elif
condition2:
   
stmt3
   
stmt4
else:
   
stmt5
   
stmt6

Iteration

There are three forms of iteration constructs, starting with the familiar "while":

while condition:
   
stmt1
    stmt2

In the first "for loop" form, the "variable" must be declared as a local variable, and the "by" part is optional:

for variable = expression1 to expression2 by expression3:
   
stmt1
   stmt2

In the second "for loop" form, "expression1" must evaluate to an array, and "variable" is bound to each value in the array:

for variable in expression1 :
   
stmt1
    stmt2

At present, there are no equivalents to C's "break" and "continue" statements, but these will be added.

Print

print expr1, expr2 , expr3; expr4

The print statement writes characters to the standard output, which is the file whose handle is the value of global variable stdout. Each expression is printed. A comma outputs one space while a semicolon outputs no space. If there is no trailing comma or semicolon, a newline is output after the last expression in the print statement. A print statement has the value nil.

Return

return expression

The expression is optional; nil is returned if no expression is provided.

Exceptions

There is no exception mechanism, but one will be added.

Declarations

Serpent functions and classes are created by declaration. Within classes, member variables and methods are declared. Within functions, local variables are declared. Global variables do not need to be declared. Symbols and global variables are equivalent: every symbol has a slot to hold the value of a global, and every global is implemented by creating a symbol.

Parameter Lists

Simple, positional parameters are declared in the parameter list by simply naming them with comma separators:

def foo(p1, p2, p3):

Parameters can also be specified as optional (the parameter can be omitted, a default value can be provided), keyword (the formal parameter is named by the caller, the parameter may have a default value), rest (there can only be one "rest" parameter; it is initialized to an array containing the value of all left-over actual positional parameters), dictionary (there can only be one "dictionary" parameter; it is initialized to a dictionary containing the values of all left-over keyword parameters), and required (standard positional parameters).

def bar(p1, optional p2 = 5, keyword p3 = 6, rest p4, dictionary p5):

This function could be called by, for example:

bar(1, p3 = 3, d1 = 4), or

bar(1, 2, 3, 4, 5)

Functions

def foo(p1, p2, p3):
    var x
    stmt1
    stmt2
    return expression

Functions return the value of the last statement if there is no return statement. Remember that statements may be expressions, allowing a functional style:

def add1(x):
    x + 1

Local Variables

As shown above, local variables are declared using "var". Locals may be initialized, and multiple locals can be declared with a single "var" declaration. The declaration may occur anywhere in the function, but it must occur before the first use of the variable.

var x = 1, y = 2

Classes

class myclass(superclass):
    var instance_var
    def method1(p1):
        instance_var = p1

Classes specify instance variables (without initialization) and methods, which look just like function declarations except they are nested within the "class" construct. A class may inherit from one superclass. Within a method, the keyword "this" refers to the object. To create an object of the class, use the class name as a constructor:

x = myclass(5)

When an object is created, the init method, if any, is called, and parameters provided at object creation are passed to init. The init method return value is ignored, so it is not necessary to explicitly return this.

Member variables may be accessed directly using "." as in x.instance_var.

Built-in Functions

array(n)
create array of length n, each element initialized to nil
dict(n)
create an empty dictionary expected to grow to size n (n is a hint)
abs(x)
absolute value of integer or float
int(x)
conversion to 64-bit integer from a float
float(x)
conversion to 64-bit double from an integer
pow(x, y)
x to the power y, x and y are floats
x ** y
x to the power y, x and y are floats
x | y
bitwise or of x and y (two integers)
x ^ y
bitwise exclusive or of x and y (two integers)
x & y
bitwise and of x and y (two integers)
x << n
x shifted n bits left (two integers)
x >> n
x shifted n bits right (two integers)
~x
the bits of integer x inverted
len(s)
length of s, an array or string
min(s)
minimum value in s, an array of numbers
max(s)
maximum value in s, an array of numbers
cos(x)
cosine of x, a float
sin(x)
sine of x, a float
tan(x)
tangent of x, a float
exp(x)
natural exponent of x, a float
sqrt(x)
square root of x, a float
rem(n, m)
remainder of n divided by m, two integers
random()
random float from [0 to 1)
s.append(x)
extend sequence s by element or sequence x
s.unappend()
remove and return last element of s
s.count(x)
count x's in s
s.index(x)
index of first x in s
s.insert(i, x)
insert x as new ith element
s.remove(x)
find and remove first x
s.reverse()
reverse order of sequence s
s.sort()
sort elements of s
a.clear()
remove all items from a
a.copy()
shallow copy of a
a.has_key(k)
t if k is key in a
a.keys()
keys of dictionary a
a.values()
values of dictionary a
a.get(k [, f])
gets item in a with key k (f is returned if no item found, None is returned if no f is given)
f.close()
close a file
f.flush()
flush a file
f.fileno()
file number
f.read([size])
read all bytes from f. If size is specified, read up to size bytes.
f.readline()
read line of characters including newline from f
f.readlines([sizehint])
read lines and return in list (if sizehint is given, then read about that many bytes rather than reading to eof)
f.seek(offset, whence)
position in file (whence = 0 means absolute, 1 means relative, 2 means relative to the end)
f.tell()
return current position in file
f.write(str)
write string to file
f.writelines(list)
write strings to file
f.closed()
boolean status
f.mode()
string that file was opened with
f.name()
string that file was opened with
chr(i)
convert int to ascii string
hex(i)
convert int to hex string
oct(i)
convert int to octal string
hash(object)
return a hash value
id(object)
address of the object as an integer
intern(string)
convert string to atom
isinstance(object, class)
is object a direct or indirect instance of class?
issubclass(class1, class2)
is class1 a direct or indirect subclass of class2?
open(filename, mode)
file open, mode is a string (see fopen() in C stdio library)
ord(c)
inverse of chr(), returns an integer
repr(object)
machine readable string representation of object
round(x, n)
round x to n digits
str(object)
string representation of object (not necessarily machine readable)
type(object)
return type of object (an atom)
flatten(array)
convert array of strings to one string
strcat(a, b)
concatenate two strings
subseq(s, start, end)
subsequence of string or array
isupper(s)
is character in A:Z?
islower(s)
is character in a:z?
isalpha(s)
is character in A:Z,a:z?
isdigit(s)
is character in 0:9?
isalnum(s)
isdigit(s) || isalpha(s)?
toupper(s)
convert letters to upper case in s
tolower(s)
convert letters to lower case in s
find(string, pattern [, start [, end])
search string for pattern contained within start:end, no match returns -1
exit()
stop execution
frame_previous(frame)
return previous stack frame
frame_variables(frame)
return dictionary of variables and their values
frame_pc(frame)
program counter of frame
frame_method(frame)
method name of frame
frame_class(frame)
class of frame
runtime_exception_nesting()
nesting level of exceptions
frame_get()
get current frame.
trace(n)
Set trace mode.  Bit 0 controls machine tracing: instructions are disassembled and printed as they are executed, and the contents of the stack are displayed. Bit 1 controls compiler tracing: tokens are printed as they are parsed, and other debugging output related to the compiler and code generation are printed.

When a built-in operation encounters the wrong types and the first argument is an object, then the call is converted into a method lookup. (This is not implemented for all primitives yet.)

It is illegal to define a global function with the name of a built-in function.

Serpent does not have first-class functions. Instead, functions are represented by atoms (call the corresponding global function) or by objects (call a method of the object).

Aura Extensions

Serpent has additional statements to support Aura message passing:

connect object-id from object-id
connect
object-id to object-id
set
attribute to value after delay
object-id <- set attribute = value at timestamp
set attribute to value

As illustrated by these examples, the "<-", "at", and "after" parts are optional. Attributes are named by symbols. A Serpent dictionary maps a symbol to the corresponding Aura object, and values are converted to match the type of the attribute.

The classes "Aura_object" and "Aura_active" are built-in and may be subclassed. When a subclass is defined, a new Aura prototype is created, the class name (as a symbol) is added to the Serpent/Aura dictionary with a reference to the prototype, and the Aura prototype is initialized to forward messages to the new class.

Subclasses of "Aura_object" and "Aura_active" may have methods defined by the following form:

on attribute:
   
statements

This declares a method to be called when the attribute is set (the method is named "_on_" appended to the attribute name. An instance variable is also declared if it does not already exist.

A dictionary is used to specify the interface between Aura messages and the Serpent object. The dictionary is stored as the instance variable aura_interface. The dictionary is a mapping from attributes to arrays with the following elements:

To build the aura_interface dictionary, every class should provide a method named build_aura_interface that constructs the interface dictionary. This method is called when the class is defined in order to create the prototype object.