TSL Basics
Identifiers are case-sensitive in TSL. Any legal C# identifier is
legal in TSL. Line comments starting with //
and block comments
surrounded by /*
and */
are supported. The syntax of TSL is
similar to that of the struct construct in C/C++ or C#.
The reserved keywords in TSL include: C# keywords, TSL keywords, and primitive data types.
struct |
cell |
protocol |
server |
proxy |
enum |
include |
extern |
optional |
invisible |
List |
Type |
syn |
asyn |
http |
Request |
Response |
stream |
bit |
byte |
sbyte |
bool |
char |
short |
ushort |
int |
uint |
long |
ulong |
float |
double |
decimal |
DateTime |
Guid |
string |
CellId |
index |
target |
void |
Name | Alias | Data Type | Bits |
---|---|---|---|
bool |
Boolean |
8 |
|
char |
Unicode character |
16 |
|
int8 |
sbyte |
signed 8-bit integer |
8 |
uint8 |
byte |
unsigned 8-bit integer |
8 |
int16 |
short |
signed 16-bit integer |
16 |
uint16 |
ushort |
unsigned 16-bit integer |
16 |
int32 |
int |
signed 32-bit integer |
32 |
uint32 |
uint |
unsigned 32-bit integer |
32 |
CellId |
signed 64-bit integer |
64 |
|
int64 |
long |
signed 64-bit integer |
64 |
uint64 |
ulong |
unsigned 64-bit integer |
64 |
float |
Single-precision float |
32 |
|
double |
Double-precision float |
64 |
|
DateTime |
DateTime structure |
64 |
|
decimal |
Decimal numbers |
128 |
|
Guid |
Globally unique id |
128 |
TSL Script #
A TSL script consists of one or more of the following top-level constructs:
TSL script inclusion statement
Enum construct
Struct construct
Cell construct
Protocol construct
Server construct
Proxy construct
We can include other TSL files in the current TSL project by using the
"include" key word. For example, include another.tsl
. Note, if a
TSL file is already added to the current TSL project, it is not
necessary to include it explicitly.
Enumerations are declared using the enum keyword. They can be referenced in the current TSL project or the .NET projects referencing the TSL project.
Struct #
A struct specifies a user-defined type. It is composed of three
types of fields: built-in data types, other user-defined structs,
and container types. A container type stores a collection of data
elements. GE currently supports three container types:
Array
TSL supports single-dimensional and multi-dimensional arrays. A
single-dimensional array of type T can be declared in two ways:
Array<T>(3) myArray;
or T[3] myArray;
.
For example:
Array<int32>(3) myArray;
int32[3] myArray;
Multi-dimensional arrays can be declared via: T[m,n] myMultiArray;
For example:
int32[3,5] mymultiArray;
Note that TSL array does not support variable-length elements. Thus the following declaration is not allowed:
string [10] strings; // Not allowed!
A list of T can be declared via: List<T> mylist;
For example:
List<int32> mylist;
TSL String/string represents a sequence of characters.
Note that a modifier optional can be applied to any field of a struct. Optional modifier indicates the current field can be absent.
Defining a struct in TSL is pretty much like defining a struct in C/C++ or C#. For example:
struct MyStructA
{
int32 Value;
Guid Id;
DateTime timestamp;
}
struct MyStructB
{
int64 Link;
List<float> Weights;
bool[16] flags;
string Name;
bit[10] bitflags;
MyStructA substruct;
List<MyStructA> substructs;
}
Cell #
A Cell is a user-defined type. It is composed of three types of fields: built-in types, other user-defined TSL structs, and container types.
The storage layer of GE is a key-value store. The schema of the value part of a key-value pair can be specified by a TSL cell. GE generates the key-value store interfaces (SaveCell, LoadCell, and UpdateCell, etc.) as well as the data access methods for each cell type. A corresponding cell 'accessor' will be generated for each TSL cell. A cell can be accessed and manipulated in-place through its cell accessor interfaces.
This is a simple TSL cell:
cell MyCell
{
int32 Value;
string Name;
}
After compilation, we can access and manipulate MyCell via its cell accessor:
long cellId = 1;
using(var cell = Global.LocalStorage.UseMyCell(cellId))
{
Console.WriteLine("Value: {0}", cell.Value);
cell.Name = "a new name";
}
The manipulations are thread-safe when a cell is accessed via its cell accessor.
At the storage level, a cell is just a blob of bytes in the main memory. From the point view of a developer, a cell can be considered as a flat structured data container. It looks very much like a struct in C/C++ or C#. A fundamental difference between a C/C++ struct and a GE cell is their way of organizing memory. All the data fields in a cell are stored in a continuous memory region, while a C/C++ struct may contain data field references pointing to other data fields stored at a different memory region.
Data Modeling in TSL #
Cells are the basic building blocks of data modeling. Here we use a simple graph modeling example to demonstrate the basic data modeling techniques.
Note: any cell in GE is referenced by a 64-bit CellId,
thus a list of cells can be represented by List<CellId>
in TSL.
cell struct SimpleGraphNode
{
List<CellId> Inlinks;
List<CellId> Outlinks;
string Name;
}
This cell type can be used to represent the nodes of a simple directed graph. Each node in this graph has a name and a list of inlinks and outlinks.
What if the graph edges have additional data associated? Here is an example:
struct MyEdge
{
long Link;
float Weight;
}
cell struct MyCell
{
List<MyEdge> Inlinks;
List<MyEdge> Outlinks;
string Name;
}
This script contains a 'Cell' and a 'Struct'. This time, each edge contains a weight. Sample code for accessing MyCell:
Global.LocalStorage.SaveMyCell(132);
using (var ca = Global.LocalStorage.UseMyCell(132))
{
ca.Inlinks.Add(new MyEdge(10, 11));
ca.Outlinks.Add(new MyEdge(11, 12));
ca.Name = "Hello, Cell Manipulator!";
}
using (var ca = Global.LocalStorage.UseMyCell(132))
{
Console.WriteLine("Inlink.Count: {0}", ca.Inlinks.Count);
Console.WriteLine("Outlink.Count: {0}", ca.Outlinks.Count);
Console.WriteLine("Cell Name: {0}", ca.Name);
}
Protocol #
A protocol specifies a contract between a message sender and its receiver. It specifies three things: protocol type, request message, and response message. For example:
struct MyRequest
{
string Value;
}
struct MyResponse
{
int32 Result;
}
protocol myProtocol
{
Type: Syn;
Request: MyRequest;
Response: MyResponse;
}
Possible protocol Type are: Syn, Asyn, and HTTP.
If the protocol is a Syn protocol:
- Its Request can be: void or a user-defined struct.
- Its Response can be: void or a user-defined struct.
If the protocol is an Asyn protocol:
- Its Request can be: void or a user-defined struct.
- Its Response MUST be void.
If the protocol is an HTTP protocol:
- Its Request can be: void, a user-defined struct, or Stream.
- Its Response can be: void, a user-defined struct, or Stream.
A Request or Response with Stream type can be manipulated via data stream interfaces.
Server/Proxy/Module #
We can bind a number of protocols to a server by defining a server struct in TSL. For example, if we have defined two protocols named protocolA and protocolB, then we can bind them to a server struct called myServer:
server my_server
{
protocol protocolA;
protocol protocolB;
}
After compilation, an abstract base server named myServerBase supporting both protocolA and protocolB will be generated. Two abstract message handlers protocolAHandler and protocolBHandler will be generated. We can implement our server logic by overriding the generated handlers.
A proxy can be defined similarly, except that the construct type "proxy" is used.
Note that a server/proxy cannot inherit protocols from another server/proxy instance. GE relies on protocol ids to dispatch messages and the protocol ids are numbered sequentially within a server/proxy instance. Inheriting protocols from other servers/proxies causes message dispatching problems. If we want to make a group of protocols reusable, we can group them into a module struct:
module my_module
{
protocol protocolC;
protocol protocolD;
}
Then, we can "plug-in" the module into a server/proxy instance:
...
my_server server = new my_server();
server.RegisterCommunicationModule<my_module>();
In this way, a client that understands the protocols defined in my_module
can talk to the server.
Attributes #
An attribute is a textual tag associated with a construct in TSL. Attributes provide metadata about the construct and can be accessed at run time. An attribute can be a string or a pair of strings, where it is regarded as a key-value pair. A single-string attribute is regarded as a key-value pair with an empty value. Duplicated keys are not allowed on the same construct. A few attributes are reserved for built-in features, e.g. Inverted Index.
To tag a construct with an attribute, place the attribute above it. The syntax is akin to that of C# attributes:
/// Tagging the cell
[GraphNode, BaseType : Person]
cell Student
{
/// Tagging the field
[GraphEdge : Outlinks]
List<CellId> friends;
}
The key and its value of an attribute are separated by a colon (:
).
Multiple attributes can reside within one pair of square brackets,
separated by a comma (,
). The attribute strings can be any literal
strings with :
and ,
escaped as \:
and \,
. Non-escaped spaces
at the start and the end of a string are trimmed, so that [ a b :
123\ \ ]
will be interpreted as [a b:123 ]
.