Cells and Messages

Syntax and Comment Styles

The following are some basic features of the syntax of CellSpeak:

  • There are several ways to include comments in CellSpeak source - see the example.
  • Indentation and naming of types, variables, functions etc, are up to the developer.
  • CellSpeak does not use curly brackets, nor semi-colons.
  • Blocks of code sit mostly between is .. end or do .. end pairs.
  • CellSpeak handles UTF-8 encoded source.
Comment Styles
-. This is a comment that can span several lines
   This comment style is well suited to include textual comments
   or descriptions in the source code.
.-
 
-- This is a single line comment - typically used for clarifications
 
// This is also a single line comment - e.g. to comment out a line of code
 
/* This comment style allows for nested comments, making it very suitable 
   to comment out blocks of code during the development. Code already 
   commented out, like this /* comment */ or this -. comment .- will not 
   end the surrounding comment.
*/

-- a for loop - Primes is an array of primes
for each p in Primes do
	...
end

-- a function
function f(int x, xyz ) out xyz is
	...
end

-- Other characters then ascii can be used for names, strings etc.
int α, β=2, γ=3
α=β+γ

-- Expressions in a string between [] are replaced by their printed values.
-- The line below will print as: 5 = 2 + 3
Window <- print("\n[α] = [β] + [γ]")
			

Sending a message

A message in CellSpeak consists of a name and a list of optional parameters. In the following examples message stands for any message.

The single most important operation in CellSpeak is to send a message to another cell. For this a left pointing arrow is used.

In some cases, it can be useful to get a delivery notification - in that case we use an arrow with a plus sign added.

Messages sent to a cell are added to the end of the message queue for that cell. Sometimes it might be necessary to send a message with a high priority - that message is then added to the front of the queue. For this type of messages we use a star in the message arrow.

It can be useful to have a message handled by a only one cell among a group of cells, for example for work-load balancing. For this type of message we put an exclamation point in the message arrow.

The parameters of a message - ints, floats, arrays, records etc. - can be small or big - from a single byte to megabyte-sized file for example.

Sending a message to a cell
-- An example of a message with some parameters
JustSomeData( 3.14, "Hello there", 10)
			
-- Send a message
cell <- message

-- Send a message and request a delivery notification
cell <+- message

-- Send a message with high priority
cell <*- message

-- Send a message that will be handled by only one of the cells a,b or c
a,b,c <!- message

-- Message modifiers can be combined
a,b,c <!+*- message

-- Messages usually have parameters - x is some variable
cell <- MessageWithContent( x, 77, "See you later, alligator")
			

Design of a cell

The fundamental unit of every CellSpeak program is the cell. A cell is defined by its design and instantiated with the create keyword as in the following example.

A design can inherit from other designs. When design inherits from another design, it inherits the permanent data, the functions and the message handlers. The inherited functions and message handlers can be modified. Other data, functions and message handlers can be added as required.

When a cell is created then the constructors for that cell are called in order, i.e. first the constructors of the ancestor, as declared in the inheritance, and then the constructor of design itself. When a cell is destroyed, then the destructors are called in reverse order.

Designing a cell
-- A design for a cell... 
design X is
	...code and data for the design
end

-- This how to instantiate a cell 
MyCell = create X

-- Another design... 
design Y is
	...code and data for the design
end

-- A new design can also inherit from other designs
design Z like X, Y is 
	...additional code and data for the design
end
			

Cell Hierarchy

In the example above Z is a variable of the type cell and stores a reference to the cell created from the design X. The reference can later be used to send messages to. To be clear: a cell is a cell, i.e. a cell has no type other than cell. A reference to a cell can take references to any cell, irrespective of its design. It is just a reference that you can send messages to.

There is also no 'main' cell or 'main' design in a program. A CellSpeak program is a collection of cells, and typically the program is started by instantiating one of these cells. That cell will most probably have been designed to then create the other cells that are required in the program. It is however perfectly possible in CellSpeak to have several cells with which the application could be started.

A design can have parameters, it can have a constructor and a destructor. Designs can also inherit from other designs. In that case they inherit the data and methods from the ancestor design. The methods of the inherited design can be changed and other data and methods can be added to the new design.

An important feature of cells is that they form a hierarchy. A cell that is instantiated by another cell is a child of the former. We will see that this feature is important with respect to the message flow.

The following diagram shows the cell hierarchy created by the code at the right: Car hierarchy

Cells are organized in a hierarchy which in this example can simply be read as: a Car consists of a Body, an Engine and Wheels, the Body consists of the Doors, the Windows and the Seats. The Car cell is the parent of the cells Body, Engine and Wheels. The cells Body, Wheels and Engine are siblings.

Cells are attached to their parent cell irrespective of the fact that the parent cell keeps a reference to that cell or not. In the example above the cell Car does not keep a separate reference for the cell of the design Engine, but that cell is attached to the cell Car anyhow. Often it is however useful to have an explicit reference to the cells that are part of a design.

Cell Hierarchy
design Car is
	create Body
	create Engine
	create Wheels
