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.

manasa on Emailmanasa on Githubmanasa on Linkedinmanasa on Twitter
manasa
Blockchain research analyst at Nvest Labs
Graduated as a Computer Science engineer from VTU in 2017. Currently pursuing my Masters in Software Engineering from University of Visvesvaraya College of Engineering (2017-19 batch). Interning as Blockchain research analyst at Nvest Labs.
WhatsApp chat