An Introduction to Serpent
Roger B. Dannenberg
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:
- Real-time garbage collection: Squeak has a pretty fast
generational scavenging collector, but Serpent does even better
with a parallel mark-sweep garbage collector.
- Multiple virtual machines: Multiple independent instances of
Serpent can run concurrently in one address space. This allows
each thread to have its own copy of Serpent and to rely on the
OS for scheduling and preemption.
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:
- Symbols (unique strings) are a primitive data type.
- Serpent does not have name spaces.
- Serpent is more of an expression language than Python: every
statement returns a value.
- Python has many large libraries.
Topics
Types
Serpent has the following primitive types:
- Integer -- a 64-bit signed integer (In serpent64, integers are
50 bits, two's complement, including the sign bit)
- Real -- a 64-bit IEEE double-precision floating point number
- String -- an immutable 8-bit (unicode in the future) character
string. Characters are represented by strings of length one.
- Symbol -- an immutable unique string with an associated global
value and global function
- File -- a handle for an open file
and the following structured types:
- Array -- a sequence of values of any type
- Dictionary -- an associative array
- Object -- an instance of a user-defined class
and the following types that users ordinarily don't think of as
types:
- Class -- a user-defined class
- Frame -- a stack frame
- Method -- a function implementation
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
Expressions Denoting Constants
Integer constants are normally familiar strings of decimal digits.
However, if the first digit is zero ("0"), the digit string is
interpreted as octal and must not contain "8" or "9". A hexidecimal
constant begins with "0x" or "0X" and uses "a"-"f" (or capitals
"A"-"F") as digits beyond "9".
Real constants are decimal numbers with a decimal point, e.g.
"56" is an Integer while "56." is a Real. Reals can also have an
exponent suffix denoted by "e" or "E" followed by an integer
exponent. "4.5e2" means 4.2 times 10 to the power of 2. If an
exponent suffix is present, the decimal point is optional.
String constants are any character string enclosed in double
quote (") characters. Within the string, two double quote
characters ("") represent a single double quote (") character (a
single double quote character would terminate the string.)
Alternatively, a double quote can be inserted into a string by
excaping it with a backslash character (\). The backslash
character is used with other characters to denote special
characters into strings. "\n" denotes the newline character (ASCII
code 10), "\t" denotes tab, "\\" denotes backslash (note that a
single backslash does not denote a backslash character), and "\r"
denotes the carriage return character (ASCII code 13).
There is no character type in Serpent. Instead, characters are
represented by strings of length 1.
Symbols are unique strings. Each symbol is stored in a system
dictionary, and symbols are associated with a global value and a
global function. Thus, every symbol is automatically a global
variable that can be assigned to, and global variables are
declared merely by being mentioned in the program. Symbol
constants are like strings, but single quotes are used as
delimiters. Although not recommended, symbols can contain double
quotes (no backslash is necessary for escaping this) and even
single quotes can be inserted using the backslash, e.g. 'symbol\'with\'single\'quotes'.
Expressions for Arrays and Dictionaries
An array is denoted by a comma-separated list of expressions
delimited by square brackets, e.g. [1, 'a', "hi"]
evaluates to an array with an Integer, Symbol, and String element
(in that order). Array expressions are constructed at run time
(unlike in Python) because the result is mutable. Therefore, if your
program needs a large constant array, e.g. a look-up table, you
should construct it once and assign it to a global variable. Then,
at the point where the array is used, use the global variable to
avoid recreating the table each time you need to use it.
A dictionary is denoted by a list of key/value pairs within curly
braces, e.g. {'top': 50, 'bottom': 100, 'left': 10, 'right':
90}. Note that the key/value pairs are separated by commas,
and a colon separates each key from its corresponding value. Also
note that keys are expressions that are evaluated; without the
quotes, a symbol such as 'top' would be evaluated as a global
variable named 'top' and raise an error if not initialized.
Expressions Using Operators
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 and Require
The "load" and "require" statements load 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" and
"require" 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"
require "myfile"
The difference between load and require is that
load always compiles and executes the contents of the file,
whereas require first checks to see if the file has been
loaded (or "required") previously. If so, the file is not loaded
again. Use load when the file contains a script of actions
that may be performed multiple times. Use require for
files that contain class and function definitions that should be
loaded at most one time.
A search path is used to locate source files. Under Windows, the
search path is stored in the registry as the SERPENTPATH value in
HKEY_LOCAL_MACHINE/SOFTWARE/CMU/Serpent. Under Linux and Mac OS X,
the environment variable SERPENTPATH holds the search path.
The search path is a list of paths separated by colons (:) in
Linux or OS X or by semicolons (;) in Windows.
Please read these installation notes
for more details on setting up a search path.
The search for a file begins by testing the supplied filename for
a ".srp" suffix. If none is found, ".srp" is appended to the
filename. Then the compiler tries to open the file in the load
directory. The load directory is current directory at the
time Serpent was started, or, if an initial file was specified on
the command line, the directory of that file. If the file is not
found in the load directory, the file is searched for sequentially
in each directory in the search path until the file is found.
A list of loaded files is kept in an array bound to the global
variable files_loaded. The only the filenames with
extensions are stored here. Therefore, when a require
command is executed, Serpent can check to see if the file has been
loaded without appending each path from the search path.
A list (array) of path names of the currently loading files is
kept on the global variable paths_currently_loading.
When a nested load begins, the new path is appended to the array,
and when the load completes, the path is popped from the end of
the array. An empty array indicates that no load is in progress.
Assignment
identifier = expression
an_array[index] = expression
a_dictionary[key] = 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" may already be declared
as a local variable. If not, a local variable is declared (so a
subsequent local declaration is not allowed). The "by" part is
optional and defaults to 1. The variable is initialized to the value
of expression1. The direction (up or down) depends on the value of
expression3 (zero or positive is "up", negative is "down"). For the
"up" case, the loop exits if expression1 is greater than or equal to
expression2. For the "down" case, the loop exits if expression1 is
less than or equal to expression2. (Expression2 is evaluated only
one time.) If the loop does not exit, the statements are evaluated
in sequence. Then expression3 (which is only evaluated once) is
added to variable. This cycle of test, execute statements, and add
is repeated until the loop exits. It is not an error if expression3
is zero.
Note that "for i = 0 to 4" loops 4 times with i equal to 0, 1, 2, 3,
while "for i = 4 to 0 by -1" sets i to 4, 3, 2, 1. Thus, iterating
the indexes of an array in the forward direction is accomplished in
a straightforward fashion: "for i = 0 to len(the_array)" while
iterating in the reverse direction is not so obvious: "for i =
len(the_array) - 1 to -1 by -1."
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, and
"index" is bound to the index of "variable" in "expression1". The
"at" part is optional, in which case the index or loop interation
count is not available unless you add extra code to track it.
for variable at index in expression1:
stmt1
stmt2
At present, there are no equivalents to C's "break" and "continue"
statements, but these may be added in the future. An alternative to
"break" is to put the loop in a function and return rather than
break. An alternative to "continue" is to put the rest of the loop
body (that would be skipped by "continue") in a conditional ("if")
construct.
display label, expr1, expr2,
expr3
The display statement is very handy for debugging and similar to the
print statement. The "label", which should be a quoted
string, is printed followed by a colon. Each expression is printed twice,
first as if it were quoted, folllowed by an equal sign, and
second as if it were an ordinary expression that is evaluated to
yield a value. Expressions are separated by commas. For
example,
display "in my_sort", x, y
might print:
in my-sort: x = 1, y = foo
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 may be added in the future.
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 required (standard positional
parameters), 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),
and dictionary (there can only be one "dictionary" parameter; it is
initialized to a dictionary containing the values of all left-over
keyword parameters).
def bar(p0, required 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)
For optional and keyword parameters, a default value may be
provided. The syntax is "= expr" where expr
is either a constant (a number, a string, or a symbol) or a global
variable identifier. If the value is a global variable identifier,
the value of that variable at compile time is used. If the
value changes at run-time, this will have no effect on the default
parameter value. The expr may not be an expression involving
functions or operators.
Formal parameters must be declared in the order: required,
optional, keyword, rest, dictionary.
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. All instance variables and methods are inherited and
fully accessible from within or from outside the class.
Within a method, the keyword this refers to the object.
You can call methods in the class by writing this.some_method(some_parameter),
but you can also simply write some_method(some_parameter),
and if some_method is defined in the class or inherited
from a superclass, it will override any global function by the same
name and will be invoked as a method on this.
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.
(init may be inherited from a superclass). The init
method return value is ignored, so it is not necessary to explicitly
return this. Within the init method of a
subclass, there should ordinarily be a call to initialize the
superclass. The special variable super refers to the new
object being instantiated as if it were an instance of the
superclass. (In the same way that this refers to the
current object in the current class, super refers to the
current object in the superclass). To call the superclass's
initialization method use ordinary method invocation syntax and with
parameters appropriate to the superclass's init method. For
example, if the superclass is myclass, and the init
method of myclass takes one argument, then there should be
a call that looks like super.init(5). The return value of
this call should be ignored.
Member variables may be accessed directly using "." as in x.instance_var.
Methods are invoked in a similar fashion: x.some_method(parameters).
Methods defined for a class can have the same name as methods in
a superclass. These methods will override the superclass methods.
You can access inherited methods (even ones that are overridden by
methods defined in the current class) by refering to the current
object as super. Thus super.meth(parameters)
will search for meth starting in the superclass method
dictionary, ignoring any definition of meth in the
current class. (This is just a more general view of the "trick"
used to call a superclass's init method explained
above.)
Built-in Functions and Methods
Most built-in functions and methods are known to the compiler, which
checks and enforces the parameter counts and translates calls to
efficient virtual machine instructions. You can define methods with
matching names, but the number of parameters must match. The
implementation may assume the "object" for some built-in methods is
an array, file, or string. This is an implementation restriction
that should be corrected.
Functions and Methods by Category
- Math
- **, abs, cos, exp, idiv, int, log, pow, real, within,
max, min, random, rem, round, sin, sqrt, tan
- Logical (Bit) Operations
- &, ^,
~, |, <<, >>
- Strings
- chr, count, find, hash, hex, insert, int, isalnum,
isalpha, isdigit,
islower, isspace,
isupper, last,
len, oct, ord, repr, reverse, set_len,
str, strcat, subseq, tolower,
toupper, uninsert
- Arrays and Dictionaries
- (create new) array, append, clear,
copy, count, flatten, index,
insert, last,
len, set_len,
reverse, sort,
resort, remove,
subseq, unappend,
uninsert
- Dictionaries
- clear, (create new) dict(ionary), get,
has_key, keys,
values
- File Operations
- close, closed,
getcwd, display,
flush, fileno,
isdir, listdir,
mkdir, mode, name open, print, read, readline, readlines,
readvalue, rename,
rmdir, seek, set_real_format_precision,
set_real_format_type tell, system,
token, unlink,
unread, write,
writelines
- Objects, Symbols and Types
- id, intern, isarray, isatom,
isinstance, isinteger, isnull, isnumber,
isobject, isreal,
issubclass, issymbol,
isstring, sizeof,
type, get_slot,
set_slot, symbol_value, set_symbol_value
- Functions and Methods
- funcall, apply,
send, sendapply
- Debugging and Other Commands
- display, exit,
frame_previous, frame_variables, frame_pc, frame_method, frame_class, runtime_exception_nesting,
frame_get, error,
trace, dbg_gc_watch,
dbg_cycles
Built-in Functions and Methods
- abs(x)
- absolute value of integer or float
- x & y
- bitwise and of x and y (two integers)
- s.append(x)
- extend sequence s (an array or string) by element x; note: use
+ operator to append two sequences; if s is a string, it is not
modified but a new string is constructed
- apply(function, argarray)
- call function (a symbol) with arguments taken from an array
(there is no provision for sending keyword or dictionary
parameters in this way)
- array(n)
- create array of length n, each element initialized to nil
- chr(i)
- convert int, an ascii character code, to a one-character ascii
string; if i is 0, the string is empty
- a.clear()
- remove all items from a, a dictionary or array
- f.close()
- close a file
- f.closed()
- boolean status
- a.copy()
- shallow copy of a
- cos(x)
- cosine of x, a float
- s.count(x)
- count x's in s
- getcwd()
- return current working directory name
- dbg_gc_watch(x)
- Turns on debugging information in garbage collector for
address x. Only one address may be watched. This only
has an effect when Serpent is compiled with the flag _GCDEBUG.
- dbg_cycles()
- Returns an integer cycle counter that counts the number of
Serpent instructions interpreted.
- dict(n)
- create an empty dictionary expected to grow to size n (n is a
hint)
- error(s)
- generate a run-time error with message s. This normally
invokes the debugger.
- x ^ y
- bitwise exclusive or of x and y (two integers)
- exit()
- stop execution
- exp(x)
- natural exponent of x, a float
- f.fileno()
- file number
- find(string, pattern [, start [,
end])
- search the substring of string from start to end-1 (inclusive)
for pattern. Default is to use the entire string. The match must
include all of pattern, in which case the offset of pattern
within string is returned; otherwise no match returns -1.
- flatten(array)
- convert array of strings to one string
- f.flush()
- flush a file
- frame_class(frame)
- class of the method of the frame.
- frame_get()
- get current frame.
- frame_method(frame)
- method name of frame
- frame_pc(frame)
- program counter of frame
- frame_previous(frame)
- return previous stack frame
- frame_variables(frame)
- return dictionary of variables and their values
- funcall(function, arg1, arg2, ...)
- call function (a symbol) with arguments
- a.get(k [, f])
- gets item in a with key k (f is returned if no item found, nil
is returned if no f is given)
- get_slot(object, symbol)
- get the value of the instance variable named by symbol from
object
- set_slot(object, symbol, value)
- set the instance variable named by symbol in object to value
- a.has_key(k)
- t if k is key in a
- hash(object)
- return a hash value
- hex(i)
- convert int to hex string
- id(object)
- address of the object as an integer
- idiv(i, j)
- integer division: i divided by j
- s.index(x)
- index of first x in array s (see find function for string
searching)
- s.insert(i, x)
- insert x as new ith element of an array or string; if i is
negative, insert at len + i; use append to insert at end of s.
If s is an array, one element is inserted. If s is a string, x
must be a string and the entire string is inserted.
- int(x)
- conversion to 64-bit integer from a float (by truncation) or
string
- intern(string)
- convert string to symbol
- isalnum(s)
- isdigit(s) or isalpha(s)? (Only the first character of s is
tested.)
- isalpha(s)
- is character in A:Z,a:z? (Only the first character of s is
tested.)
- isarray(a)
- is a of type array?
- isatom(x)
- is x a symbol, nil, integer, or real?
- isdigit(s)
- is character in 0:9? (Only the first character of s is
tested.)
- isdir(path)
- return true iff path names a directory
- isinstance(object, class)
- is object a direct or indirect instance of class?
- isinteger(n)
- is n of type integer? (Note that isinteger(3.0) is false
because 3.0 is of type real.)
- islower(s)
- is character in a:z? (Only the first character of s is
tested.)
- isnull(x)
- is x null, equivalent to x == nil
- isnumber(n)
- is n an integer or real?
- isobject(obj)
- is obj an object (an instance of a class)?
- isreal(n)
- is n of type real? (Note that isreal(3.0) is true
because 3.0 is of type real.)
- isspace(s)
- is character a space, tab, vertical tab, form feed, carriage
return, or newline? (Only the first character of s is tested.)
- issubclass(class1, class2)
- is class1 a direct or indirect subclass of class2?
- isupper(s)
- is character in A:Z? (Only the first character of s is
tested.)
- issymbol(s)
- is s a symbol (nil is not a symbol)
- isstring(s)
- is s of type string?
- a.keys()
- keys of dictionary a
- s.last()
- return last element of s, an array or string
- len(s)
- length of s, an array or string
- listdir(path)
- Obtain a directory listing of path (a string). The
result is an array of strings, or nil if the path does not name a directory.
- log(x)
- natural log of x, a float
- max(s)
- maximum value in s, an array of numbers
- min(s)
- minimum value in s, an array of numbers
- mkdir(path)
- make the specified directory
- f.mode()
- string that file was opened with
- f.name()
- string that file was opened with
- ~x
- the bits of integer x inverted
- oct(i)
- convert int to octal string
- open(filename, mode)
- file open, mode is a string (see fopen() in C stdio library)
- x | y
- bitwise or of x and y (two integers)
- ord(c)
- inverse of chr(): returns the integer that is the ascii code
for the first character in c, a string; if c is empty, zero is
returned
- 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
- random()
- random float from [0 to 1)
- f.read([size])
- read up to size (default 4096) bytes from file f. Returns
empty string if end-of-file is reached.
- f.readline([size])
- read line of characters including newline from f, limit number
of bytes to size (default 255). Return nil if end of file is
reached.
- f.readlines([sizehint])
- read lines and return in list (if sizehint is given, then read
about that many bytes rather than reading to eof). Long lines
(> 255 chars) may be split.
- f.readvalue()
- read and parse a constant (integer, real, string, or symbol).
Note that dictionaries and arrays are not parsed. Whitespace is
skipped. Anything that starts with something other than a digit,
a period, a single quote, or a double quote is parsed as a
symbol (terminated by whitespace).
- real(x)
- conversion to 64-bit double from an integer or string
- rem(n, m)
- remainder of n divided by m, two integers
- a.remove(x)
- remove item with key x from dictionary a, or remove first
element equal to x from array a
- rename(oldpath, newpath)
- rename a file. Returns nil on success, or errno on error.
errno is system dependent.
- repr(object)
- machine readable string representation of object
- s.resort([f])
- if all but the last element of array s are sorted in decreasing
order, this will sort s (into decreasing order). Use
this function to implement a priority queue. Insert by appending
an element and calling resort(). Remove the lease element by
calling unappend(). Elements of s may be arrays, in which case
the first element of each array is the sort key.
- s.reverse()
- reverse order of sequence s, an array or string.
- rmdir(path)
- remove the directory (if empty) named by path. Returns nil on
success, or error code.
- round(x[, n])
- round x to n digits to the right of the decimal point; returns
Real if n > 0, otherwise Integer; n can be negative. Rounding
is performed by scaling by 10^n, rounding to an integer, and
scaling by 10^-n. Rounding is performed by adding 0.5 to
positive numbers or -0.5 to negative numbers, then truncating
toward zero. If n is omitted, round x to an integer, and if x is
an integer, return x.
- runtime_exception_nesting()
- nesting level of exceptions
- f.seek(offset, whence)
- position in file (whence = 0 means absolute, 1 means relative,
2 means relative to the end)
- send(object, method, arg1, arg2, ...)
- invoke method (a symbol) on object with the given arguments
- sendapply(object, method,
argarray)
- invoke method (a symbol) on object with the arguments given in
an array (no provision is made for keyword and dictionary
parameters)
- s.set_len(n)
- set the length of array s to n; truncate or append nil as
necessary; truncate string s or pad with spaces to make new
string of length n
- x << n
- x shifted n bits left (two integers)
- x >> n
- x shifted n bits right (two integers)
- set_real_format_precision(precision)
- set the precision for printing real numbers (doubles). The
precision is interpreted as in sprintf in the standard C
library. The previous value (an integer) is returned if the
parameter is valid; otherwise, nil is returned. The initial
value is 6. The precision must be in the range 0 through 99.
- set_real_format_type(letter)
- set the format type for printing real numbers (doubles). The
letter must be one of "e", "g", or "f" and is interpreted as in
sprintf in the standard C library. The previous value is
returned if letter is valid; otherwise, nil is returned. The
initial value is "g".
- set_symbol_value(s, v)
- set the global value of a symbol s to v.
- sin(x)
- sine of x, a float
- sizeof(object)
- size in bytes of actual memory used by object, not counting
the reference to the object. Since some objects are stored in
the reference, some objects have a size of zero.
- s.sort([f])
- sort elements of array s in increasing order, or use f
(a symbol) to compare using a global function; f(x, y) returns
true iff x should be after y in the sorted sequence. Elements of
s may be arrays, in which case the first element of each array
is the sort key.
- sqrt(x)
- square root of x, a float
- str(object)
- string representation of object (not necessarily machine
readable)
- strcat(a, b)
- concatenate two strings
- subseq(s, start [, end])
- a subsequence of string or array, returns a new string or
array consisting of elements from start through end-1. The
default value for end is len(s). If start and/or end is
negative, it is interpreted as len(s)-start or len(s)-end,
respectively. E.g. subseq([0,1,2,3],-2,-1] is [2].
- symbol_value(s)
- get the global value of a symbol.
- tan(x)
- tangent of x, a float
- system(s)
- invoke the shell using command s, returning the exit status of
the shell (may not be implemented on Windows, see Unix system()
for details)
- f.tell()
- return current position in file
- f.token()
- skip over white space in file f; read string of non-whitespace
characters terminated by a whitespace. The maximum token length
is 255 characters.
- tolower(s)
- convert letters to lower case in s
- toupper(s)
- convert letters to upper case in s
- 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.
- type(object)
- return type of object (a symbol)
- s.unappend()
- remove and return last element of s, an array
- s.uninsert(i [, j])
- remove s[i] (through s[j-1]), where s is an array or string.
- unlink(filename)
- unlink (delete) a file. Returns nil on success, or errno on
error. errno is system dependent.
- f.unread(c)
- push a single character (a string of length 1) back to an
input file
- a.values()
- values of dictionary a
- within(x, y, epsilon)
- true if x is within epsilon of y (three reals)
- f.write(str)
- write string to file
- f.writelines(list)
- write strings to file
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 symbols (call the corresponding global
function). A planned extension is to let objects represent
functions. When an object is applied to a parameter list, a
special method (possibly 'call') is invoked on the object.
Special Variables
- command_line_arguments
- An array of strings from the command line.
- dbg_trace_output_disable
- In the debug version (compiled with _DEBUG defined), Serpent
copies every output byte to the trace() function, a platform
dependent debugging output function. If you set this variable to
non-nil, this trace output is disabled. This can make the
program run faster in debug mode.
- files_loaded
- An array of files that have been loaded so far. The require
statement uses this list to decide whether to load a file.
- paths_currently_loading
- An array of strings naming the paths of the set of nested
loads in progress. If you want to load a file relative to a
source file, you should save paths_currently_loading.last()
in a statement that is executed while the file is being loaded.
- runtime_exception
- If defined as a global function, this function will be called
by Serpent to handle execution exceptions within Serpent code.
See debug.srp, which uses this feature to implement a simple
debugger.
Standard Libraries
Serpent comes with a number of files in the lib directory,
and normally this directory should be on the Serpent search path.
Serpent libraries are evolving with use. Feel free to contribute new
libraries and methods of general utility. The list below is intended
as a rough guide. Please read documentation in the source files
themselves for more detail.
- debug
- Serpent has a primitive debugger. When you load the debug
library, errors are passed to some library code that can print a
stack trace and examine some variable values. Also, you can exit
back to the command line prompt. Type RETURN to the debugger for
a summary of commands.
- file sets, find files, etc.
- The File_set
class represents a set of file paths. Find_files is a subclass that can search a
directory tree for files that match certain criteria. Find_extensions is a
subclass of Find_files
that can search a directory tree for files with a certain
extension, e.g. ".jpg".
The lib/files.srp
file has these classes and other handy file and path utilities
for manipulating path names.
- regression
- The Regression class can be used to perform (linear)
regression.
- statistics
- The Statistics class can be used to perform simple
statistics such as mean and standard deviation. It can also
buffer a set of statistical samples and save them to a file.
- stredit
- The String_edit class is intended to edit files,
e.g. perform global query-and-replace operations. The original
purpose was editing templates to automatically generate HTML as
part of the serpent software release process, but any file or
string editing might make use of this class.
- strparse
- The String_parse class is intended to parse data
files of all kinds one line at a time. You pass in a string you
want to parse, and then sequentially advance through the string
skipping spaces, reading words, integers, floats, special
characters, or whatever. Highly recommended for all your text
input needs.
- utils
- This is a grab-bag of handy functions, including: irandom
to get random integers, uniform to get uniformly
distributed real numbers, change_file_suffix to
replace a file name suffix, file_from_path to extract
the file name from a full path, and pad to pad a
string to a given length.
- readcsv
- This code, currently in programs,
not lib, can read
comma-separated value files from Excel or other spreadsheets.
Thread Interface and Functions
Serpent has a very primitive interface to allow multiple threads. It
is strongly recommended that you do not depend heavily on
this facility. It was created to support a course and is not
intended for "real" use. The facility is limited to the creation of
one additional thread that loads a file and then periodically
calls a function. The two threads run in a single process but have
no shared variables. The only possibility of communication is
through message queues. Two queues are set up and initialized to
hold up to 100 strings of up to 100 characters each. Only strings
may be sent and received. To build Serpent with these thread
functions, link Serpent with the objects obtained from
threadcreate.cpp and threadhack.cpp.
- thread_create(period, filename, mempool)
-
- create a new instance of the Serpent virtual machine
- allocate an initial memory pool (mempool is a hint
for the size of the initial memory pool. It is currently
ignored. Future implementations will interpret a value of 0
to indicate the default memory pool size, which is currently
1MB.)
- load the file indicated by filename (a string)
- set the variable thread_id in the new thread to an
integer (see below)
- return the new thread's thread_id
- Once a new thread is created and it has finished
successfully loading/compiling/executing commands from filename,
the thread uses PortTime to wake up every period
milliseconds and call porttime_callback(ms), where
ms is the current time in milliseconds.
- thread_send(thread_id, string)
- enqueue string for receipt by the other thread.
Return the number of strings sent (0 if the queue is full, 1 if
the send is successful.) thread_id should be the thread
id of the caller, not the destination. (Use the global variable
thread_id.
- thread_receive(thread_id)
- check the queue and if there is a message from the other
thread, return the message as a string. If there is no message,
"" (the empty string) is returned. Note that it is
possible to send an empty string, but this will be
indistinguishable from no message (an empty queue). thread_id
should be the thread id of the caller, not the thread that sent
the message. (Use the global variable thread_id).
Network Interface and Functions
If Serpent is compiled with NETWORK defined, then some basic
communications functions are built-in. They are defined in this
section.
- server_create(portno)
- Create a socket, bind it to portno, and listen for
client connections. A socket descriptor (number) is returned. -1
is returned to indicate an error.
- server_accept(socket)
- Accept a client request on socket, which was
created by server_create. If the return value is nil,
then no client request is pending (this is a non-blocking call).
If the return value is -1, an error occurred. Otherwise, the
return value is socket that can be used to read the client
request. Under Windows, calling this function initiates a
blocking accept call in another thread. In order to
call server_connect or socket_receive, you
must continue (re)calling server_accept until it
returns something other than nil. To terminate the
blocked accept, try closing the server socket and then
re-calling server_accept to read the error return.
- server_connect(name, portno)
- Establish a connection with a server using its name and port
number. The result is a socket, nil if no result is
available yet (this is a non-blocking call), or -1 if there is
an error. If nil is returned, you must re-call server_connect
until a non-nil result is obtained.
- socket_receive(socket, n)
- Read up to n bytes of data from socket.
Returns a string if successful, nil if no input is
available (this is a non-blocking call), and otherwise returns
an integer error code. The socket is normally obtained from server_accept
or server_connect. If nil is returned, the
read is still in progress, and you must re-call socket_receive
until a non-nil result is obtained.
- socket_send(socket, string)
- Send a string to the given socket, which is normally obtained
from server_accept or server_connect.
Returns the number of bytes sent or -1 on error.
- socket_close(socket)
- Close a socket.
Windows Shell File Operations
The Win32 version of Serpent includes an interface to "Shell File
Operations" that perform tasks such as copying directories. These
functions are:
- sfo_copy_directory(from_path, to_path)
- Copy a directory named by from_path to to_path
(both arguments are strings).
- sfo_delete(path)
- Delete a file or directory named by path (a string).
- create_directory(path)
- Create a directory named by path (a string).
- local_time()
- Return the local time as an array of integers, organized as
follows: [seconds, minutes,
hours, day-of-month, month, year, day-of-week, day-of-year,
dst], where dst
is 1 for daylight savings time and 0 otherwise.
Open Sound Control
Serpent includes a simple interface to the liblo implementation of Open Sound Control (OSC).
Note that while there are functions to send many different types,
the server code currently defined in Serpent only allows you to
receive integers and floats (this is fairly easy to extend if
necessary). The functions are:
- osc_server_init(port, debug_flag)
- Initialize an Open Sound Control server, using the specified port
string (e.g. "7770"). If debug_flag is non-nil,
debugging information may be printed. (Exactly what is printed
is intentionally not specified.) It is an error to open more than one OSC server.
On success, OSC_SERVER_SUCCESS (= 0) is returned.
Upon failure, an integer error code less than zero is returned.
If the OSC server is already initialized and this call is made
from the same thread as the initial one, OSC_SERVER_ALREADY_OPEN
is returned. However, if an attempt is made to initialize the
server from a second thread, an error is raised, stopping
execution of the thread and calling the debugger or resetting
Serpent to the top level command prompt.
- osc_server_multicast_init(group, port, debug_flag)
- Initialize an Open Sound Control server and join a multicast
group. Both group and port parameters are
strings. Otherwise, behavior is identical to osc_server_init
described above.
- osc_server_method(path, types, obj, method)
- After initializing the OSC server, incoming messages will be
examined, but only messages that match a registered path result
in any action. Use this function to register a path, a
string (e.g. "/slider"). The types of the arguments are
given by types, a string (e.g. "if"). Currently, only
integer and float types are implemented, indicated by "i" and
"f", respectively. When a message matching path is
received, arguments are coerced into the specified types and a
handler specified by method is called. If obj
is non-nil, it must be an Object with the given method.
Otherwise, method is called as a global function. The
first method parameter will receive the (string) value
of the path parameter. The remaining method
parameters must be compatible with types (e.g. either
the count matches or method has optional or
rest parameters).
- osc_server_poll()
- Since Serpent is single-threaded, the server must be
explicitly activated by calling this function, which receives
all pending messages, parses them, and dispatches any handlers
registered using osc_server_method. Normally, 0 is
returned, but upon failure, an integer error code less than zero
is returned. You should call this function at a rate consistent
with the rate of OSC messages and your tolerance for latency.
- osc_server_finish()
- Close the OSC connection, freeing any resources that were
allocated for the server.
- osc_create_address(host, port)
- Initialize an OSC client to send to a server denoted by the host
and port strings. If host is nil,
the host is the local host. Returns an address identifier (a
non-negative integer) on success, or a negative integer error
code on failure. Note: Serpent can manage only a limited number
of addresses, so create an address once and save it for
use with osc_send. Do not call osc_create_address
every time you call osc_send.
- osc_send_start()
- Prepare to send a message. This should be followed by
appending arguments and then sending to a path. It is an error
to call osc_send_start() a second time with no
intervening call to osc_send.
- osc_add_double(x)
- Append a double argument x to the current message
(which must have been created by osc_send_start()).
- osc_add_float(x)
- Append a float argument x to the current message
(which must have been created by osc_send_start()).
- osc_add_int32(i)
- Append a 32-bit integer argument i to the current
message.
- osc_add_int64(i)
- Append a 64-bit integer argument i to the current
message.
- osc_add_string(s)
- Append a string argument s to the current message.
- osc_add_timetag(i)
- Append a 64-bit integer time tag argument i to the
current message.
- osc_add_symbol(s)
- Append a symbol argument s to the current message.
- osc_add_char(c)
- Append a character (represented in Serpent by a one-character
string) argument c to the current message.
- osc_add_midi(cmd, d1, d2)
- Append a Midi message (represented in Serpent by 3 integers)
argument to the current message.
- osc_add_bool(b)
- Append a boolean argument b to the current message.
- osc_add_nil()
- Append a nil argument to the current message.
- osc_add_infinitum()
- Append an "infinitum" argument to the current message.
- osc_send(address, path)
- Send the constructed arguments to the address, a
value returned by osc_create_address. The message
is sent to the path, a string. You must call osc_client_start()
and append arguments to send another message. A return value
of -1 indicates that the address parameter is invalid.
This API is intentionally small and simple. If users find OSC
useful and need additional features, feel free to contact the
author about extending this specification. Also, any serious OSC
client should probably write a Serpent library to at least
implement something like osc_send(path, types, arg1, arg2,
...) to construct and send an OSC message.
ZeroMQ
ZeroMQ is a message passing library and concurrency framework.
When Serpent is linked with ZeroMQ, the following functions are
available.
- zmq_init()
- Initialize the ZeroMQ library and a ZeroMQ context to be used
by subsequent ZeroMQ function calls.
- zmq_open_reply()
- Create a socket for reply messages. To use a reply socket, you
first receive a message (which was sent from a request socket)
and then send a message. ZeroMQ assumes a strict alternation of
receive and send. Null is returned on error, otherwise a socket
is returned. You should next connect or bind the socket.
- zmq_open_request()
- Create a socket for request messages. To use a request socket,
you first send a request message (which is delivered to a reply
socket), then receive the reply. ZeroMQ assumes a strict
alternation of send and receive. Null is returned on error,
otherwise a socket is returned. You should next connect or bind
the socket.
- zmq_open_publish()
- Create a socket to publish messages. To use a publish socket,
you simply send messages where the initial bytes are the topic
(see zmq_set_filter). ZeroMQ assumes the socket is
send-only. Null is returned on error, otherwise a socket is
returned. You should next connect or bind the socket.
- zmq_open_subscribe()
- Create a socket to subscribe to messages. To use a subscribe
socket, you simply receive. ZeroMQ assumes the socket is
receive-only. Null is returned on error, otherwise a socket is
returned. You should next connect or bind the socket and set the
filter. By default, all messages are filtered so no messages are
received.
- zmq_open_push()
- Create a socket for push messages. To use a push socket, you
simply send messages to it. ZeroMQ assumes the socket is
strictly send only. Null is returned on error, otherwise a
socket is returned. You should next connect or bind the socket.
- zmq_open_pull()
- Create a socket for pull messages. To use a pull socket, you
simply receive the reply. ZeroMQ assumes the socket is
receive-only. Null is returned on error, otherwise a socket is
returned. You should next connect or bind the socket.
- zmq_bind(socket, protocol, host, port)
- Select the protocol and bind a socket to an address. ZeroMQ
connections allow either the "client" or the "server" to call zmq_bind,
but one side must use bind, and the other end must use connect.
The protocol choices are the strings "tcp", "ipc", or "inproc"
(see ZeroMQ documentation for more information on protocols).
The host is a string, and the port is an integer. For "tcp," the
host is typically "*." For "ipc" the "host" is really the
address, e.g. "/tmp/feeds/0", and port is ignored. For "inproc,"
"host" is really a name, e.g. "myendpoint" and port is
ignored. Returns success (true) or failure (false). For
"inproc", the bind must take place before a socket is connected.
- zmq_connect(socket, protocol, host, port)
- Select the protocol and connect a socket to an address. The
protocol choices are "tcp", "ipc", or "inproc" (see ZeroMQ
documentation). The host is a string, and the port is an
integer. For "tcp", the host may be "localhost". For "ipc" and
"inproc", port is ignored. Returns success (true) or failure
(false). For "inproc", the connect must take place after bind on
the other end.
- zmq_subscribe(socket, filter)
- Sets the filter on a socket opened with the "subscribe"
protocol. Messages are received the prefix of the message
matches the filter, a string. A socket can contain multiple
filters, including duplicates.
- zmq_unsubscribe(socket, filter)
- Remove a filter that was added to a socket by zmq_subscribe().
- zmq_send(socket, message)
- Send a message, a string, to a socket. Generally, this call
does not block and messages are queued for the receiver. In some
cases, the receiver need not exist at the time of the send.
Caution: sending faster that the receiver receives or before the
receiver exists implies that the sender can queue an unbounded
number of messages. Returns true if and only if successful.
- zmq_recv_noblock(socket)
- Receive a message, a string, from a socket. If no messages are
ready or an error occurs, nil (false) is returned. Otherwise a
string is returned. WARNING: If the message is longer than
STRMAXLEN-1 (STRMAXLEN is a C++ compile-time constant, not
exported to Serpent, and is currently 255), the message is
truncated. A sender should limit messages to 254 characters, and
receivers should assume data was lost when messages arrive with
length 255.
- zmq_recv_block(socket)
- Block until a message is available. Returns nil (false) if an
error occurs. Otherwise, the message is returned as a string.
See the warning above on message truncation.
- zmq_close(socket)
- Close a socket. Returns nil.
- zmq_term()
- Close the current context and shut down the ZeroMQ library.
Aura Extensions
Serpent has an extended syntax to support Aura message passing.
This syntax is enabled by setting the AURA flag during
compilation. Normally, you would only do this when compiling
Serpent as a library for linking with Aura, but you can also
compile a stand-along Serpent this way.
An Aura message is used to invoke operations on Aura objects.
These are "real" messages is the sense that they are represented
as a sequence of bytes and they can be sent to objects in other
threads or even to other address spaces. Aura messsages have a
method identifier (a string) including a type signature and a
set of parameters. The type signature for a given method
identifier is the same for all objects. For example, the method
identifier "set_hz" has one parameter that is a double.
All objects that accept the "set_hz" have the same
signature and require one double.
Aura Send
To send an Aura message, you can use one of the following
syntax forms:
target <- message(p1, p2, p3)
target <- message(p1, p2, p3) @ when
<- message(p1, p2, p3)
<- message(p1, p2, p3) @ when
While this looks like some kind of procedure call, it is quite
different. The target, which is optional, is any
expression that evaluates to an Aura ID (represented in Serpent as
an integer, and usually obtained by a call to the function aura_create_inst,
loaded from auramsgs.srp.)
The message must be an identifier that has
been declared as an Aura message. Although parsed as an
identifier (unquoted), Serpent uses the string name of this
identifier to form the method part of an Aura message. The message
is also used to find the type signature for the message by using
message (now as a symbol) as a key in the Serpent
dictionary aura_type_strings. The lookup must succeed
at compile time or an error is reported. The result of the
lookup is a string consisting of the letters "i" (Long), "d"
(Double), "s" (String), "l" (Logical), and "v" (Vector of
floats, obtained from a Serpent array).
The parameters p1 through p3 are a
comma-separated list of expressions equal in number to the
length of the type string for message. The types of
these parameters must be compatible with the letters of the type
string, but full checking can only be done at run time.
The Aura Send statement is compiled to a call to aura_zone_send_from_to.
Normally this is a built-in function that calls into Aura to
construct and send a message. The parameters to aura_zone_send_from_to
are:
- aura_zone_id -- The Aura ID for the current zone.
This is obtained from the global variable aura_zone_id,
which must have a value at run time.
- aura -- The Aura ID of the sending object. This is
0 if there is a target expression and the message is
actually sent from the current zone using aura_zone_id
as the sender. If there is no target, then the
message is sent to all objects the sender is connected to. In
this case, the sender's Aura ID must be in the variable "aura."
Normally,
the
aura
send
statement
would be in the method of a class that inherits from class Aura,
which provides an instance variable named aura.
- destination -- The Aura ID for the receiver. If a target
expression appears, the destination is the value of that
expression. Otherwise, destination is 0 and the
message is broadcast to all connected receivers.
- method -- The string name of the method to be
invoked, obtained from message.
- parameters -- An array consisting of the type
string for method followed by parameter values,
obtained by evaluating the parameter expressions. The array is
automatically allocated and constructed.
- timestamp -- A timestamp giving the Aura time for
the message to be delivered. (Aura messages are held in a
queue until their delivery time.) The default value is the
value of the variable AURA_NOW which must be defined
(normally to 1e-9) at runtime.
Note that the Aura preprocessor should be used to generate
correct type strings. Do not add type strings to aura_type_strings
manually.
For testing, it is possible to compile Serpent using the AURA
compile-time flag. You will have to define things expected by
the compiler including aura_zone_send_from_to, but
this can be an ordinary Serpent function.
Example
iecho_aura = aura_create_inst("Iecho", 3)
iecho_aura <- set_in(audio_io_aura, 0)
iecho_aura <- set_delay(0.3)
This creates an "Iecho" instance, sets the input to the left
channel (0) of the system audio input, and sets the delay to 0.3
seconds. The last (third) statement is equivalent to the
following:
aura_zone_send_from_to(aura_zone_id, 0,
iecho_aura,
"set_delay",
["d", 0.3], AURA_NOW)
Extending Serpent
Serpent can be extended with functions and data types. The
interface between Serpent and external code is generated
semi-automatically using the Serpent program interface.srp.
Not all C types are supported, and the mapping between Serpent and
C types has some restrictions and special cases, so sometimes the
developer must create some "glue" functions to translate between
Serpent and C. The supported types are as follows:
| Type name in interface description |
Converted to/from this type |
Type within Serpent |
| long |
int64 |
Integer |
| short |
int64 |
Integer |
| int |
int64 |
Integer |
| char |
char |
String |
| string |
char * |
String |
| double |
double |
Real |
| float |
double |
Real |
| bool |
FVal |
Symbol |
| any |
FVal |
-- |
| Object_ptr |
Object_ptr |
Object |
| FILE * |
FILE * |
File |
All interfaces are explicitly indicated by adding comments to C
code as follows:
/*SER type function_name [c_name] (type1,
type2, type3, ...) PENT*/
Generates an interface to function_name,
which refers to the Serpent name for the function. If the C name
is different, it is specified between square brackets. (Note
that bracket characters actually appear in the comment; they are
not meta-syntax characters.) The types are parameter types as
shown in the first column of the table of types shown
above. In addition, "external" types may be specified as extern
typename.
Finally, there are cases where the function should have
access to the virtual machine, which is a C++ object of type Machine.
Serpent programs cannot access the virtual machine as an
object, so it is impossible to explicitly pass the machine as
a parameter. However, if the type is specified as Machine, a
pointer to the machine of the caller will be passed
automatically. Since the parameter is implicit, the generated
function in Serpent will have one less parameter than the
corresponding C function.
/*SER class Class_name PENT*/
Serpent can be extended with new types using this form
of comment.
/*SER variable = value PENT*/
Global variables in Serpent can be initialized to a
value using this form of comment. The value must be a string,
integer, symbol, or real constant. No expressions are allowed.
When in doubt, value is converted to symbol.
Interfaces to Classes and Structures
The files extclass.h and extclass.cpp give an example of
an interface to a class. Note the use of the class Descriptor to
describe the external class to the Serpent run-time system. Descriptor
is subclassed to build a descriptor for the new type. This is not
an automatic operation; you must build your own descriptor
subclass.
Interfaces to Functions
The files extfuncdemo.h and extfuncdemo.cpp provide an
example of an interface to ordinary functions written in C or C++.
Building an Interface
Interfaces based on one or more .h file are generated by loading
"interface.srp" and calling the interf function as shown
in the following example:
interf("smid", ["midi.h", "midiserpent.h"])
The first parameter specifies the output file name (without the
.cpp extension). This name is also used for some internal names
that must be generated. The second parameter is a list of files to
process. Each of these files is included in the generated output
file using an #include directive, so if you want a file
included, list it even if it contains no /*SER ... PENT*/
comments.
In order to find header files not in the current directory, you
can provide a search path as follows:
interface_search_path = ["..\\midi\\", ...]