ts: github.com/bobappleyard/ts Index | Files | Directories

package ts

import "github.com/bobappleyard/ts"

This is the TranScript SDK. TranScript is a dynamic programming language where everything is an object.

Basic Syntax

Comments are as in Go. They are ignored by the language. Block comments don't nest.

// line comment
/* block comment */

Numbers are represented as series of digits, optionally separated with "." and/or preceded with "-".


The basic arithmetic and comparative operators work as expected.

1 + 3           // 4
3 / 2           // 1.5
12 * 4 - 6      // 42
5 >= 4          // true
5 == 4 + 2      // false

Supported operations are "+", "-", "*", "/", "==", "!=", "<", ">", "<=", ">=".

Names follow the C syntax convention: Letters or "_" to begin, letters, digits or "_" after that.


Names refer to things. They might refer to a definition, or they might refer to a member on an object.

a               // definition
a.prop          // member

There are two boolean values, "true" and "false".

Booleans support one operation, negation.

!true           // false
!false          // true

This is actually supported by every other object in TranScript as well. For every value other than "false", "false" is returned. For "false", "true" is returned.

The logical operators "&&" (and) and "||" (or) are provided.

true && true    // true
false || true   // true
false && true   // false
false || false  // false

These operators have short-circuiting. This means that if the value of the expression can be determined after evaluating the left operand, the right operand is not evaluated.

There is "nil", which represents no value. This is primarily used as the return value of functions and methods that are only called for their side effects, as well as for uninitialised fields and variables.

Strings have the usual C-like syntax: enclosed in speech marks with "\" for escape sequences.

"Hello, world!\n"

Two strings may be concatenated using "+".

"Hello, " + "world!\n" // gives the same value as before

Arrays are series of objects enclosed in brackets and separated with ",".

[1, 2, 3]

Arrays may be concatenated as strings are.

[1, 2] + [3, 4] // [1, 2, 3, 4]

Hashes are key-value pairs enclosed in curly brackets and separated with ",". In each pair, the key is separated from the value by ":".

{"key1": "value1", "key2": "value2"}

Strings, hashes and arrays are all instances of collections. Accessing the members of a collection follows the familiar subscript notation.

coll[0]       // read a member from the collection
coll["ok"]    // write a member to the collection

Different kinds of collections impose different restrictions on what can appear in the subscript.

Conditional Evaluation

An if statement evaluates an expression. If it evaluates to "true" then "if" evalutates its "then" block. If the expression evaluates to "false" then "if" evaluates its else block. Every value other than "false" is counted as "true" to an "if" statement.

if <expression> then <block> else <block> end

A block is a series of statements, each terminated with ";".

a = 1;
b = 2;
print(a + b);


def a = 1;
if a < 5 then
	print("a is less than five");
if a > 5 then
	print("a is greater than five");

This prints "a is less than five".

Variables And Scope

Variables allow you to store state and refer to the results of expresssions.

Defining a variable looks like

def <name>;                  // uninitialised
def <name> = <expr>;         // initialised to the value of <expr>
def <name>, <name>;          // comma allows multiple definitions
def <name> = <expr>, <name>; // different forms may be mixed freely

Variables may refer to previously defined variables in their initialisation section.

Locally defined variables shadow previous definitions.


def a = 1;
def f()
	def a = 2;
	return a;
print(f(), a);

This prints "2 1".

Once a variable has been defined, it may be updated so that the variable refers to a new value.

<name> = <expr>;

So if we alter the previous example, removing the internal definition, "a" is altered in place rather than shadowed.

def a = 1;
def f()
	a = 2; // <--- no longer has "def" in front of it
	return a;
print(f(), a);

This prints "2 2".


Functions are an important part of TranScript. They are first-class and are properly tail-recursive.

To call a function, take the expression that evaluates to the function and append expressions enclosed in "(" and ")" and separated by ",". These expressions are evaluated before they are passed to the function. They are then bound to the corresponding arguments in the function's environment, and the function's body is evaluated in this new environment.

