Serpent – Threads

Serpent – Threads

Thread Interface

Serpent threads are unlike Python or Unix threads in that they are non-preemptable, meaning that threads run until a command explicitly allows another thread to run. Only one thread runs at once, even if multiple processors are available.

To create a thread, use fork(), which copies the current stack frame (the current function’s local variables and program counter). The original thread continues executing and the new thread starts executing as if both called fork(). The difference is that in the original thread, fork() returns the new thread (a reference to a thread primitive of type Thread, while in the new thread, fork() returns nil (false) and when this thread returns from the function where fork() was called, the thread terminates.

After creating a new thread with fork(), the calling thread yields to the new thread. You can also suspend threads to take them off the runnable list so they will not be yielded to (see suspend(), and you can make a suspended thread runnable again (see resume()).

A typical way to create a thread to perform some computation is:

    if not fork():
        some_computation() // runs on new thread
        return
    ... work for main thread continues ...

Alternatively, one can make a function that runs on a separate thread as follows:

def some_computation()
    if fork(): // create new thread for the work
        return // caller does none of the work
    ... some computation here for new thread ... 

One could also arrange things to defer computation until after the main thread suspends or yields control:

def some_computation()
    var caller = threadid() // get the calling thread
    if fork()
        return // caller will run this soon after fork()
    // after fork, new thread will run immediately,
    // so yield control back to calling thread
    yieldto(caller)
    // eventually, control will come back to this new thread:
    ... some defered computation here for new thread ... 

Procs – Preemptable Thread Interface and Functions

Serpent has two types of threads. “Normal” threads are non-preemptable coroutines that share the heap (thus all globals are common to these threads). You can create as many of these threads as you wish. The second form is limited to one additional thread (called a “process” or “proc” to avoid overloading the term “thread”) that loads a file and then periodically calls a function. This proc has an independent heap (thus no variables are shared) and runs at higher priority than the main proc. It can preempt the main proc or run while the main proc is blocked waiting for input or sleeping. The entire preemptive proc interface consists of proc_create()proc_send() and proc_receive().

All communication between these two procs 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 proc functions, link Serpent with the objects obtained from proccreate.cpp and prochack.cpp. (Look for the CMake option USE_PROC to use procs.)

It is strongly recommended that you do not depend heavily on this simple proc interface. It was created to support a course and is not intended for “real” use.

proc_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 proc_id in the new proc to an integer (see below)
  • return the new proc’s proc_id
  • Once a new proc is created and it has finished successfully loading/compiling/executing commands from filename, the proc uses PortTime to wake up every period milliseconds and call porttime_callback(ms), where ms is the current time in milliseconds.
proc_send(proc_id, string)
enqueue string for receipt by the other proc. Return the number of strings sent (0 if the queue is full, 1 if the send is successful.) proc_id should be the proc id of the caller, not the destination. (Use the global variable proc_id.
proc_receive(proc_id)
check the queue and if there is a message from the other proc, 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). proc_idshould be the proc id of the caller, not the proc that sent the message. (Use the global variable proc_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 negative, an error occurred. -1 indicates an error was reported from the accept operation. -2 indicates that the socket parameter value is invalid. (Other negative integers should also be treated as errors.) 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 error code -2 is returned if the socket parameter is invalid. 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.
Serpent – Program style

Serpent – Program style

Program Style

Here are some suggestions for Serpent programming style.

Spaces and Indentation

Indent with 4 spaces. Tell your editor to insert 4 spaces rather than TAB characters. Never use TAB characters because they are invisible and editors treat them differently. See serpent/extras for Sublime and Emacs support files.

Use spaces around all operators: x = a + b / c, not x=a+b/c or even x = a + b/c.

Use space after commas to separate parameters: f(x, y, z + 3), not f(x,y,z + 3).

Use blank lines carefully. With longer functions, blank lines can be used to separate logical blocks of code. Often you will want to put a one-line comment at the beginning of such blocks to describe what the block does:

  // create a new named widget

   var w = Widget(name)

   widget_table[name] = w

 

   // add the widget to the display

   display_list.add(w)

   display_list.update_layout()

 

Since functions and methods have embedded blank lines, it is best to use at least two blank lines between functions and methods. If methods are separated by double-spaces, perhaps classes and function definitions at the top level should be separated by three blank lines. (This can look too spread out; if it does not help readability, less space often looks better.)

And speaking of comments, use a space after comment tokens:

  // good comment with space after “//”

   //do not run comments into “//”

   # another good comment

   #this comment is missing a leading space

 

Both “//” and “#” are acceptable comment tokens. Since “#” is darker, I prefer to use it sparingly and sometimes to call attention to important points or key 1-line summaries of functions.

Long Lines

Limit line lengths to 80 characters. To write long literal strings, just break the line and use + to join them:

  “Pretend this is a very long string of text that requires two lines.”

could be written as:

  “Pretend this is a very long string ” +

   “of text that requires two lines.”

Long lines also occur in print statements. You can use a trailing comma or semicolon to avoid printing a newline, then continue printing with another print statement:

  print “Imagine that this statement is too long for one line”, var1, var2

This can be rewritten as:

  print “Imagine that this statement is too long for one line”,

   print var1, var2

Similarly, display statements have a trick for long lines:

  display “Imagine this is too long for one line”, var1, var2

If a display statement ends in a comma, no newline is printed. If a display statement does not begin with a literal string, it will not generate an initial label from the string, so the following can be used to break the statement into two lines:

  display “Imagine this is too long for one line”,

   display var1, var2

and the output will be something like:

  Imagine this is too long for one line: var1 = 1, var2 = 2

Of course, it can be inconvenient to produce very long output lines as well. If you have one message to output across multiple lines, you can indent lines after the first, e.g.

  print “Imagine that this statement is too long for one line”

   print ”    “; var1, var2

which will print something like:

  Imagine that this statement is too long for one line

       1 2

With display you more-or-less have to use the initial label, but it can be blank, as in:

  display “Imagine this is too long for one line”, var1

   display ”                                     “, var2

and the output will be something like:

  Imagine this is too long for one line: var1 = 1

                                        : var2 = 2

Comments

Unlike Python, Serpent comments can begin with // or #. Always follow // or # with a single space before the comment (or multiple spaces if you want to form an indented block of text.) In the author’s opinion, // is prettier than and preferable to #. You can use #, which is darker, for emphasis, e.g. to mark code that needs attention or to provide a one-line summary of each function. Full-line comments should generally precede the thing commented on, and end-of-line comments should follow the thing commented on, e.g.

// pr_range – return integer from low through high – 1

def pr_range(low, optional high)

   int(pr_unif(low, high)) // truncate real value

Names and Capitalization

Variable, class, and function names should be descriptive. It is worth a minute to globally rename variables and naming conventions if it makes your code more readable and consistent!

If you want to be consistent with Serpent libraries, use underscores for multi-word variable names, e.g. file_length, and use lower-case everywhere except to capitalize classes, e.g. Labeled_slider. Use all-caps for global “constants,” e.g. NOTE_ON = 0x90.

If you must use “CamelCase,” at least start with lower case for variables and functions, and upper case for classes, e.g. var fileLength and class LabeledSlider.

Serpent – Program style

SERPENT – Declarations and Debugging

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.

The body of the function can be a statement list after the colon (:), statements being separated by semicolons. If there are no statements on the same line, the colon is optional.

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 maybe 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 superrefers 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.)

Debugging

Debugging in Serpent uses compiler messages, run-time error messages, a built-in debugger, and most of all, print statements. Serpent has a very fast compiler, so when an error is encountered, the compiler simply stops compiling and prints an error message. The error message tells you a file name and line number and possibly more. The line number reflects where the error was detected as the file was sequentially processed. The location of the actual error may be before the location where an error is detected.

Run-time error messages occur for many reasons: attempting to access an uninitialized global variable, dividing by zero, an out-of-bounds array index, passing too many or too few parameters to a function, type errors such as using a string where a number is required, etc. When a run-time error occurs, an error message is printed. A line number is printed, but it corresponds to the location of the next instruction. The location of the error may be an earlier line. For example, if the error message reports an array access problem at line 30, but there is no indexing, e.g. “[expr]” on line 30, you should look backwards for an expression with indexing.

The debugger is very simple but very useful. Every program should say:
    require "debug"
The debugger is just Serpent code. The main limitations are that the debugger cannot single-step or set breakpoints. Instead, the debugger is invoked when a run-time error occurs. The debugger can then print out a stack trace (most useful), print the values of local variables, move up and down the stack to examine variables in different stack frames, and resume execution. Type RETURN for a brief summary of commands.

Under wxSerpent, if the debugger is started, it will prompt for input in a pop-up dialog box, which may be confusing. Type ! to exit, ? for a stack trace, or > to resume (the other debugger commands work too).  On Windows, text output including debugger output is written only to stdout, which Windows does not display! This can make it difficult to use any debugging functions. Unless you disable it, the stack trace will go to any log file you have set up by initializing stdlog to a file. If there is no log file, the debugger will attempt to open “srp.log” as a log file before printing the stack trace. If necessary, just kill the (wx)serpent program, then type srp.log or open the stack trace in a text editor to study the stack trace and any other debugging output you might have generated in your program.

The most useful debugging tools are print and display. Do not be afraid to put display statements in Serpent library programs to help understand how they work.  The display command was especially created for quick debugging output.

Serpent – Program style

SERPENT – Types, Syntax and Expressions

Serpent is a programming language enlivened by Python. Like Python, it has a simple, minimal syntax, dynamic composing, and support for object-oriented programming. Serpent additionally draws inspiration from XLisp, Squeak, SmallTalk, Ruby, and Basic. How is it different? Serpent is intended for use in real-time systems, especially interactive multimedia systems. Serpent is unique in providing the following combination of highlights:

  • 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 enables each thread to have its own copy of Serpent and to rely on the OS for scheduling and preemption.

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
  • Thread — a thread (coroutine)

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, utilizes indentation to indicate structure. Statements are grouped, not by brackets, but by indenting them to the same level, each statement on a separate line.

Generally, statements may be consolidated on one line by separating them with a semicolon. If two or more statements are separated by a semicolon, all statements are considered to have the same indentation level as the first one.

Certain control constructs may include statements on the same line after the colon rather than indented on the next line. These are def, if, elif, else, for, and while.

 

Expressions

Expressions Denoting Constants

Integer constants are normally familiar strings of decimal digits. Be that as it may, if the first digit is zero (“0”), the digit string is interpreted as octal and must not contain “8” or “9”. A hexadecimal 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 floating 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 solitary double quote () character (a single double quote character would terminate the string.) Alternatively, a double quote can be inserted into a string by escaping it with a backslash character (\). The backslash character is used with other characters to denote special characters in strings:

  • “\a” denotes audible bell (ASCII code 7),
  • “\b” denotes the backspace character (ASCII code 8),
  • “\f” denotes the form feed character (ASCII code 12),
  • “\n” denotes the newline character (ASCII code 10),
  • “\r” denotes the carriage return character (ASCII code 13),
  • “\t” denotes tab,
  • “\v” denotes vertical tab (ASCII code 11),
  • “\\” denotes backslash (note that a single backslash does not denote a backslash character),

  • “\'” denotes the single quote character (ASCII code 39),
  • “\”” denotes the double quote character (ASCII code 34),
  • “\123” denotes the octal code 123 (any 3 octal digits are permitted, only the low-order 8 bits are used),
  • “\x1A” or “\x1a” denotes the hex code 1A (any 2 hex digits are permitted). In particular, “\x00” represents a zero byte, which is allowed in Serpent strings.
  • “\X1A” is also a hex code; “\x” followed by two hex digits is treated the same as “\X”.

The backslash character before other characters indicates the same character, thus “\c” is equivalent to “c”.

There is no character type in Serpent. Rather, 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. In this manner, 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 do resemble strings, but single quotes are used as delimiters. The same escape codes are valid. Thus, although not recommended, symbols can contain double quotes 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 developed 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 utilize it.

A dictionary is signified 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. Additionally, 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.

Array/List Comprehensions

The simple syntax of square brackets containing values is extended to support “list comprehensions.” Specifically, each element in the comma-separated list can be an expression followed by a for clause and an optional if clause. In that case, the for clause is executed, and for each iteration, the expression is evaluated to become the next element of the array. (So this is more of an “array comprehension,” than a “list comprehension” unless you call dynamic arrays “lists” as does Python.) In addition, the for clause can be followed by an if clause, in which case the if condition is evaluated on each iteration, and only when true is the expression evaluated and appended to the array. A for clause takes any of the forms of for statements), only the clause is in-line (not on a separate line) and is never terminated with a colon. An if clause is simply the keyword if followed by an expression (the condition). For example, the following constructs a list of integers from 0 through 9:

[i for i = 0 to 10]

The following constructs a list of factors of 144:

[i for i = 1 to 145 if 144 % i == 0]

The following constructs a list of symbols from a list of strings:

[intern(s) for s in [“string1”, “string2”]]

The following illustrates the feature that multiple elements can have for clauses. This expression evaluates to an array with integers from 0 to 9 followed by even integers from 10 through 18:

[i for i = 0 to 10, i for i = 10 to 20 by 2]

Finally, the comprehension expressions are just expressions, so they may be nested. The following constructs an array of arrays of increasing length:

[[i for i = 0 to n] for n = 5 to 10]

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

if else

This last “if else” “operator” is used in expressions of the form expr1 if condition else expr2. The condition is evaluated first. If it is true, the first expression is evaluated to form the value of the overall if else expression; otherwise, the second expression is evaluated to form the value.

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.