Menu Close


Level: Basic Types

In programming languages, a type system is a collection of rules that assign a property called type to various members of a program, such as constants, variables, parameters and function results. We can have system define types and user defined types.

Type Paradigms:

Level language is statically typed with  type inference and polymorphic types.  That means a variable and constants must be declared with type information and can’t be changed during run-time.

We wish Level to be a safe language. Having strong types has advantages and some minor inconveniences. Our focus is productivity and maintainability. We create a compiler not an interpreter therefore strong types better for Level.

Native Types

Native types are data types known by the operating system that can provide support for manipulation and storage. That means native types are the most important and must be defined first in the language.

Level-1 native types are: { number, logic, string }

Composite Types

Composite types are defined by Level language as base types to be extended by the user.Most significant composite types are:

{ tuple, vector, matrix, record, list, set, map }.

User Defined Types

All type names start with capital letter and contains digits and lowercase ASCII letters. We do not support Unicode identifiers and also no special characters except underscore that is used instead of space.

  • Developers can define types in the declaration region of a program;
  • Users can define types in a library and these types can be exported;
  • The program or modules can import types or classes from a library;
  • For readability purpose we include keyword type in front of a user type declarations;

Type usage

Once a user type is defined it can be reused as a built-in type. The type can be associated with other composite types or with variables. We know that we declare a type not a variable because type names start with a capital letter.


procedure main is
  Point:Record of (a:Integer, b:Integer);
  p1, p2: Point; --> type reference
  print("p1: a=#n, b=#n" <- p1);
  print("p2: a=#n, b=#n" <- p2);
end procedure;
p1: a=1, b=1
p2: a=2, b=2


A constant is a memory container that is initialized with a literal. Constants are immutable. That is a constant can’t be changed during program execution and must be initialized to a value. A constant is declared in a constants region. Constants can use only uppercase letters, underscore and digits. Some example of predefined constants are: { PI, TRUE, FALSE }.

Note: A constant can’t be defined with one single uppercase letter, it needs at least 2. A good practice is to use C_… prefix. Using uppercase letters is not mandatory for example c_constant is as good as C_CONSTANT. We recommend uppercase though.


  C_ERR="Error măi ţâcă!":Unicode;


A variable is represented by a symbol, and is associated to a type. Variables represents a memory container having a defined structure and size. Variable value is stored on a specific memory address and use several other memory addresses that are usually consecutive. Content of a variable can be changed during the execution of the program.


We use keyword “variables” to declare one or several variables. Identifiers for variables are using lowercase letters, digits and underscore. It is good practice to use several characters not only one. A good convetion is to use a prefix for example v_this_variable. Having (v_) prefix we will know every time we read this identifier that is a variable.

Syntax for Region:

  r2=0.2, r3=0.3 :Real;

Variables can be defined in one line. We separate multiple variables using comma separator. When multiple variables are declared in one line all will have the same type. The value for each can be set using  define symbol “:”.  Variable keyword is optional if we do not have other regions to care about.

Syntax Alternative:

This alternative allow use of optional keyword variable with indentation of two spaces. This keyword is useful when we declar only several variables of the same type and we do not have a group of variables to define.

[variable] <name>=<value>[,<name>=<value>] ... :<type>;

Note: The variable keyword is optional

Initial value:

It is a good practice to initialize variables with explicit literals. Literals are constants but they are used one single time and have no name in the program except after they are assigned to a variable.

If the initialization is missing the variable take default Zero value. This is 0 or .0 if a variable is numeric and Empty ” for strings. Composite types are not initialized. Uninitialized composite variables are Void and can be checked using “is” operator like this: “m is void”.

System Variables

We can define system variables using “$” name prefix. These variables can be created using operating system and are known as Environment Variables. Users can define global variables using this prefix: “$”. They can be derived from the existing environment variables using concatenation “.” notation. This concatenation is depending on operating system. On Windows it is replaced by “\\” on Linux is replaced by “/”. There is no need to use “…” for strings when we use this notation except if the string contains spaces.

  $SEARCH_PATH = $PRO_HOME.components;


We use several operators for assignment: The primary operator is “=”  used for initialization. Other assign operators are called modifiers: {:=, +=, -=, /=, *=, %=, ^= }. This operators are polymorphic. The assignment triggers automatic memory allocation if there variable is a reference to composite type.