<function>(<expr>, ...)

Here <function> means an expression that evaluates to a function.



Prints "5".

Functions can be defined in two ways.

Named functions:

def <name>(<args>) <body>

Anonymous functions:

fn (<args>) <body>

<args> is a series of names separated by "," and represents the arguments to the function.

An argument may be designated as optional by appending "?" to the name. All the optional arguments must appear after all the normal arguments. If the argument isn't provided when the function is called its value is set to "false".


def fprint(x, f?)
	if f then
		f.writeString(x.toString() + "\n");

If the final argument is followed by "*" then it is a "rest argument" and represents any parameters passed to the function that are not caught by previous argument names (optional or otherwise). This is encoded as an array.


def compose(f, g, more?, rest*)
	if more then
		g = compose.apply([g, more] + rest);
	return fn(x) = f(g(x));

<body> is either "=" followed by an expression, or a block terminated with "end".

i.e. for anonymous functions

fn (<args>) = <expr>
fn (<args>) <block> end

In the former case, it is as if

fn (<args>) return <expr>; end

had been written instead.

Functions may return values using the "return" statement.

return <expr>

This causes the function to return whatever the provided expression evaluates to. It returns straightaway, so no further statements are evaluated for that call. If the expression representing the return value is a function call, the caller's environment is cleared up before the call is made. This means that recursion in this case does not grow the stack.

You can exploit this property to make iterative processes using the syntax of recursion.


def for(i, t, f)
	if i < t then
		return for(i+1, t, f);
for(0, 10, fn(i) = print(i));

Prints "0" to "9" on consecutive lines. When "for" calls itself, the stack remains where it is. This is exactly equivalent to a "for loop" in many other languages, but using function calls rather than special syntax.

A function has access to its enclosing scope. This includes definitions that follow the function's definition. In the case of internal functions, this is true even when the enclosing function has returned.


def accumulate(x)
	return fn(y)
		x = x + y;
		return x;

This implements a function that, when called, returns another function. This inner function refers to "x" after "accumulate" has returned. This is what some people call "closures," others call "lexical scope."

def a = accumulate(1);
a(1);                   // 2
a(4);                   // 6

Objects and Classes

Objects are collections of slots. They support three basic operations: property get; property set; method call.

a.prop       // get prop
a.prop = x   // set prop to x
a.method()   // call method

Objects are instances of classes, which describe them. To create an object, call its class like a function:

def o = Object();

Here, "Object" is the class that will be used to instantiate the object. This may be any expression that evaluates to a class.

To define a class, use the "class" keyword

class <class name>(<ancestor>)
	<class body>

The <ancestor> is an expression that evaluates to a class, and is used for inheritance. Any members defined on the ancestor are also defined on this class. It may be omitted, in which case "Object" is used.

The <class body> is a series of "def" statements punctuated with ";". Definitions that look like variables correspond to properties and definitions that look like functions correspond to methods.


class Vector()
	def x, y;
	def create(x, y)
		this.x = x;
		this.y = y;
	def length()
		return sqrt(this.x*this.x + this.y*this.y);

The "create" method is the constructor. This is called immediately after the "Vector" object has been created. More precisely, "__new__" is called, which calls "create" and then returns the new object.


Properties provide access to storage on objects. They are typically defined and accessed as variables are.

Properties can also be defined with pairs of methods, a getter and a setter. When the property is read from, the getter is called with zero parameters. Its return value is taken to be the value of the property. When the property is written to, the setter is called with one parameter, the value being assigned to that property.

To define a property, use "get" and "set" in a definition:

def <name> get() <body> set(<val>) <body>;

Note that only one of "get" and "set" need to be present. If "set" is absent, the property is read-only. If "get" is absent, the property is write-only.


