Menu Close


Level: Program Sections

Section concept

Level define sections of code to split a large program into smaller, manageable parts. It is an abstract concept used to implement separation of concerns architecture principle. The section keyword do not actually exist in Level.

Sections are a general language structure. This can help compiler developers to organize the code. Compiler will decide what kind of section to deal with and parse the code accordingly. In this article we explain section syntax and enumerate the sections types implemented.

Unnamed sections

These sections start with “let” or “for” keyword:

[let | for] 
end <section_type>;

Note: “let”  keyword is optional. It can be used with all anonymous blocks except “for”.

section scope:
Keyword “let” define a local scope. User can define one or more local variables here. This variables are called “control variables” and can be initialized with a literal or expression from parent scope or use type inference to define new variables.

section body:
The section body contains one or more statements that execute once or multiple times depending on section type. Executable region can be terminated normally or early using a conditional exit point or exception.

Named sections

Named sections are larger then the anonymous sections. These kind of sections have a name and can receive parameters.

<section_type> section_name(<parameters>) is
end <section_type>;


Anonymous sections are introduced in Level-1 and 2:

  • Level-1: { do, if, loop, switch, check, step}
  • Level-2: { for, job }

Named sections are gradually defined in Level (1,2,3):

  • Level-1: { program,  procedure,  function  };
  • Level-2: { method, closure, generator, coroutine };
  • Level-3: { class, feature };

Nested Sections

Sections can be nested in a parent child relation. Each section has a local scope. The visibility of member elements is from outer scope to inner scope as a hierarchical tree. Declared elements of a parent section are visible in a child section but elements defined in a child section are not visible in the parent section.


Programs can be implemented only in the module scope not in the local scope of another section.  Procedures can have nested procedures or static functions. Static functions can have local functions but not procedures. Methods can be defined only in the module scope after type definition. Methods can have local functions. Local scope created using Let do not allow creation of local procedures, functions, types and methods.

Relation is 1:M in this direction.



Section regions

A section of code have two regions: The “declaration” regions and the “executable” region. The regions are divided in sub-regions using keywords: {is, begin, step, recover, finalize}. Keywords {step, recover, finalize} are optional. Steps are indented 2 spaces after begin keyword.

Declaration region

For named sections declaration region start after the name of section before “begin” or “export”. This region create a “name space” that contains definition of members: {variable, procedure, method, function }.

For unnamed sections we use “let” keyword to create a local scope. This scope is available in section: {if, switch, check, loop, job} and is terminated with keywords: end <section_type>. Let region can contain only variables no types and no local functions.

Executable region

Usually there is only one executable region for a section. Executable region is between “begin” keyword and recover or finalize or end of program. This region contains steps, expressions, procedure calls and nested unnamed sections.

Executable region can be normally terminated when all the statements are executed. In case of exception the normal execution is intrerupted and the recover region is executed. After first executable region is executed the finalization region is executed also all the time even if we had an error.

Recover region

Define an “exception handling region”. This region is executed when an exception is raised. Consist of a series of decision statements based on “error.code”. The recover section is executed before the finalization region. Here we can use decision statements: (if, when, switch) or selector  (check … match). The most common way is to use “when” decisions.

  when exception.code == <code> then
  when exception.code == <code> then 
end <section>;

Finalize Region

This region is executed before the program control is released to the parent section. Finalize region is executed regardless of termination case. Normal termination or early termination will continue with finalize. Notice finalize is executed after recover region.

Programmer will close any file or connection to databases or other resources that may be reserved for the section. Resources that are locked and not released may remain locked for a while until the program terminates and the operating system release the locked resources.

Note: The recover and finalize regions are available for named sections: { program, module, procedure, function, class } and not available for unnamed sections.

Section description

Named section properties are accessible using keyword: “this”. We use several predefined properties that can be assigned in program body.

procedure test is 
  this.description="Procedure description";

Named parameters

Parameters may be specified by name or by position. We use notation -x or –param_name to pass parameters by name. A parameter can have alternative short name or long name. This is done using notation (x | name [=value] : type).  The value is the default value of the parameter and is optional. Parameters that do not have a default value are mandatory.

Variadic parameters

Some programs can receive arguments from the command line when it is called by the operating system. These parameters are usually named parameters. Unnamed parameters are received in default variable “arguments” that is a vector declared using variadic notation “*”. The variadic argument is last in the list of parameters.

Note: Using single arguments -x with no value is usable only for Logic and Numeric parameters. This is going to be the True or 1 if parameter is available and False or default value otherwise.

We can define default value for multiple parameters like this: (x|first=-1, y|sec= -1: Integer). Then we know that the flag was used with no arguments. The name of the parameters “first” and “sec” are used in the program as variables while “x” and “y” are used as flags -x and -y.

Note: A section can terminate normally or with an exception. Developer will create programs such that will return 0 when program terminate normally or a number representing the last exception code to signal program termination with failure to the operating system.


By default this example will print “hello world”. If an argument “name= John” is given then will print “hello John”

  from console use print; 

procedure main(user_name='world':String) is
  message='Hello ':String; 
  message = message & user_name;
end procedure;

Level Project