end
design Body is
	create Doors
	create Windows
	create Seats
end
design Wheels is
	create Wheel
	create Wheel
	create Wheel
	create Wheel
end
			

Message syntax

In this example the message ShiftToGear is being sent to the cell MyEngine with just one parameter, an integer. The name of a message is used at the receiving end, together with the message signature, to select a message handler. The message signature is the list of types for the parameter(s), which in our example would simply be int. A message name is just a name, so it is actually possible to use a string, for example because you want to include white space or special characters:

Sending Messages
design Car is

	-- Create the engine of the car ..
	cell MyEngine = create Engine

	-- ..and send a message to it
	MyEngine <- ShiftToGear(4)

	-- Send a message with a name that is a string
	MyEngine <- “Is the oil below this level ?”(75)
end
			

Message handlers

Actions that have to be executed when a message is received by a cell are called handlers. In the example to the right StartTheEngine and Drive are two handlers.

Message handlers start with the keyword on, followed by the name of the messages and the optional parameter list. The parameter list determines the parameter signature of the message handler. A parameter signature is a string that contains one or more bytes for each parameter of the handler. The parameter signature does not contain user defined type names, but it records the standard types and the structure of the types used as parameters.

In CellSpeak every design has a message table that is used to select a message handler for an incoming message based on the message name and signature string. For performance reasons the string that consists of the name and the parameter signature is converted by the compiler to a single hash-value, and it is that value that is used to select in the message table, making the process very fast irrespective of the length of the message name or parameter signature.

Message handlers have no return values. If they must return some result to the sender of the message they have to send a message as well. Because this is a common situation, a cell has access to the sender of the message by using the keyword sender.

Message handlers have access to the permanent data of a cell.

Message Handlers
design Car is
	
	initialisation code and data..

	on StartTheEngine do	
		code and data..	

		-- Returning a confirmation to the sender of the message		
		sender <- EngineStarted
	end

	on Drive(float kms) do
		code and data ..
	end
end
			

Message Flow

When a message is handled for a particular cell, then that message is taken from the message queue.

Messages however can also be redirected to another cell, for example one of the children cells. To redirect a message the keyword same can be used. It has to be placed between brackets because otherwise it is interpreted as the literal message 'same'.

The message Honk for example is not handled by the cell MyCar but instead redirected to the cell CarBody, which has a handler for the message.

A cell can send a message to all its children by using the keyword all.

It is possible to define a catch-all message handler by using a question mark.

Message Flow
design Garage is
	cell HerCar = create Car
	cell MyCar = create Car
	cell JoesBike = create Bicycle
	HerCar, MyCar, JoesBike <- Fill(100), Honk(), Drive(1.6)
end

design Car is

	cell Body 
	Body = create CarBody
	
	on StartTheEngine do
		..statements
	end
	
	on Drive(float kms) do
		..statements
	end

	-- Redirect a message		
	on Honk do
		CarBody <- (same)
	end
end

design CarBody is
	on Honk do
		..statements
	end
end

-- To redirect all unhandled messages to the cell's children, use this:	
on ? => all <- (same)
			

Interfaces

For larger cells, i.e. cells that offer more extensive services, it is often useful to group messages that belong logically togehter in an interface.

An interface starts with the keyword interface followed by the name of the interface and continues until the next interface starts or until interface end

Messages sent to an interface, are always preceeded by the interface name.

Data defined inside the interface but oustside the handlers of the interface is permanent data and is only accessible to the message handlers of the interface.

Interfaces
-- The interface 'Drive' of this design
interface Drive is

	-- These variables are only accessible to the handlers of the interface
	float 	Speed
	int 	Gear
	
	on SetSpeed(float NewSpeed) do
		...
	end
	
	on ShiftGear(int NewGear) do
		...
	end
	
-- The interface 'Turn' for the same design starts here
interface Turn is

	on Left do
		...
	end
	
	on Right do
		...
	end
	
	-- All other messages to the turn interface are signalled as invalid.
	on ? do
		sender <- Turn.Invalid
	end

interface end

-- To send a message to an interface, just use the name of the interface:
car <- Drive.SetSpeed(30.0), Drive.ShiftGear(2), Turn.Left
			

The structure of a CellSpeak program

A Cellspeak program consists of a collection of cells. There is no main. To start an application, the user selects a cell and instantiates that cell. Often that will be a cell that creates other cells etc. and in this way bootstraps the whole application, but it can also be a simple cell that offers its services to the system for other cells to make use of.

A cell design consists of private data, an optional constructor and destructor, functions and message handlers.

The constructor has no parameters, but 'sees' the parameters of the design. A destructor also has no parameters. The destructor of a cell is called when the cell is destroyed. Destroying a cell also destroys all the children of the cell.

Designs, or data and code, that belong together can be grouped into a group. The group functions as a container and namespace. Groups are included in a source code with the use directive.

It is possible to define types and functions outside of the design of a cell typically as types or functions to be used by many cells. Examples of that are the math library and the string library of the language.

