HomeForumSourceResearchGuide
<< back to guide home

Type System Overview

Dana generally separates data types from types which describe functionality, with two distinct type heirarchies. This is primarily done so that we can hot-swap code while maintaining common data formats between alternative implementations. Dana supports primitive types, data types, and interface types:

Both data and interface types support single inheritance. All data types automatically inherit from the base type Data, and all interface types automatically inherit from the base type Object.

Primitive types

Dana has two primitive types: unsigned integers, and signed decimal numbers.

Integers are able only to represent positive numbers, while decimal types can represent both positive and negative numbers. Integer types come in various sizes, with the default integer type being int. An integer variable declared as:

int a

Is an unsigned integer with a bit-width matching that of the host machine. On a 32-bit machine this is a 32-bit unsigned integer, or on a 64-bit machine this is a 64-bit unsigned integer.

Specific byte-widths of integer are also available from int1 up to int512, with each one being double the width of the previous one.

Integers are internally represented in big-endian form, regardless of the underlying hardware platform, such that the least significant byte is at the right-hand-side. This number representation is analogous to the way in which we tend to represent base-10 numbers, such that in the number "1024" the "4" on the right is the least significant part of the number.

Integers are also used to represent characters, bytes, and boolean values. A variable declared as:

bool b

Is equivalent to a 1-byte integer int1.

Characters are declared using the char type:

char c

Which is also equivalent to int1. Variables of type char have some particular semantic meaning to the Dana compiler, such as for inline string variables.

Variables intended to store binary data are declared using the byte type:

byte b

This type is also equivalent to int1, but has no semantic meaning to the compiler and so will be untreated by string-handling expressions.

Real number types are declared using the dec type, which can hold both positive and negative numbers:

dec d = 0.5

If the left hand side of an assignment is a decimal type and the right hand side is an integer, the integer is automatically converted up to its decimal equivalent. If the left hand side is an integer type and the right hand side is a decimal, the integer part of the decimal number is taken and assigned to the integer (discarding the fractional part).

Variables of type dec are fixed-point real numbers encoded in base-10. As with integer types, various specific byte-widths are available, from dec1 up to dec512.

Data types

Data types are the typical way in which collections of data fields are represented. They must be instantiated before use and are always accessed by reference.

Data types are declared as follows:

data Cat{
	char name[]
	int age
	}

A data type is initialised by using the new operator with the type name followed by the values for each field in the correct order:

Cat c = new Cat("Johan", 3)

You can also instantiate a data type by only setting specific fields by name, for example:

Cat c = new Cat(age = 3)

A data type can inherit from one other data type using the extends notation. A data type that inherits from nothing else automatically inherits from the base type Data.

When a data instance is passed between two components, it becomes read-only at the component that did not instantiate it; this read-only copy can be made into a writeable copy by cloning it using the clone operator.

Finally, data types can have constants associated within them in their type definition, for example:

data Token{
	const byte TTYPE_KEYWORD = 1
	const byte TTYPE_OPERATOR = 2
	byte tokenType
	}

The values of these type-associated constants are accessed using the notation TypeName.ConstantName, for example:

if (b == Token.TTYPE_KEYWORD)
   // ...

Interface types

Interface types represent functionality. They must also be instantiated before use and so instances are always accessed by reference. An interface may only contain function prototypes, event prototypes, and transfer fields (for adaptation); interfaces are not permitted to have any public data fields for direct variable access.

Interface types are declared as follows:

interface Hotel{
  Hotel(char name[])
  void addGuest(Guest g)
  }

In general the parameters declared on interface functions are assumed to be required to have a value passed in by the caller unless otherwise indicated. Interface functions (as well as local functions) can explicitly have optional parameters declared using the opt keyword:

interface Hotel{
   Hotel(char name[])
   void addGuest(Guest g, opt char allergies[])
   }

When using the opt keyword, every parameter that follows the first use of opt is assumed to be optional (i.e., the opt keyword does not need to be used more than once for the same function declaration).

An interface is implemented by a component which provides the logic of each function. An interface type can inherit from one other interface type using the extends notation. A interface type that inherits from nothing else automatically inherits from the base type Object.

Like data types, interface types can also can type-associated constants, which are declared in the same way:

interface File{
   const byte READ = 1
   File(char path[], byte mode)
   }

The value of a type-associated constant is accessed using the notation TypeName.ConstantName, for example:

File fd = new File("my_file.txt", File.READ)