In declaration region a variable can be initialized using “=” to a constant literal, parameter value or other variable value. We can’t use an expression to initialize a variable. Expression based initialization is done in executable area. This is called deferred initialization.

Note: We use colon “:” to set value for collection literals or tuples of arguments. This is called pair-up operator. It is used for records and maps as well as procedure, function and method call with named parameters.


procedure test_assign is
  <variable_name>=<value_literal>:<type>; !declare with initial value
  <variable_name>:=<level_expression>; !assign new value
end procedure;


-- in this example we use multiple lines to declare variables
  a=10:Integer; --define 1 variable with initial value 10
  b,c,d=40 :Integer; --define 3 variables b=0, c=0, d=40 all integers

Note: variables b and c will have default value zero.


This example introduce a concept that is called “slicing” operator “..”

-- int this example we define several constants and variables
  PI=3.14:Real; ! define a constant 
  var1, var2:Integer; !define module variables; 
  $big_data:Vector(1000) of Integer; !define global variable 
program test_var is
  a,b,c=40:Integer; !define and initialize b=0, c=0, d=40 
  big_data:=var1+var2; !set all 1000 elements to 25
  big_data[50..]:=50; !make last 950 elements 50
  big_data[..49]:=10; !make first 50 elements 10
  big_data[100..149]:=100; !make 50 elements starting from 100 to 149 equal to 100
  big_data[0]:=a; !set first element to 0
  big_data[1]:=b; !set second element to 0
  big_data[2]:=c; !set third element to 40
end program;

Identifier names

The name of identifiers in Level can have a length of 64 characters. A name starts with lowercase letters (a..z) or capital letters (A..Z) followed by one or more characters or numbers. No special characters or spaces are permitted in the name except underscore “_”.

A variable name can’t start or terminate with underscore but can contain underscores. This is beneficial for writing Wiki page templates since in wiki notation the underscore is used for italic text like: _italic_. It could be very confusing for developers to create templates using variable names starting or ending with underscore.

The underscore is equivalent to space. So the identifiers that have space in a JSON or in a database can be mapped to internal variables that use an underscore instead of a space as identifier.


These are invalid identifiers

1'st step
1a, 2b,3c

These are valid identifiers

a, b, c, i, k, m, n, p, q, t

Naming variables

Variables usually have a meaning or a purpose therefore variable must have a proper name. Variables can’t have are the language reserved keywords. Therefore we advise for variables to use a prefix. The prefix can be a single letter or several letters.  “v_” is a good prefix for variables; “p_” is a good prefix for parameters.

Sometimes the prefix is not improving readability so you can take a bold approach. You can give creative names to variables to make your code very readable.

Example: In next example we name a logical variable and use the “is” operator.  This time by not using any prefix for logical variable we make a very readable program.

procedure test() is
  situation=TRUE; !make situation true
  if situation == urgent then
    print("this is an emergency");
    print("this is a normal situation");
  end if;
end procedure;

Note: Keywords “variable” is optional. This is required only for programs and modules when the import region is present. Otherwise the variable region is implicit and is available for multiple variable declarations.

Numeric types

In mathematics there are very few numeric data types defined and well known:

  • ℤ = Integer
  • ℕ = Natural
  • ℚ = Rational
  • ℝ = Real
  • ℂ = Complex

All of these types are infinite. Level is designed for handling these types at maximum computer capacity and can’t emulate the infinite number. Level do not try to be a hero and does not define anything new. We try to reduce complexity as much as possible.

Number representation:

Computer science define numerous representations for numbers. Some more efficient the others. Level is a 64 bit language. That means we use 64 bits for representation of integer numbers. We have distinguish these important type categories required in Level:

Type categoryMath Types
Discrete Types{Byte, Short, Long, Integer, Natural};
Continuous Types{Real, Rational, Decimal, Complex};
Polymorphic Types{Number, Positive};  (variants)

Discrete numbers:

We define several discrete numbers in Level:

typeChars(*)Bytesminmaxmaximum numberLevel
Long114-2³¹2³¹-1≤ ‭2,147,483,6472
Integer208-2⁶³2⁶³-1≤ 9,223,372,036,854,775,8071
Natural20802⁶⁴-1≤ 18,446,744,073,709,551,6152