Functions, message handlers and the cell design itself, can have an exception table. An exception that occurs is routed to the closest exception handler on the current call stack.

Program Structure
-- the group of vehicles
group Vehicles

-- a design with parameters
design Car(int NrOfWheels, xyz Position, cell Garage) is

	-- local permanent data of the cell
	var Speed = 0.0
	keep Position, Garage

	-- a function for use in the cell	
	function CalculateDistance(xyz NewPosition) out float is
		..
	end

	-- The constructor of the cell
	constructor is 
		create Body
		create Engine

		-- inside the constructor you have access to the parameters		
		for i = 0 to NrOfWheels do
			create Wheel
		end
	end

	-- The destructor of the cell has no parameters	
	destructor is	
		Garage <- CarIsDestroyed	
	end

	-- a message handler for the cell	
	on GetDistance do
		var Distance = CalculateDistance( Speed )
		sender <- CarHasDriven( Distance )
	end

	-- the exception handler for the cell - see below for an example	
	catch 	
		...
	end
end
		

Types and Variables.

Types and variables

CellSpeak has all the types that you would expect - integers of different sizes, floats and doubles, arrays - and then some. Many applications make use of vectors - with two, three or four elements - and of matching matrices , so these types are also built-in into CellSpeak. It reduces boiler plate code, and it makes the implementation of these types fast, because use is made of specific processor instructions to handle these types.

In CellSpeak you can define your own types and all types that you define - integer type, array etc - can have methods associated with them.

Types also have operations defined between them - addition, multiplication and so forth - and also the operations between vectors and matrices are built-in to CellSpeak. The developer can also define his own operations between types.

In operations or assignments between different types, the type of the arguments will be cast automatically to another type when it is safe to do so (e.g. from an int to a float). If required also explicit casts can be made by putting the cast target between angled brackets.

Types
-- Declarations of some simple variables
int i,j,k											
float f						
byte b						

-- constants are initialised with an assignment
const BottlesPerCrate = 24 
const Planck = 6.626069E–34	

-- Also for variables the type can be deduced from the initialisation
var Temperature = 0.0		
var Count = 77	

-- You can define a variable by referring to the type of another variable
var Fever like Temperature

-- Every type can have methods 
type distance is float with
	
	-- 'this' refers to the variable itself
	function km out float is
		return this / 1000.0
	end
	
	-- a small function (single expression) can also be written as follows
	function mm => this * 1000.0
end
	
-- declare and initialize a variable of the type 'distance'
distance WeeklyTraining = 15365.92
	
-- In strings expressions between [] are replaced by their values.
Window <- print("\nEvery week I run [ WeeklyTraining.km() ] km")
	
-- The cross product for vectors
xyz Force, Speed, Field
Force = Speed # Field

-- A coordinate transformation
matrix3	Transform
Force = Transform * Force

-- An explicit typecast
const double γ =  0.57721566490153286061
var x = <float>γ

-- This has the same effect
float x = γ
			

Arrays

Arrays are an important data type in CellSpeak and straightforward to define: the size of the array is simply added to the name of the type or the variable between square brackets. As all arrays are dynamic in CellSpeak, the size of an array can be a variable or an expression. Arrays can also easily be resized. If the size of an array is not known when declared, empty square brackets can be used. The index of an array always starts at 0.

Arrays can be one-dimensional or multidimensional. The implementation of arrays in CellSpeak is such that every variable of the type array also carries some extra information about its dimension and size. These few extra bytes reduce the chances of errors in loops or in array assignments

Arrays can be sliced, i.e. ranges of the array can be assigned to other arrays. As for all types, arrays can also have functions defined on them.

Strings are at their core just arrays of bytes. Starting from there several string types with their respective encodings and methods are defined in the standard CellSpeak string library. Strings are mutable, immutable strings are defined as constants.

Arrays
-- an array of 100 integers
int Numbers[100]			

-- size of an array determined by a variable
var NrOfCities = ...
float Distances[ NrOfCities ]

-- a new type for arrays of integers (size is not set)
type Counters is int[]		

-- ..now we have set the size of the array for a variable
Counters[365] CallsPerDay	

-- arrays can be defined by initialisation
int Primes[] = [2, 3, 5, 7, 11, 13, 17, 19]

-- A basic string - expressions between [] are replaced by their value.
byte[] TextLine = "The sixth prime is [Primes[5]]."
-- Result: The sixth prime is 13.

-- Multi dimensional arrays - an array of 10x10x10 vectors
xyz V[10,10,10] 				

-- an array of arrays is not the same as a multi-dimensional array
int I[3][4][5] 	

-- Example of the use of slices	- define an ascii string
ascii Smart = "Bob and Alice share the same password :'1234' !"

-- find the password
var i = Smart.find("1234")

-- overwrite the 1234 by using a slice
Smart[i:i+3] = "abcd"
			

Records

The design of a cell can be simple or complicated, so also inside the design of a cell it makes sense to use an object-oriented approach to simplify and share code.