A project is a folder structure on the operating system specific to level programs. This structure can contain one or several main programs. Any of this program can be compiled into an executable application. The application can use one or more programs. Programs can be used to collaborate into n-tire application. For example one can be the server while other could be a client app.


In any good computer language there is a modular structure available to create large applications. For example in Java one package is a folder that can include multiple classes. In Python a folder become a module if contains a special file This looks very stupid to me.

We have defined modules to contain code. Each module can be regular or library. A regular module belong to project. A library can be shared between projects. Level comes with a set of modules that are called “Standard Libraries”.


Modules and libraries can be grouped in sub-folders. The main program is found in the main module that is stored in project root. We can have several programs into the root folder. These programs can be executed by a Leve virtual machine or can be compiled into bin folder. Once compiled the executable should not depend on the folder structure.

  |---> bin
  |---> module
  |      | module1.lev
  |      | module2.lev
  |---> library 
  |      | Library1.lev
  |      | Library1.lev
  |---> documents
  | app_client.lev
  | app_server.lev

Module Syntax

-- Module comments
  <library_name> as lib_alias;
  from <module> | <alias> use all;
  from <module> | <alias> use <public_member>[,<public_member>]...;

end <section_type>;

Note: Regions can be repeated. For example a module can have multiple {constants, variables, types} regions. The order of sub-regions is important. Import sub-regions are always first. If a member is not defined it can’t be used.

Import region

Is used in programs and module to include one or several libraries. Imported libraries are enumerated in this region and are separated by semicolon “;” on several lines. The next region or reducion the indentation will terminate this region.

Import statements have several forms:

  <lib_name> as <lib_alias>; 
  <folder>.<lib_name> as <lib_alias>; 
  from <lib_alias> use all;
  from <lib_alias> use <member_name>[,<member_name>][...];

The library is search in local library folder. If not fount standard library is used. If the library is not found then the path $LEV_PATH is used. If library is not found then the program fail to compile. $LEV_PATH can be specified in the environment variables or can be declared as a constant in the main program.

Forward declarations

Before use a named sections must be declared then implemented. A section that can call itself is recursive. For this we can declare the section without it’s implementation and implement the section later in the program. This is called forward declaration. We use “;” at the end of this declaration.

<section_type> section_name(<parameters>);

Note: Forward declarations are necessary. We do not use “hoisting” technique in Level-1. This technique can be used in Level-2 and Level-3 to avoid forward declarations. However Level-1 compiler must be simple and fast therefore we use forward declarations.

Export region

A module that export some of it’s members becomes a library. This can be reused by other modules.  One library is imported and parsed only once. If a library is found a second time in a nested library it is not parsed again. Also during execution variable values are initialized only once.

System Constants

There are several predefined constants that program will provide for dealing with environment. System constants are using uppercase letters and “$” prefix. These variables can be concatenated with dot “.” that will be replaced by “\\” or “/” depending on the opperating system.


Path separator

We use symbol “/” to separate path folders. This is specific to Linux. In Windows this symbol is inverted automatically to “\” . The replacement is JIT: just-in-time before usage in system functions to handle file locations.

Project Folders

The libraries are stored in folder $PRO_LIB. Modules are stored in folder $PRO_MOD while the standard libraries is folder $LEV_LIB. The location where the Level is installed is $LEV_HOME. These variables are created by the Level run-time environment.

$LEV_HOMELEV_HOMELevel home folder
$LEV_LIBLEV_LIBLevel library home
$LEV_PATHLEV_PATHLevel library path
$PRO_HOMEProgram home folder
$PRO_LIBProgram library home
$PRO_MODProgram module home

Standard Library

Level project can define it’s own set of modules and store them in sub-folder: /library. These modules have the purpose to be reused. Level language comes with a set of predefined modules organized as “standard library”. These are written in Level or other languages and contains signatures of procedures and functions that are available for reuse. It is also called built-in library.

Library Import

A library stick in memory and is not free until the program or module terminates. On the contrary a module is loaded in memory it is executed and is free from memory after execution. We import other libraries to use it’s exported members.

Type Methods

Methods are special sections attached to a type. Methods are like functions with parametrized types. We use keyword self to reffer to the variable that was extended. With this technology we can extend existing types or create types with new methods. The difference from a function is a method can have optional result while a function has mandatory result.

Method section:

method (<type>) <name>(<parameters>)[=><type>] is   
end method;

Method usage:

We can use methods with a composite declarations to enhance it’s functionality. After this we can use a method using dot notation with qualifier or with _do_ section. When we export a type, we do not have to also export the methods. The methods become visible automatically.


-- define a public type
  Person: Record of ( name: String, salary: Integer ); 

-- attach a method to Person type 
method (Person) raise_salary(amount:Integer) is 
end method; 

procedure main is
  person1, person2: Person; -- not initialized
begin -- main
  -- create and initialize two persons
  person1:=Person("Ion",1000); !use default constructor
  person2:=Person("Ana",1000); !use default constructor
  -- raise salary
  person1.raise_salary(100);   !raise salary with default 100
  person2.raise_salary(150);!raise salary with 150
end procedure;

Read next: Level: String Templates