(*) For conversion into characters:

  • The number of characters required for long numbers is 11. (10+sign);
  • The number of characters required for integer numbers is 20. (19+sign);
  • To reduce the number of bits a user can define a decimal number.

Note: I have used Unicode superscripts: N⁰¹²³⁴⁵⁶⁷⁸⁹ ⁻ⁿ to represent power. In level power operator is x^y.

Floating numbers

The type real is represented using floating decimal number.
Floating decimal numbers are most simply described by 3 integers:

  • s: a sign (0 or 1)
  • c: a coefficient
  • n: an exponent

The numerical value of a finite number is −1ˢ × c × 2ⁿ Using this formula level define one singl type: real.

real number:
Real = double-precision 64-bit IEEE 754:
sign has 1bit, exponent has 11 bits and coefficient has 52 bits;

See also: scientific notation

Numeric literals

Level 123 can support several notations for numeric literals.

0Integer zero
1234567890Integer number using symbols: (0,1,2,3,4,5,6,7,8,9)
0b10101010Binary integer using symbols: {0b,0,1}
0xFFHexadecimal integer using symbols: (0x,0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F)
0.5Real number: (.,0,1,2,3,4,5,6,7,8,9)
5E2Real number 5 * 10²  = 500
5e-2Real number 5 * 10⁻² =  0.05

Rational number

Rational number is any number that can be expressed as the fraction p/q of two numbers.  Numerator p of type cardinal and a non-zero denominator q of type positive. Since q may be equal to 1, every integer is a rational number.

Notation: p\q

Example define a variable having value 3\4 = 0.75

variable v=3\4 :Rational;

See also: wikipedia rational data type

Decimal number

Decimal number has a magnitude and resolution defined by the user.
Is defined using “fixed point arithmetic” declared using Q notation:

  • Qm.n is m+n+1 bit signed integer container with n fractional bits.
  • Qm is a m+1 bit signed integer containing 0 fractional bits.
number of bits = m+n+1
resolution is 2⁻ⁿ
range is [-(2ᵐ), 2ᵐ -2⁻ⁿ]

For example A number format “Q5.2” can store in range(-32.00 to 31.75) on 8 bits.

  • with resolution of 0.25
  • from value: -2⁵ = -32.00
  • to value: 2⁵ – 2⁻² = 32 – 0.25 = 31.75

Declaration example:

variable v=0.00 :Decimal Q5.2;

Number example:
A number format “Q20.11” can store range(1048576, -1048575) on 32 bits with resolution = 0.0005

See also: wikipedia Q number format

Polymorphic number

To declare a polymorphic number we use keyword: Number. This can support any kind of numeric data type re-defined at run-time. Numeric type can be used for declare generic procedure or a function and use polymorphic operators.


procedure test is
   r: Real;        -- initial value is 0.0
   v: Integer;     -- initial value is 0
   q: Rational;    -- initial value is 0\0
   d: Decimal Q7;  -- decimal on 8 bits  = range(-128 .. 127)
   d: Decimal Q15; -- decimal on 16 bits = range(-32,768 .. 32,767)
   v:=3\2;   -- variable v become of type rational
   q:=v;     -- implicit conversion to q
   r:=v;     -- conversion of rational in real 
   d:=v;     -- conversion of rational in decimal
   print(v); -- will print 3\2 not 1.5 
   print(q); -- will print 3\2 not 1.5 
   print(r); -- will print 1.5    
   print(d); -- will print 1  v is truncated    
end procedure;

Will print:


Complex number

A complex number is a number that can be expressed in the form a + bj, where a and b are real numbers and j is the imaginary unit, that satisfies the equation j^2 = −1. In this expression, a is the real part and b is the imaginary part of the complex number.

(1+2.56j)Complex number. (a+bj). When b is 0 the number is compatibe with a real number.
(-3.5-2j)Complex number. (-a-bj). Both a and b can be positive or negative real numbers.


Must read this first: Binary Numbers on Wikipedia. Each level dialect may chose to implement more or less types:

  • Level-1: {Byte, Word, Long, Integer, Real}
  • Level-2: {Byte, Word, Long, Integer, Natural, Rational, Real, Complex}