Therefore CellSpeak has the type record. It has roughly the same characteristics of a class in C++, i.e. it is a collection of state and methods, has inheritane etc. Because CellSpeak allows a tight integration with software written in C/C++, the keyword class is used however to refer to external C++ classes.

Fields and methods of a record are referenced using a period '.' character. 'this' is the reference to the object itself.

CellSpeak does have pointers, but only for records, to allow to build interesting structures with records. The pointer specification can only be used in the definition of variables or fields, not in the definition of types.

Records can be sent as parameters in messages, but ony the data in the record is actually transmitted, not the methods obviously.

Records
-- Records have state and methods
type CityData is record

	ansi 	Country
	int		NrOfInhabitants
	float 	Altitude
	
	function HeightDifference( CityData OtherCity) out float is
		return Altitude - OtherCity.Altitude
	end
end

-- a variable of the type with initialisation
CityData London = ["United Kingdom", 8538689, 35.0 ]

Window <- print("\-\nLondon is the capital of the [London.Country], 
					has a population of [London.NrOfInhabitants] 
					and lies at an altitude of [London.Altitude] meter.")

-- Records have inheritance - we can derive a new type from a previous one
type MoreCityData is CityData with			
	rename 	Altitude to Elevation	-- we can rename fields..
	float	SurfaceArea				-- ..and add new fields and methods
end

-- We can define a record without having to define a record type		
var Painting is record
	ansi Title = "Victory Boogie Woogie"
	ansi Artist = "Piet Mondriaan"
	int NrOfColours = 3
end

-- supposse we have two variables (or parameters)..
float Distance = 1153.28
ansi City = "Vienna"

-- we can use 'keep' to copy variables as fields of a record
var Trip is record
	keep Distance
	keep City
end

-- We can create other records of the same type
var LongTrip like Trip

-- ..and set as required
LongTrip.City = "New York"
LongTrip.Distance = 5834.06

-- We can define pointers in field and variable declarations
type CityListType is record
	CityData 			City
	CityListType.ptr 	Next
end

CityListType.ptr CityList
			

Dynamic Memory

Memory is allocated to a variable by using the keyword 'new' followed by the name of the variable - not the name of the type. Memory is deallocated by using the keyword 'release' followed by the name of the variable.

Memory that is allocated to pointers that are declared at the highest level in a cell, is allocated on the heap. If not needed anymore, it has to be de-allocated. Note that when a cell is destroyed all its memory - stack and heap - is automatically returned to the system.

Because of the message based nature of CellSpeak, CellSpeak uses a second allocation scheme while a message handler is executed: memory that is allocated to pointers in functions or message handlers is allocated on the scratchpad memory and does not have to be de-allocated. Scratchpad memory is automatically returned to the system when the message handler exits.

The developer does not have to keep track of where a variable has to be allocated. The compiler and the virtual machine keep track of that. Also parameters to functions will allways be correctly allocated depending on their 'lifespan'.

The use of scrtachpad memory reduces considerably the burden on the developer to keep track of memory usage in cells - only permanent data has to be tracked until a cell is destroyed, which will also return that data automatically. Because of these features, CellSpeak has no need for garbage collection and consequently does not lose any time doing it.

Using the simple assignment sign '=' always copies values from the record on the right to the record on the left, irrespective whether left or right, or both, are pointers or not. If the right hand side and the left hand side are both pointers, then you can copy the pointer iso the value by using the pointer assignment ':='

Memory Allocation
-- A simple linked list
type CityListType is record
	CityData 			City
	CityListType.ptr 	Next
end

CityListType.ptr CityList

-- In CellSpeak we allocate a variable, not a type
new CityList

-- Syntax to access fields and methods is the same.
CityList.City.Country = "Germany"

-- Allocate the next
new CityList.Next

-- Copy the values from the first record
CityList.Next = CityList

-- Change the pointer in the last record, to point to the first record
CityList.Next.Next := CityList
			

Statements

Expressions and Assignments

Expressions are, as in any language, combinations of data and operators. In CellSpeak assignments are also expressions, the result of which is the left hand side. Operators and assignments can be combined for a more concise notation.

The swap-assignment is a statement that allows to exchange values without having to use a temp variable.

CellSpeak has two types of assignments, the normal assignment and the delayed assignment. In the delayed assignment the left hand side is first used in the expression or statement it is in, and only then replaced by the result of the right hand side. This allows for a more concise but equally clear notation in many circumstances.

For pointers there is also a pointer assignment but, because all arrays are dynamic, the pointer assignment can also be used for arrays. The normal assignment copies the content of one array to the other array, the pointer assignment copies the pointers to the content. If an unsized array is assigned to, then the array will be allocated to receive a copy of the right hand side. If the array has received a fixed size from the user, then only as many components will be copied as there is place in the array. Pointer assignment cannot be used for slices.

Expressions and Assignments
-- The ^ raises to a power and can be used for floats, doubles and integers
var y = a*x^2 + b*x + c

-- two assignments and a multiplication
c = (a = 3.14)*( b = 73.8)

-- declaring and initialising a number of variables
int r,s,t,u,v,w	
r = s = t = u = v = w = 144	

-- we can swap two variables without the need for a temp variable
a,b = b,a

-- Operations and assignments can be combined
a += 25.1		
b /= a + b^3	-- equivalent to b = b/(a + b^3)

-- Delayed assignment - i is first used to select and then incremented.
P = Primes[ i +~ 1]

-- Delayed assignment - the vector v is returned, and only then multiplied.
return v #~ [0,1,0]

-- Pointer assignment
SomeRecord A, B 
SomeRecord.ptr C, D
...
A = B	-- copies content of B to A
A = C	-- copies content of what C points to, to A
C = A	-- copies content of A, to what C points to
C = D	-- copies content of what D points to, to what C points to
C := D	-- copies pointer D to C
C := A  -- illegal, both sides must be pointers

-- Array assignment
int Q[20], R[20], S[10], T[]
...
Q = R	-- copies content of R to Q
S = R	-- copies 10 ints from R to S
T := Q	-- T and Q refer to the same array now
T = R	-- allocates T and copies 20 ints from R to T
			

Conditional Statements

CellSpeak has the usual if then else statement. Nested if then else statements can be simplified by using elif.

Very often we just need to check something and then have only one thing to do in both cases - false or true. For that reason CellSpeak has a conditional expression test ? execute if true : execute if false, similar to the same construct in C. The difference with C however is, that the right hand side can also be a control flow statement, like return, yield, leave or continue. This short conditional form fits on one line, where an equivalent if then else end is usally written on six lines.

Also in the short form, if there is nothing to do in the false case, the second part can be dropped. To enhance readability, the keyword is can be used in a comparison iso the operartor ==, both are valid. For a comparison to 0 or null, the comparison can be dropped altogether, as for integer types and pointers this is considered the default case.

Conditional Statements
int a, b
MyRecord.ptr p
...

-- A standard if then else statement
if a is 5 then 
	...
elif a > 5 then
	...
else
	...
end

-- The conditional expression
b = a > 5 ? a+1 : a^2

-- The right hand side can also be a control flow statement
a is 0 ? return : b = 1/a

-- Comparison to null or 0 is implicit
p ? yield

-- Combine an expression and a control flow statement by using &
a < 0 ? a=0 & return
			

Loops

CellSpeak has several variations on the for-loop. These variations reduce boiler plate code and make writing loops for arrays safer. Loop variables do not have to be declared beforehand, but it is not an error if they are.

CellSpeak also has a while do loop and a repeat until loop.

If the loop is only one expression long, a lambda arrow followed by the single expression can be used.

To restart a loop, there is a continue statement, and to jump out of a loop, there is a leave statement. In some cases it might not be clear which loop is restarted or left, therefore statements can be given a name using the task header. The leave statement then also makes sense outside a loop, the continue statement not.

Loops
-- Simple loop
for i=1 to 10 do 
	...
end

-- For a single expression we can use a lambda
for i=1 to 10 => single expression

-- Nested loops can be combined - k spins first, i last
for i=1 to 10, j = -5 to 5, k = 1 to 3 do 
	...
end

-- A is an array
for each a in A do
	...	-- a refers to the actual array element
end

-- If we need to use the indices of the array in the loop
for each [i,j] in A do
	...	-- i, j take all index values of A: 0..i-1, 0..j-1 
end

-- Looping through a list of values
for each prime in [2, 3, 5, 7, 11, 13, 17, 19] do
	...
end

-- A general loop
float Distance
for ( Distance = 0.25 , Distance < 12.50, Distance *= 1.5 ) do 
	...
end

-- Using leave and continue 
task FindPrimes
for (i=3,true,i+=1) do
	...
	task TestPrimes
	for j=0 to NrOfPrimes-1 do

		-- Check if the prime divides i
		i % Primes[j] is 0 ? continue FindPrimes 
		
		-- We can stop testing if the prime is bigger then SquareRoot
		Primes[j] > SquareRoot ? leave TestPrimes
	end
	...
	-- If we have enough primes, we stop
	NrOfPrimes is n ? leave FindPrimes
end
			

Switch Statement

The switch statement allows to select a statement, or a group of statements, based on the selector value. In CellSpeak the selector value can be a discrete type - integer, word, boolean, enumerated - or a string type.

All the selector values in the table have to be constants. If for a number of values the same statements have to be executed, then the values can be combined, seperated by commas.

Only the statements for the selected value are executed. There is no 'fall-through'. If there is no matching value for the selector, then the 'default' statements will be executed, if any.

The CellSpeak compiler puts the switch-entries into a sorted table, making the selection in the table very efficient, even for large table sizes.

Switch Statement

-- Selecting with an integer value
switch i
	case 17: 	Alpha = "seventeen"
	case 21: 	Alpha = "twenty one"
	case 13: 	Alpha = "thirteen"
	case 405: 	Alpha = "four hundred and five"
	case 55: 	Alpha = "fifty five"
	case 70, 5: Alpha = "Old model"
	default :	Alpha = "Unknown model"
end

-- Also a string can be used as selector
switch month

	case "january":		Days = 31
	case "february":	Days = 28
						Quotation = 
							   "Why, what's the matter, 
								That you have such a February face, 
								So full of frost, of storm and cloudiness? 
								
								William Shakespeare"
	case "march":		Days = 31
						Quotation = 
							   "Our life is March weather, 
								savage and serene in one hour. 
								
								Ralph Waldo Emerson"
	case "april":		Days = 30 
	case "may":			Days = 31 
	case "june":		Days = 30 
	case "july":		Days = 31 
	case "august":		Days = 31
						Quotation = 
							   "August rain: the best of the summer gone, 
								and the new fall not yet born. 
								The odd uneven time. 
								
								Sylvia Plath"
	case "september":	Days = 30 
	case "october":		Days = 31 
	case "november":	Days = 30 
	case "december":	Days = 31 
	default :			Days = 0		
end
			

Functions

Function Basics

All functions and methods in CellSpeak follow the same syntax.

A function that consists of a single expression can be written on one line. The return value is inferred from the type of the expression.

Functions can return several values, for example a result and an error indicator. Return values can be given names, but these names are informative only, i.e. they are not used as variables inside the function.

The CellSpeak compiler will determine the most efficient way to pass a parameter (by value or by reference). By default, parameters are in-parameters only and cannot be changed inside a function, but if a parameter is preceeded by out, then the parameter can be modified inside the function.

Functions can be modelled after another function by using the keyword like. If a function is defined without a body, add the keywords to do. If a function has no parameters, the no brackets are required. Calling a function requires brackets, to be able to distinguish between calling a function and assigning a function (see later).

Two functions can have the same name, as long as their parameter signatures are different. Functions can be nested. Functions at the highest level in a design have access to the permanent data of the cell, as you would expect from a 'method' of an object, nested function however do not have access to any other variables than their local variables and parameters.

Function Basics
-- A trivial function
function Add( int a, int b ) out int is			
	return a + b			
end

-- A record with a method
type BirthDay is record
	utf8	Name
	function Congratulate out utf8 is
		return "Happy Birthday, [Name]"
	end
end

-- A function without parameters and return value
function DoSomething is 
	...
end

-- Single expression function - the result determines the return type
function Multiply(int a, int b) => a * b

-- Multiple return values - inf_f is the IEEE constant for infinity
function Divide( int a, int b ) out ( float Result, ansi Error ) is	
	b is 0 ? return inf_f, "Division by zero !" : return a/b, null
end

-- a function with the same signature as Divide
function SomeOperation like Divide is
	...
end

-- a function definition without body - it follows later in the code.
function GetInformation(utf8 Name, int Nr) out float to do

-- Only the out-parameters can be changed inside the function
function FindAddress( utf8 Name, out utf8 Street, out utf8 City) is
	...
end

-- Functions can be called recursively.
function Factorial( int n) out int => n ? n*Factorial(n-1) : 1	

-- Example : a function to calculate the roots of a quadratic equation
function Roots( float A, float B, float C ) out (complex R1, complex R2) is
	
	-- Define a local function
	function Discriminant( float A, float B, float C ) => B^2 - 4*A*C

	-- calculate the discriminant D
	var D = Discriminant(A,B,C)
	
	-- Two helpful quantities
	var I = sqrt(abs(D)) / (2*A)
	var R = -B / (2*A)
	
	-- Return the results according the sign of D
	if D > 0 then
		return [ R+I, 0], [R-I, 0]
	elif D < 0 then
		return [R, I], [R, -I]
	else
		return [R, 0], [R, 0]
	end	
end	
			

Function Variables

The functions in the previous examples are constant functions, but CellSpeak also has function variables. A function variable is a function whose body can be changed either by an assignment or with the body keyword.

To declare a function variable, the function name is preceeded by var. The name of the function variable, like any variable, has to be unique. A constant function or a function variable can be assigned to another function variable as long as they have the same parameter and return signature.

Function Variables and Closures
-- Declare a variable and the function code for the variable
var function VectorFunction( xyz V1, xyz V2) out float is
	return V1*V2
end
	
-- Change the body of the function
body VectorFunction is
	return (V1#V2).length()
end

-- Define another function..
function LengthOfVectorSum( xyz U1, xyz U2 ) out float is 
	return (U1 + U2).length()
end

-- ..and assign that to the function variable
VectorFunction = LengthOfVectorSum
			

Function Closures

When a function - a constant or a variable function - is defined the function can take a snapshot of variables that are in scope at that moment and access these variables when the function is invoked. The data in the closure remains valid between function invocations, even if the variables that were copied in the closure go out of scope. The data in the closure can be changed by the function when it is called, but it cannot be changed from outside the function.

If a function has a data part, it starts immediately after the header of the function. The variables that one wants to include in the closure are preceeded by the keyword keep. The closure can also include other variables but then you have to use with .. end.

Assigning a function with a closure to another function, will also transfer a pointer to the original closure, i.e. they will share the same closure. If a new copy of the closure is needed, then the keyord new has to be used.

Closures
-- A variable that is in scope when the function is defined.
ansi Airplane = "Boeing 747"
ansi From = "Amsterdam"

-- A function definition that captures some data at its definition
function Fly(ansi To) 
	keep Airplane, From
is
	Window <- print("\nI fly from [From] to [To] with a [Airplane].")
	From = To
end
		
-- A function with data is called like a normal function
Fly("Berlin")		-- "I fly from Amsterdam to Berlin with a Boeing 747"
		
-- Next time we call the function..
Fly("New York")		-- "I fly from Berlin to New York with a Boeing 747"

-- We define a function variable without body
var function IFlyToo(ansi Where) to do

-- IFlyToo will allocate and copy the data fields from Fly
new IFlyToo = Fly

-- Calling the functions
Fly("Chicago")		-- "I fly from New York to Chicago with a Boeing 747"
Fly("Tokio")		-- "I fly from Chicago to Tokio with a Boeing 747"
IFlyToo("Paris")	-- "I fly from New York to Paris with a Boeing 747"
IFlyToo("Bejing")	-- "I fly from Paris to Bejing with a Boeing 747"

-- A function that captures two variables and defines an extra one
function FrameAction with
	keep AngularSpeed, Axis								
	matrix4 RotationMatrix = MakeMatrix( Axis , AngularSpeed)	
end 
is
	... 
end
			

Functions as Parameters

Functions can be passed as parameters to other functions. A function as parameter always starts with the keyword function to make it easily recognisable. The function signature can be determined either by using like to copy the signature of an existing function, or by using a simplified function definition, which is the same as a normal function definition but without names for the parameters. Also the output type(s) have to be put between brackets.

Functions - constant or variable - cannot be parameters in messages. Functions can access local cell data and sending code to another cell with a different layout would have unpredictable effects.

It is however possible to transmit code between cells in the form of bytecode files. The file can be registered at the local system and designs in that file can then be instantiated. This is the normal way with which application code is transmitted on distributed systems in CellSpeak. In the examples of the CellSpeak distribution there is an example of how this mechanism works.

Functions as Parameters
-- a function prototype
function SimpleFunction(int P) out int to do

-- We define an array type with member functions as follows
type NumberList is int[] with

	-- A function to initialise the array
	function Populate is 
		for each [i] in this => this[i] = i
	end

	-- A function to apply a function to all elements
	function Apply(function f(int) out (int)) is
		for each N in this => N = f(N)
	end	
	
	-- Alternatively we could have used
	function Apply2(function f like SimpleFunction) is
		for each N in this => N = f(N)
	end		
end
	
-- We define a variable of the type with size 20
NumberList[20] MyList

-- We set the elements of the list
MyList.Populate()

-- We define a simple function
function Square(int x) => x^2
	
-- And call apply - all entries will be replaced by their squares
MyList.Apply( Square )
			

Other Features

Integration with C++

CellSpeak code can easily be integrated with libraries compiled from code written in C/C++, without requiring access to the source code. In this way CellSpeak can re-use a massive amount of existing software and libraries.

The type class is a standard type in CellSpeak and is used to refer to a C++ class. When an external C/C++ library is loaded in CellSpeak, the compiler analyzes the library for the classes, methods, functions and types used in that library. The developer has to indicate which classes he wants to use from that library and has to establish a link between the types used in the library and the corresponding CellSpeak types. He then has access to these classes and their methods and the to the functions exported in the library.

The close integration of C/C++ in CellSpeak also makes it possible to re-engineer an existing C/C++ application, for example to break it up in a number of micro-services. The messaging layer of CellSpeak can be added gradually as the application is being disentangled, while the rest of the code can be left in C/C++. This makes porting such an application a gradual, controlled process.

The correspondance between standard C/C++ types and standard CellSpeak types is straightforward and for many types the same name is used in both languages. For structures the developer has to pay some more attention. As C++ libraries do not contain layout details of structures, it is up to the developer to make sure the layout of a CellSpeak record corresponds to the layout of the structure.

C/C++ functions or methods often take pointers as parameters. In CellSpeak it is therefore possible to pass a pointer to a variable in a C++ function or method call, by using the & sign.

Integration with a C++ library
-- An external library always has its own group
group D3D11 is lib "Direct3D11Lib.dll"

-- type mapping from the lib types to CellSpeak types of the same name
lib type int, float, double, void

-- C++ types with special characters are put between ""
lib type "Vector3<float>" is xyz

-- 'unsigned int' corresponds to a word in CellSpeak
lib type "unsigned int" is word

-- The C type colour corresponds to the CellSpeak type Color.rgba
lib type colour is Color.rgba
	
-- lib classes we want to use
lib class CameraClass
lib class MeshClass
lib class CubeClass
	
-- We can rename classes in the library
lib class SphereClass is PolarSphereClass

-- We define a CellSpeak record..
type MeshType is record
	MeshClass	cppMesh	
	PointType 	Point[]		
	word16		Index[]				
end

-- ..and establish the equivalence with the MeshStruct of the D3D11 library
lib type MeshStruct is MeshType

-- We can define now a CellSpeak variable of the type class..
class PolarSphereClass	Sphere

-- ..and using local CellSpeak parameters..
xyz Origin
float Radius

-- ..we can call the methods of this class
Sphere.Create(Origin, Radius)
-. 
The library analyser in the compiler knows with which C++ types 
the variables 'Origin' and 'Radius' correspond, and is able to find 
the method 'Create' with that parameter profile, in the C++ library 
.-

-- We can pass a pointer to a variable as a parameter to a C/C++ function
CppFunction( &Variable )
			

Exception Table

Exceptions are events that occur in a program for which it is difficult or cumbersome to cope with in the normal flow of control of the program. The exception can occur because of some action in the current code, or because of a problem in a possibly long list of functions that was called from te current code.

If there is a choice, it is almost always better to handle error conditions as much as possible in the normal flow of the code, because it is faster and often 'cleaner'. But there are situations where this is not possible or desirable and then the exception mechanism can be used.

To handle exceptions in CellSpeak, each function, message handler, constructor etc., can define one optional exception table. The exception table defines what has to happen in that function when an exception occurs. Exceptions have a name and can have parameters.

When an exception occurs the VM will check the current code for and exception handler and, if not found, will start to unwind the stack, i.e. take the functions that are on the calling stack off one by on, until it finds a function, message handler etc. that has an exception handler for the exception that has occurred. It will then transfer control to that entry in the table. If no handler is found, it will take a default action which, at worst, will result in the destruction of the cell where the exception occurred.

Exception Table
-- a simple function
function IntegerDivision(int u, int v) out int is 
	return u/v
end

-- a design with an exception table
design ExceptionExample is

constructor is 

	-- This will generate an exception..
	var c = IntegerDivision(9,0)
	...
end	

-- The exception table of the design
catch 
	
	-- we can use a lambda arrow if the handler is a single expression
	DivideByZero(int r,int s) => system <- println("DivideByZero: [r]/[s]")
	
	-- we can also intercept all other exceptions with a default exception
	default (utf8 Name) => system <- println("[Name] was thrown !")
end 

end -- design
			

Templates

Templates in CellSpeak are used to write code which is applicable to several types. A template is defined in CellSpeak and then instantiated for the actual type(s) with which the template is supposed to be used.

Templates, combined with records and inheritance, are powerful tools for code re-use and are particularly well suited to create all sorts of container structures.

In templates it is also possible to add conditional code, e.g. for testing purposes.

Templates
-- A group for a double linked lists
group Structures.DoubleLinked
	
-- A record prototype for use in a double linked list
type Record is record 
	Record.ptr	Previous
	Record.ptr	Next

	-- Some methods for this type
	function InsertAfter( out Record R) is ... end
	function InsertBefore( out Record R) is ... end		
end

-- The linked list is a template with two parameters
template LinkedList for ListType, RecordType is

-- Define the ListType
type ListType is record 
	
	-- This is where the linked list starts
	RecordType.ptr Head		
	
	-- Some methods for the ListType	
	function Push(out RecordType R) is ...	end
	function Pop() out RecordType is ... end
	function Remove(RecordType R) is ... end	
	function New out RecordType is ... end
end 

template end -- a template ends like this

-- To use this template we would first define the actual record to use
type MyRecord is Record with
	...additional specific fields and methods
end

-- ..instantiate the template with this type and a name for the list type
use template LinkedList for MyListType, MyRecord

-- ..and then we can create variables of this type
MyListType MyList
			

The Module Section

The unit of compilation in CellSpeak is the module. A module is defined by the module section in the first source file. The module section mainly contains the list of files that are needed to build the module:

source is the list of source files that are part of this module. The order of the files is not important and the file with the module section is not included.

cells is the list of compiled packages from which only the cells and messages are used. The version and content of thes packages can change without requiring recompilation of this module.

symbols is the list of packages from which also the symbols are used - types, functions etc. When these packages change, the module also has to be recompiled. Note that a module can import symbols from a package without needing access to the source code of the module.

xlib is the list of directories - not files ! - where the external libraries used in the module can be found.

The module section can also contain a version string that is stored with the module.

Module section
module is 

	-- 'source' contains the source files of the project
	const source = [
		"source_dir\\file_1.cellsrc",
		"source_dir\\file_2.cellsrc",
		...
		"source_dir\\file_n.cellsrc" ]
		
	
	-- 'cells' :  packages for which only the designs are loaded
	const cells = [
		"package_dir\\package_1.cellbyc",
		"package_dir\\package_2.cellbyc",
		...
		"package_dir\\package_q" ]
		
	
	-- 'symbols' : packages for which also the symbols are loaded
	const symbols = [
		"package_dir\\package_1.cellbyc",
		"package_dir\\package_2.cellbyc",
		...
		"package_dir\\package_m" ]

	
	-- 'xlib' : the directories where the ext. libraries can be found
	const xlib = [
		"lib_dir1",
		"lib_dir2",
		...
		"lib_dir3" ]
		
	-- A version
	version = "some version description"
end