Methods are functions that are attached to classes. When in the body of a method an extra variable is available, "this", which represents the object the method is being called on. There is also "super", which represents "this" without the current class' overrides.


1.toString()     // this set to 1 in call to toString()
a.b.c(d);        // this set to b in call to c()

Methods will "remember" the "this" value when accessed as properties.


def f = 1.toString;

Prints "1".

Packages and Programs

Evaluation of programs is controlled by the client of the library. In the default interpreter (https://github.com/bobappleyard/tsi) programs are a series of top-level statements that are evaluated in the order they appear in the source file. A different approach may be preferred if TranScript is being used to extend a program.

Packages provide a mechanism for collecting code together into a namespace so that it may be re-used by programs. To use a package, use the "import" statement.

import <package names>

Here <package names> consists of a list of <package name> separated with commas, where <package name> is a list of <name> separated with dots.


import system;
import my.big.long.package.name;
import a, b, system;

This creates a variable in the scope that the "import" appears, and is an object containing all the exported definitions of the package as attributes.

To define a package, use the "package" statemtent.

package <package name>
	<package body>

Here <package body> is a <body> where "export" statements may appear.

export <names>

Where <names> is a list of <name> separated with commas. These should refer to variables and functions defined within the package. Anything that has not been exported is private to the package.

Packages need to be placed in the package path to be visible to the system. This defaults to


but may be changed. Note that multiple paths separated by ":" may be used.

The directory containing the source file issuing the import request will also be searched.

Packages are only loaded and evaluated once during the lifetime of the interpreter.


Package Files

classes.go compiler.go doc.go errors.go interpreter.go parser.go primitives.go


const (
    Final = 1 << iota


var (
    Undefined = errors.New("undefined")

func ArgError

func ArgError(c int) error

func RegisterExtension

func RegisterExtension(n string, f func(*Interpreter) map[string]*Object)

Inform the system about an extension to the language.

func TypeError

func TypeError(x *Object) error

type Accessor

type Accessor struct {
    // contains filtered or unexported fields

Accessors refer to names that can be looked up on objects.

func (*Accessor) Name

func (a *Accessor) Name() string

The accessor's name.

type Class

type Class struct {
    // contains filtered or unexported fields

Classes describe objects. They each have a name and an ancestor, accompanied by a series of entries describing fields and methods.

var ClassClass, AccessorClass, NilClass, BooleanClass, TrueClass, FalseClass,
    CollectionClass, SequenceClass, IteratorClass,
    StringClass, NumberClass, IntClass, FltClass, FunctionClass, ArrayClass,
    ErrorClass, BufferClass, PairClass *Class

Primitive data types. The normal operations on classes (extension, creation) do not work on these classes.

var HashClass *Class
class Hash(Collection)

A Hash maps keys to values.

For keys, the following rules hold:

* If the key is a string or number, its value is used directly.
* Otherwise the pointer value of the key is used.
* If the key defines a __key__() method then this is called, and its return
  value is used for the key.
* If the return value from a call to __key__() equals another call to
  __key__() but the receivers for the method calls have different classes
  then the hash table will consider those keys as different.
var ObjectClass *Class

The root class. All other classes descend from this one.

func (*Class) Ancestor

func (c *Class) Ancestor() *Class

Get the class' ancestor.

func (*Class) Call

func (c *Class) Call(o *Object, i int, args ...*Object) *Object

func (*Class) Extend

func (c *Class) Extend(i *Interpreter, n string, flags int, e []Slot) *Class

Create a descendant of c with the given name and a series of entries describing the class' members. Panics if the class cannot be extended.

func (*Class) FlagSet

func (c *Class) FlagSet(f int) bool

func (*Class) Get

func (c *Class) Get(o *Object, i int) *Object

func (*Class) IndexOf

func (c *Class) IndexOf(n string) int

func (*Class) Is

func (c *Class) Is(d *Class) bool

Check if c is the same class as d, or one of d's descendants.

func (*Class) Name

func (c *Class) Name() string

Get the class' name.

func (*Class) Names

func (c *Class) Names(hook, deep bool) []string

func (*Class) New

func (c *Class) New(args ...*Object) *Object

Create a new object instance. Panics if such an instance cannot be created.

func (*Class) Object

func (c *Class) Object() *Object

Get the class' object.

func (*Class) Set

func (c *Class) Set(o *Object, i int, x *Object)

func (*Class) SetFlag

func (c *Class) SetFlag(f int)

func (*Class) Slot

func (c *Class) Slot(i int) Slot

func (*Class) SlotCount

func (c *Class) SlotCount() int

func (*Class) UnsetFlag

func (c *Class) UnsetFlag(f int)

type Interpreter

type Interpreter struct {
    // contains filtered or unexported fields

An interpreter provides a global environment and some methods to control the general flow of execution.

func New

func New() *Interpreter

Create a new interpreter with the default environment.

func (*Interpreter) Accessor

func (i *Interpreter) Accessor(n string) *Accessor

Retrieve the named accessor.

func (*Interpreter) Define

func (i *Interpreter) Define(n string, v *Object)

Define a new global variable.

func (*Interpreter) Defined

func (i *Interpreter) Defined(n string) bool

Check whether a global variable is defined.

func (*Interpreter) Eval

func (i *Interpreter) Eval(s string) *Object

Evaluate an expression, returning its value. Panics on error.

func (*Interpreter) Exec

func (i *Interpreter) Exec(u *Unit) *Object

Run some compiled code. Panics on error.

func (*Interpreter) Get

func (i *Interpreter) Get(n string) *Object

Look up a global variable and return its value. Panics if the variable does not exist.

func (*Interpreter) Import

func (i *Interpreter) Import(n string) *Object

Import a package and return it.

func (*Interpreter) ListAccessors

func (i *Interpreter) ListAccessors() []string

List the available accessors.

func (*Interpreter) ListDefined

func (i *Interpreter) ListDefined() []string

List the global variables

func (*Interpreter) Load

func (i *Interpreter) Load(p string)

Load a code file into the interpreter. May be in source or compiled form. Panics on error.

func (*Interpreter) LoadPrimitives

func (i *Interpreter) LoadPrimitives()

registration function called by New()

func (*Interpreter) Repl

func (i *Interpreter) Repl(input func() io.Reader, save func(string))

Start a prompt that reads expressions from stdin and prints them to stdout. Swallows and prints all errors.

func (*Interpreter) Set

func (i *Interpreter) Set(n string, v *Object)

Update the value of a global variable. Panics if the variable does not exist.

type Object

type Object struct {
    // contains filtered or unexported fields

Everything that the runtime sees is encoded as an object. An object has a set of fields and methods, specified by a class. The fields may vary in value between different instances of an object and the methods are fixed for all instances of a given class.

Note that in some cases objects use extra space on the end to store primitive data. Therefore always refer to Objects as pointers to structs.

var Nil, True, False, Done *Object

Built in values.

func Wrap

func Wrap(x interface{}) *Object

Given a bool, number, string, slice or map return an object corresponding to that value. Well, it doesn't support all those types yet.

If a function is passed in: This function should take an object argument representing the receiver, and then zero to four other object arguments representing the arguments to the primitive. Alternatively, an array of objects may represent the arguments.

Ignore the receiver in the case of functions that are not methods; its value is undefined.

func (*Object) Call

func (o *Object) Call(a *Accessor, args ...*Object) *Object

Call a function or method. If a is nil, o is the function to be called. Otherwise a is an accessor for a method to be called on o. Pass args to the function or method and return what the function or method returns. Panics if the object is not a function or the method does not exist, or if the function or method panics.

func (*Object) Class

func (o *Object) Class() *Class

Get the object's class.

func (*Object) Defined

func (o *Object) Defined(a *Accessor) bool

Check to see whether a member is defined on an object.

func (*Object) Error

func (o *Object) Error() string

Implements error.

func (*Object) Get

func (o *Object) Get(a *Accessor) *Object

Get the slot to which the Accessor corresponds from the object. Panics if the slot does not exist.

func (*Object) Is

func (o *Object) Is(c *Class) bool

Check if the object is an instance of c (or one of its descendants).

func (*Object) Read

func (o *Object) Read(b []byte) (n int, err error)

Implements io.Reader.

func (*Object) Set

func (o *Object) Set(a *Accessor, x *Object)

Set the corresponding slot on o to x. Panics if the slot does not exist or cannot be written to.

func (*Object) SetUserData

func (o *Object) SetUserData(x interface{})

func (*Object) String

func (o *Object) String() string

Implements fmt.Stringer.

func (*Object) ToArray

func (o *Object) ToArray() []*Object

Retrieve []*Object associated with the object. Panics if there is no such datum.

func (*Object) ToBuffer

func (o *Object) ToBuffer() []byte

func (*Object) ToClass

func (o *Object) ToClass() *Class

Retrieve *Class associated with the object. Panics if there is no such datum.

func (*Object) ToFloat

func (o *Object) ToFloat() float64

Retrieve float64 associated with the object. Panics if there is no such datum.

func (*Object) ToInt

func (o *Object) ToInt() int64

Retrieve int associated with the object. Panics if there is no such datum.

func (*Object) ToString

func (o *Object) ToString() string

Retrieve string associated with the object. Panics if there is no such datum.

func (*Object) UserData

func (o *Object) UserData() interface{}

func (*Object) Write

func (o *Object) Write(b []byte) (n int, err error)

Implements io.Writer.

type Slot

type Slot struct {
    Name       string
    Flags      SlotFlags
    Value, Set *Object
    Class      *Class
    // contains filtered or unexported fields

Slots describe class members.

When constructing a primitive class, fill in the first four fields. The runtime will fill in the rest.

func AbstractMethod

func AbstractMethod(n string) Slot

Slot describing a method that descendant classes ought to implement.

func FSlot

func FSlot(n string, f interface{}) Slot

Public field slot.

func MSlot

func MSlot(n string, f interface{}) Slot

Public method slot.

func PSlot

func PSlot(n string, f interface{}) Slot

Private field slot.

func PropSlot

func PropSlot(n string, g, s interface{}) Slot

Public property slot.

type SlotFlags

type SlotFlags byte

func Flags

func Flags(k SlotKind, v SlotVis) SlotFlags

func (SlotFlags) Kind

func (f SlotFlags) Kind() SlotKind

func (SlotFlags) Vis

func (f SlotFlags) Vis() SlotVis

type SlotKind

type SlotKind byte
const (
    Field SlotKind = iota

type SlotVis

type SlotVis byte
const (
    Private SlotVis = iota << 2

type Unit

type Unit struct {
    // contains filtered or unexported fields

A unit represents some compiled code.

func (*Unit) Compile

func (u *Unit) Compile(in io.Reader, f string)

Compile a TranScript source file. The results of the compilation can then be saved to a file or executed.

func (*Unit) CompileStmt

func (u *Unit) CompileStmt(l *Lexer) bool

Compile a single toplevel statement.

func (*Unit) CompileStr

func (u *Unit) CompileStr(s string)

Shorthand wrapper around Compile().

func (*Unit) Copy

func (u *Unit) Copy() *Unit

Copy the unit. You should use copies if you are trying to run a unit multiple times.

func (*Unit) Load

func (u *Unit) Load(r io.Reader) bool

Load a compiled file. Panics on error. Returns whether or not the unit is in a valid format.

func (*Unit) Save

func (u *Unit) Save(w io.Writer)

Save a compiled file. Panics on error.



Package ts imports 16 packages (graph) and is imported by 7 packages. Updated 2014-10-01. Refresh now. Tools for package owners.