ASCII String

Leve-1 strings are collections of Extended ASCII symbols. Each character is represented using a byte= 8bit equivalent to integers from 0 to 254. Printing extended ASCII symbol on terminal is dependent on the operating system local settings (code-page).

ASCII Encoding

Default code page 437 is used for MS-DOS and Windows console. Greek alphabet code page 737 is preferred for mathematics algebra.

Change code page on Windows

Programmers working in Level 1 can decide to use a console editor (like edit) that is capable to display correct the characters for specific code page. User can verify or change the console code page using command chcp:

C:>chcp 737

Note: Level is using regular ASCII characters from 0 to 176 to define program symbols and operators. For strings programmers can use several additional characters specific to a language or region. In Level 1 we use this list of characters to represent the strings: Code ASCII Standard

String Literals

We can create new strings using string literals. String literals are enclosed in single quotes ‘…’ and can include only printable characters. For non printable characters we must use escape sequence. We use double quotes “…” for a Unicode text literal.

String declaration:

<string_name>: String;
<string_name>: String(10); 
<string_name>='list of chars':String;


program test_string is
  str:String; -- strings are initialized with ''
  str:='This is a string.';
end program;

String concatenation

Strings can be concatenated using the string concatenation operator: “&”.

-- this is example of string concatenation
str: 'this string is ' & 'concatenated with other string'); 

Escape sequence

For special characters we use escape sequence inside a string literal. We use backslash “” symbol in front of the special character. This is to keep the strings compatible with other languages. To represent “” character we must duplicate the symbol “\”.

A string literal containing a single quote, we can escape using: \’


s:='This isn\'t my fault!';

=> This isn’t my fault!

A string literal containing a Windows file path that require double quotes:

s:='path="c:\\program files\\java\\bin\\"';

=> path=”c:\program files\java\bin”

Control code

Control codes are unprintable ASCII codes used for controlling the output device (classic printer). The range of control codes is (0..31) hex (0x00..0x1F) and DEL = 127 (0x7F). Other ASCII characters in range (32..126) hex (0x20..0x7E) are printable characters. Several control codes are represented as escape sequences (\x) to be more easy to insert into a string.

list of escape characters:

909HT\tHorizontal Tab
100ALF\nLine Feed
110BVT\vVertical Tab
120CFF\fForm Feed
130DCR\rCarriage Return
3422ESCDouble quote
3927ESCSingle quote

Note: It is also possible to insert control code into a string using concatenation operator and format placeholder #. For example format “#h” for hexadecimal and “#d” for decimal codes. This is explained into the string format page.

Unicode Strings

The unicode is a standard for representation of writing for all human languages. An unicode string is a set of code points using symbols from universal character set (UCS). Unicode is more difficult to represent then ASCII strings. There are many encoding techniques available. Java uses UTF-32. We will probably use UTF-8 to be more efficient.

See also: wikipedia ucsunicode characters


   us="I can write Greek: \"αβγδ\"." :Unicode;
end do;
I can write Greek: "αβγδ".

To edit source code containing Unicode literals one must use a specific font and UTF-8 source files. The preferred font for Level programming is “DejaVu Sans Mono”. Any other UTF-8 compatible font should work including “Lucida Console”.

Logic expressions

A logical expression is a sequence of code created with mathematical elements and symbols that represents a small demonstration or deduction and create a logical result that is TRUE or FALSE. The order of operations can be controlled using operator precedence and parentheses ().

Result of logical expressions can be used into a conditional statement to make a decision. Also results of logical expressions can be stored in logical variables to be used later in other conditions.

Logic type

Logic={FALSE, TRUE}: Option;

The Logic type is an option of two symbols {FALSE, TRUE}. So a logical type is not a number but a member of Logic type enumeration.


We can define logical variables using following syntax:

Initialize using explicit type

 <variable_name>=FALSE :Logic;
 <variable_name>=TRUE  :Logic;

Initialize using type inference

end do;

The logical variable is initialized using type inference or automatic casting. A logical variable that is not initialized is FALSE by default and can’t be Void. In data files and text files we prefer to store Boolean values as numbers: {0,1}.

Relation Operators

Numeric values can be compared using relation operators: {“==”, “<>”, “>”, “<“, “<=”,”>=”}. Some of these operators are extended to be able to compare other more complex types like strings. For example “this”==”this” Is very true in Level language, while in other languages this may not be true.

Note: Data coercion is automatic for comparing two numbers of different type but is not possible to compare a number to a string: ( “1” == 1 )  will fail. Correct is to convert one or the other before comparison. “1” == format(1) or parse(“1”) == 1 will be TRUE.

Keyword Operators

In addition to relation operators we can use several predefined keywords as logic operators. These keywords are also operators but instead of special symbols we use an English word that represents a logical operation.

inverify if a value is member of a vector, matrix or tuple
orlogical or
andlogical and
notlogical not

Data Coercion

In computer science coercion is used to implicitly or explicitly change an entity of one data type into another of different type. This is done to take advantage of type hierarchies and type representations.

Polymorphic operators

In mathematics there are very few operators: {+, -, / , *} that can operate with many kind of numbers. So the numeric operators are not very specific to a number type. This property of operators is called “polymorphic” and is a problem for computer science.

Some languages define different operators for integers and floating decimal numbers. For example in OCaml the operator “/” can divide integers while “/.” can divide floating numbers. This is unexpected for a mathematician or engineer. Therefore some other languages are introducing polymorphic operators.

In Level we implement operators using polymorphic functions. To resolve polymorphic aspect we use multiple dispatch using input parameters and output parameters. We store these functions in type related library by result type. We can have many procedures for combinations of types.

Implicit coercion

In Level the arithmetic operators are polymorphic. Numeric operators can do implicit data conversion to accomodate the data types and produce an accurate result. Automatic conversion is possible only when there is no risk of loosing data precision. If there is a los of data the program will generate a run-time error.

Implicit conversion is possible and safe in this direction: –>> Binary, Integer, Natural, Rational, Decimal, Real, Complex.

Explicit conversion is possible but unsafe in this direction: –>> Complex, Real, Decimal, Rational, Integer, Natural, Binary.

Implicit result type

Some operators determine a particular result type.

Operatorresult type

Examples of implicit coercion:

  a = 2; -- this cast a number literal to integer
  b = a; -- this implicit cast is possible b=2.0
  b = a + 3.5; -- this cast a to float and add 3.5 then assign result to b=5.5
  a = b;       -- error: it is not possible to assign a real to an integer
end do;

Explicit coercion

Explicit coercion is a forced conversion. Can be used to convert backwards from higher data range to lower data range or from continuous numbers to discrete numbers. This however can cause a data or precision loss.

The explicit coercion is using a functions. The function apply to an expression or variable that need coercion and produce a result of a new data type.

Examples of explicit coercion:

  a=0 :Integer;
  b=1.5 :Double;
  a:=floor(b); -- explicit coercion lose (0.5)
  print(a); ! will print: 1
  a:=ceiling(b); -- explicit coercion add (0.5)
  print(a); ! will print: 2
end do;

Automatic coercion to string:

Concatenate a number to a string using operator & will work. This is automatic coercion to string can be safe.

procedure test is
  print("this is a number: " & 123);
end procedure;

=> this is a number: 123

The best way to convert a number into a string is to use format placeholder #n and format operator “~”. This is a polimrphic operator that can use a string and a tuple to format numbers based on a string template.

procedure test is
  print("this is a number:#n and this is another:#n. " <- (123,456));
end procedure;

=> this is a number:123 and this is another:456.

Number to a string:

  s:=format(v); -- explicit coercion s="1000"
end do;

We need to use format() function to convert number to string. If we try to assign a numeric value to a string implicit conversion is not automatic like in other languages. Assignment operator do not do automatic coercion for strings. Only for numbers.

String to a number:

This can be done using the casting function parse(), only if the string contains a number. Otherwise the conversion fail and will rise exception.

procedure test is
  v:=parse(s); -- make v=1000
  v:=parse(r); -- make v=200 and decimal .02 is lost
  b:=parse(r); -- make b=200.02 and decimal .02 is preserved
end procedure;

Note: These { parse(), format(), ceiling(), floor() } are build-in functions that are located in _level_ library. This library is one of the standard libraries that are automatically included in any Level program. No need to use dot notation for using these functions.

Read next: Level: Composite Types