Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / OO

Introduction to ELENA Programming Language

4.54/5 (69 votes)
10 Jul 2024MIT33 min read 277K   2.1K  
ELENA is a general-purpose, object-oriented, polymorphic language with late binding
ELENA is a general-purpose language with late binding. It is multi-paradigm, combining features of functional and object-oriented programming. It supports both strong and weak types, run-time conversions, boxing and unboxing primitive types, direct usage of external libraries. Rich set of tools are provided to deal with message dispatching : multi-methods, message qualifying, generic message handlers.

Introduction

A programming language is a formidable task to develop and to learn. So encountering a new language you may ask: why another programming language? The short answer is to try it a little differently. Why not to treat the message like a normal language element with which different operations are possible : loading, passing, modifying, dispatching? Could we do it without using reflection all the way? This was my motivation. ELENA is designed to facilitate writing dynamic, polymorphic code, combing elements of dynamic and static languages.

ELENA is a general-purpose language with late binding. It is multi-paradigm, combining features of functional and object-oriented programming. It supports both strong and weak types, run-time conversions, boxing and unboxing primitive types, direct usage of external libraries. Rich set of tools are provided to deal with message dispatching : multi-methods, message qualifying, generic message handlers. Multiple-inheritance can be simulated using mixins and type interfaces. Built-in script engine allows to incorporate custom defined scripts into your applications. Both stand-alone applications and Virtual machine clients are supported

To summarize all of the above, let's name some functions of ELENA:

  • Free and open source (MIT licensed)
  • Complete source code
  • GUI IDE & Debugger
  • Optional types
  • Multiple dispatching / multi-methods
  • Support of variadic methods
  • Support of yieldable methods
  • Closures
  • Mixins
  • Type interfaces / conversions
  • Class / code templates
  • Script Engine

Supported Platforms

ELENA 6.0 supports x86 (Windows / Linux)x86-64(Windows / Linux)Aarch64(Linux) and PPC64le(Linux).

Comparison with other languages

The main question is with what language? There are hundreds of languages out there and dozens of them are quite popular. And most of them are Turing equivalent. So this type of comparison is alway quit subjective. But there is a special site for this - Roseta code. You may compare around two hundred tasks impelemented in ELENA with any language you may think of - http://rosettacode.org/wiki/Category:Elena.

Let's take one - Closures/Value capture:

In C#:

C#
var captor = (Func<int, Func<int>>)(number => () => number * number);
var functions = Enumerable.Range(0, 10).Select(captor);
foreach (var function in functions.Take(9))
{
    Console.WriteLine(function());
}

In Clojure:

JavaScript
(def funcs (map #(fn [] (* % %)) (range 11)))
(printf "%d\n%d\n" ((nth funcs 3)) ((nth funcs 4)))

In Smalltalk:

Smalltalk
funcs := (1 to: 10) collect: [ :i | [ i * i ] ] .
(funcs at: 3) value displayNl .

In ELENA:

ELENA
var functions := Array.allocate(10).populate::(int i => {^ i * i} ); 
functions.forEach::(func) { console.printLine(func()) }

So which of them is better? The fastest one? It depends on the compiler. The shortest one? No idea. It is like asking which language is better French or English?

Portability

It is written in C++ & assembly. So it is quite easy to support a new CPU, only assembly part must be ported.

Writability and readability

The current syntax is similar to C-style grammar (though it is not 100% compatible). So it has the same advantages / disadvantages. In general ELENA uses LL(1) grammar. The language syntax structures can be extended using templates. E.g. if, while, for - are user-defined code template patterns. The language does not have built-in keywords. They are user-defined as well. (As a result it could be quite easily localized).
The language uses UTF-8 source files by default.

What are its advantages and disadvantages?

Con: the compiler could be considered as a semi-industrial (it is quite advanced in comparison to amateur compilers but still a lot of efforts should be put in it to achieve good code performance), very small (practically non-existing) userbase, small codebase.

Pro: mixins, message dispatching, multimethods, integrated into the compiler script engine, a debugger, full Unicode support (including keywords / attributes), templates (classes / operators)

Where Can I Get The Executable Of Compiler And Source Code?

ELENA Development Community

Build the ELENA Compiler

The ELENA compiler was fully implemented in C++. To compile, the compiler does not require external dependencies, such as Flex, Bison... you just need Visual Studio 2017 or higher.

The unit tests requires googletest

Alex implemented its own syntax generator, assembler and the tools he needed.

To build the compiler, you can open the CMD, go to root elena folder and type:

  • recompile60.bat

or, to generate release files:

  • build\create_package_x86.bat elena-lang-6.2.0-win32

First Steps

It is extremely important before beginning the program in a 100% object-oriented language, to know the basic concepts of a truly object-oriented language.

Basic OOP Concepts and Terminology

What are the basic concepts of ELENA ?

  • Objects
  • Classes
  • Fields
  • References
  • Methods
  • Messages
  • Inheritance
  • Receiver
  • Dynamic Binding

Classes

In OOP, a class defines a set of properties and methods that are common to all objects of this type. It may contain special methods to create its instances, called - constructors.

Objects

An object is an instance of a class. In general, objects are stored in dynamic memory. There is a special case of the object - singleton, which exists only as a single instance. Note that in ELENA, a class is an object itself and can be used like any other object.

Fields

Fields, also known as the instance variables, are the variables declared within a class. These variables can only be read with the use of methods to assign or read the contents of the field. Static fields are supported as well.

References

In C++, we talk about "pointers" to objects while in C#, we talk about "references" to objects. Both concepts are basically the same and references are implemented by straight pointers, which are just memory addresses. In ELENA, we use references.

Method

Method is like a function, except that it is applied to a specific object. We also say that the method is "invoked" on an object or "sent to" an object. The object in question is called the "receiver". Every method is invoked on receiving object. In C++ or C#, the receiver is called "this", in ELENA - "self".

Methods in ELENA are similar to methods in C# and C++, where they are called "member functions". Methods may take arguments and always return a result (if no result provided "self" reference is returned). The method body is a sequence of executable statements. Methods are invoked from expression, just as in other languages.

Messages

There is an important distinction between "methods" and "messages". A method is a body of code while a message is something that is sent. A method is similar to a function. in this analogy, sending a message is similar to calling a function. An expression which invokes a method is called a "message sending expression".

ELENA terminology makes a clear distinction between "message" and "method". A message-sending expression will send a message to the object. How the object responds to the message depends on the class of the object. Objects of different classes will respond to the same message differently, since they will invoke different methods. Generic methods may accept any message with the specified signature (parameter types).

The "Hello World!" Application

A great start for when we are studying or just knowing any new programming language, is always the famous program "Hello world!".

In order to create our first program, we have to get the ELENA compiler, for this, you just need to download the compiler in this link.

After you download and extract all the contents to the folder of your choice...

Important: You need to put the "bin" folder in your system path.

With the bin folder is in your path system, this is necessary to be able to generate the executable to access the elena runtime that is used for example, when a exception throws to show in what class & method the exception happens.

Let's start, first of all, we need to open the ELENA IDE, for this, you can type in CMD "elide" or, just open the executable called "elide".

After opening the elide IDE, we will create a new project, for this, we just need to go to menu "File->New->Project", when you click in the project menu, it will open a dialog like this:

Image 1

What every field means:

  • Type: Is the type of application than you will create, like GUI, Console, Console App running over VM ....
  • The namespace of application
  • The checkbox to warning about unresolved references
  • Additional options for the compiler
  • Target file name: the output file name
  • Output path: output of ELENA objects
  • Enable or disable the debugger
  • Command line arguments to test the application

For this article, we will use these specific settings:

Image 2

After you click in "ok", we need to create a new source file, for this, just go to menu "File->Source file" or use the shortcut "Ctrl+N".

Now, we can start to code... this is the code for Hello World.

ELENA
public program()
{
    system'console.writeLine("Hello World!");
}

After you write this code, you need to save the project and the source code in some directory and you just need to build it.

Understanding the Code

ELENA
public program()

Here, we declare a public function (a static class containging function message). By default, the "program" is the entry point of the program, but, it's possible to change this.

ELENA
system'console.writeLine("Hello World!");

In this line of code, we have very intresting information to look at:

  1. "system": It is a namespace, the namespace itself may contain sub elements separated by apostrophes
  2. "console": It is a class
  3. "writeLine": It is a message
  4. "Hello World!": It is the parameter of the message
  5. ";": Terminator

What does this mean for us?
That means that we send a message to method writeLine of class console in namespace system with parameter "Hello World!".

Like in other languages, like C#, C++, Python, we can add a special keyword to "import" some namespace, with this, we can 'reduce' code.

So, the hello world example with "import" keyword will be like:

ELENA
import system;

public program()
{
    console.writeLine("Hello World!");
}

The unique problem that we have with this program is that we can't read the output..., so, to fix this, we just need to use one more method, and, we can use this method in two ways.

First:

ELENA
import system;

public program()
{
    console.writeLine("Hello World!");
    console.readChar();
}

Second:

ELENA
import system;

public program()
{
    console.writeLine("Hello World!").readChar()
}

The last semicolon can be omitted.

This second way to write the same program works well because in ELENA, every method returns an object.

Declaring Variables

A variable is a named place in the program memory (for example in the function stack) which contains a value. In the simplest case it is a reference to an object or null pointer - nil value. A variable name can contain any UTF-8 symbols (except special ones, white-spaces; the leading character cannot be a digit) and is case-sensitive. There are no reserved keywords. Variables with duplicate names (declared in the same scope) are not allowed.

ELENA
public program()
{
    var myFirstVariable := "Hello";
    var Δ := 1;
}

The new variable declaration starts with an attribute var, the bound value are placed after an assigning operator (NOTE: assigning and equivalent operators are not the same in ELENA). The value can be the result of the expression as well.

The declaration and assigning can be separated:

ELENA
public program()
{
    var myFirstVariable;         // declaring a new variable myFirstVariable
    myFirstVariable := "Hello";  // assigning a value
    var Δ := 1;                  // declaring and assigning in the same statement
    Δ := Δ + 1;
}

If a variable is declared without an assignment, its value is nil.

A variable type is optional in ELENA. If the type is not explicitly stated the variable is type-less (or more exactly its type is system'Object, the super class). It means the type is not checked and any value can be assigned.

ELENA
var v := "Hello";

v := 3;

In reality there are many situations when we do care about the variable types. So let's create a strong typed variable. The required type (a class name or alias) should be written before the variable name:

ELENA
var string v := "Hello";

where string is an alias of system'String class.

var attribute in this case is optional and we could simplify the code:

ELENA
string v := "Hello";

The variable type restricts the possible values of the variable. The biggest difference with statically-typed languages that the type-check happens in run-time (though the compiler will warn that no conversion routine was found if both variables are strong-typed). So for example the following code can be compiled but will generate a run-time error:

ELENA
public program()
{
    var o := 2;
    string v := o;
}

and the output is:

system'IntNumber : Method #cast[0] not found
Call stack:
system'Exception#class.new[1]:exceptions.l(96)
system'MethodNotFoundException#class.new[1]:exceptions.l(1)
system'MethodNotFoundException#class.new[2]:exceptions.l(190)
system'$private'entry.#invoke[0]:win32_app.l(42)
mytest'program.#invoke[0]:test.l(6)
system'$private'entry.#invoke[0]:win32_app.l(37)
system'#startUp:win32_app.l(55)

The value can be successfully converted if an appropriate conversion method is declared:

ELENA
var o := 2;
real r := o;

In this case system'IntNumber class supports a conversion to system'RealNumber type.

The variable type can be automatically deduced from its initializer using auto attribute:

ELENA
auto r := 1.2r;

the variable type, system'RealNumber (or real), is defined by an assigned value - a numeric literal - 1.2r

Control Flow

ELENA supports rich set of control flow constructs : branching, looping, exception handling. They are implemented as code-templates and can be easily extended. Branching and looping statements are controlled by boolean expressions. Though branching operator can deal with non-boolean result as well. Exceptions are used to gracefully deal with critical errors. Exceptions can contain a stack trace to identify the error source.

Boolean type

BoolValue (alias bool) is a boolean type that has one of two possible values : true and false. It is a typical result of comparison operations. A boolean value is required by control flow statements (though non-boolean result can be used as well, providing a conversion operation exists). It supports typical comparison and logical operations:

ELENA
console
    .printLine("true==false : ",true == false)
    .printLine("true!=false : ",true != false)
    .printLine("true and false : ",true && false)
    .printLine("true or false : ",true || false)
    .printLine("true xor false : ",true ^^ false);

The results are:

ELENA
true==false : false
true!=false : true
true and false : false
true or false : true
true xor false : true

A boolean inversion is implemented via Inverted property:

ELENA
console.printLine("not ",true,"=",true.Inverted)

and the result is:

ELENA
not true=false

A boolean value can be used directly for branching operation. The message if with two functions without arguments can be send to it. true value will execute the first function, false - the second one.

ELENA
import extensions;

public program()
{
    var n := 2;
    
    (n == 2).if({ console.printLine("true!") },{ console.printLine("false!") });
}

with the result:

ELENA
true!

iif message is supported as well: true value will return the first argument, false - the second one

ELENA
var n := 2;

console.printLine((n != 2).iif("true", "false") )

The output is as expected:

ELENA
false

Branching operator

ELENA supports built-in branching operator ?. It requires that a loperand is a boolean value. True-part argument follows the operator immediately. Optional false-part is introduced with an exclamation sign:

ELENA
import extensions;

public program()
{
    var n := 2;
    
    (n == 2) ? { console.printLine("n==2") };
    (n == 3) ? { console.printLine("n==3") } ! { console.printLine("n!=3") }
}

The following result will be printed:

ELENA
n==2
n!=3

If right operands are not functions the operator returns true-part if the loperand is true and false-part otherwise.

ELENA
var n := 2;

console.printLine(n == 2 ? "n==2" : "n!=2");

with the similar result:

ELENA
n==2

Branching statements

ELENA does not reserve keywords for the control statements. But there are set of code templates that help make the code more readable. This list can be extended by programmers themselves.

Let's start with if-else, known for many.

ELENA
var n := 2;
if (n == 2)
{
    console.printLine("n==2")
}
else
{
    console.printLine("n!=2")
}

the result is

ELENA
n==2

if we need only true-part, we can skip the else block.

ELENA
if (n == 2)
{
    console.printLine("n==2")
}

Alternatively only else part can be written:

ELENA
ifnot (n == 2)
{
    console.printLine("n!=2")
}

If the code brackets contains only single statement we can write the statement directly - if it is the last block:

ELENA
if(n == 2)
   console.printLine("n==2");

Conditional typecasting

It is possible to use a special if-is statement to try to typecast the object

add(o)
{
    if (o; is int n) {
       ^ self.add(n);
    };

    console.printLine("unsupported")
}

The code in the curly brackets will be executed if the variable o can be converted to an integer and the new variable n contains the converted value.

Looping statements

ELENA provides several type of loop constructs : for, while, until, do-until, do-while.

FOR loops is used to execute the loop body for the specified iteration. It consists of initialization, condition and iteration expressions separated by semicolon and the main loop body:

ELENA
import extensions;

public program()
{
    for (var i := 0; i < 5; i++)
    {
        console.printLine(i)
    }
}

The first expression declares and initializes the loop variable, the second one is checked if the variable is inside the iteration range and the third one is increased the loop variable after the main loop body is executed.

The result will be:

ELENA
0
1
2
3
4

Similar if the loop body contains only single statement we can omit the brackets:

ELENA
import extensions;

public program()
{
    for (var i := 0; i < 5; i++)
        console.printLine(i)
}

If we need to execute an iteration step before the condition check we can skip the last expression. In this case the first expression will both initialize and iterate the loop variable on every step. The condition (second expression) will work similar, the loop continues until a false value is encountered. As a result the iteration step is guarantee to be executed at least once.

ELENA
import extensions;

public program()
{
    var sum := 0;
    for(var line := console.readLine(); line != emptyString)
    {
        sum += line.toInt()
    };
    
    console.printLine("The sum is ", sum)
}

In this example we read numbers from the console until an empty line is encountered, and print the sum. A method readLine reads the next line of characters from the console. emptyString is an empty string literal constant. An extension method toInt converts a string to an integer.

The output looks like this:

ELENA
1
3
5

The sum is 9

WHILE is a classical loop construct, it executes the code while the condition is true. Let's write the enumeration loop. Enumerators are special objects which enumerates collections, returning their members one after another. We will enumerate a string:

ELENA
import extensions;

public program()
{
    auto s := "♥♦♣♠";
    auto e := s.enumerator();
    while (e.next()) {
        console.printLine(e.get())
    }
}

The result will be:

♥
♦
♣
♠

The opposite construct is UNTIL. It repeats the code until the condition is true. Let's repeat the code while the number is not zero:

ELENA
import extensions;

public program()
{
    var n := 23;
    until(n == 0)
    {
       console.printLine(n);
     
       n /= 3
    }
}

The output will be:

23
7
2

It is clear that both loop constructs are interchangeable. The choice can depend on rather semantic meanings : to underscore one condition over another one.

If we need to execute the loop at least once whatever condition is, we have to use DO-WHILE or DO-UNTIL. Let's calculate the factorial of 5:

ELENA
import extensions;

public program()
{
    int counter := 5;
    int factorial := 1;

    do {
        factorial *= counter;
        counter -= 1
    }
    while(counter > 0);
    
    console.printLine(factorial)
}

Note that we have to put colon after while token

The result as expected:

ELENA
120

A special loop foreach can be used to easily goes through the enumeration values:

import extensions;
import algorithms;

public program()
{
   var array := new int[] { 2,3,4 };

   foreach(auto item; in array.quickSort()) {
      console.writeLine(item);
   }
}

?. and \. operators

ELENA supports the following template operators: doIfNotNil ( ?., similar to C# ) and tryOrReturn ( .\ ).

doIfNotNil sends a message only if left-operand is not nil.

tryOrReturn tries to send a message to left-operand and returns the result of the operation. If the object does not handle it - nil is returned.

ELENA
import system;
import extensions;

B
{
    test(string command)
    {
        console.printLine(command);
    }
}

C;

public program()
{
    var a := nil;
    var b := new B();
    var c := new C();
  
    a?.test("Fired a?.test");
    b?.test("Fired b?.test");
    
    a\.test("Fired a\.test");
    b\.test("Fired b\.test");
    c\.test("Fired c\.test");

    console.readLine();
}

The output is:

Fired b?.test
Fired a\.test

It can be used with a function call as well:

ELENA
public program()
{
    // declaring a nested function
    Func f1 := { console.writeLine("Fired 'f1'") };
    Func f2 := nil;
    
    f1?.();
    f2?.();
    
    console.readLine();
}

The output is:

Fired 'f1'

Symbols

Symbols are named expression which could be use to make the code more readable. A typical symbol is a constant declaration. Though they can more. Symbols are used to declare global singleton instances. The module initialization code is implemented via them. They can be private or public.

Values / Constants

A typical symbol use case is a value which can be reused further in the code:

ELENA
import extensions;
import extensions'math;

real PiOver2 = RealNumber.Pi / 2;

public program()
{
    console.printLine("sin(",PiOver2,")=", sin(PiOver2))
}

Here we declare a private strong-typed symbol PiOver2. The type can be omitted. In this case it will be a weak typed value. The result is:

ELENA
sin(1.570796326795)=1.0

If the symbol should be public (be accessible outside the module) public attribute should be put before the type and the name.

If the symbol value can be calculated in compile-time we can declare a constant symbol

ELENA
import extensions;
import extensions'math;

public const int N = 3;

public program()
{
    for (int i := 0; i < N; i++)
    {
        console.printLine(i,"^2=", sqr(i))
    }
}

The output is:

0^2=0
1^2=1
2^2=4

Static symbols

A normal symbols is evaluated every time it is used. A static symbol is a special case which is initialized only once by the first call (so called lazy loading) and its value is reused for every next call. It is a preferred way to implement a singleton (if it is not stateless).

ELENA
import extensions;
import extensions'math;

static inputedNumber = console.print("Enter the number:").readLine().toInt();

public program()
{
    console.printLine(inputedNumber,"^3 = ", power(inputedNumber, 3))
}

In this example the symbol is evaluated only once and the entered value is preserved, so the number should be entered only once:

ELENA
Enter the number:4
4^3 = 64

Preloaded symbols

A preloaded symbol is a special symbol which is automatically evaluated on the program start if namespace members are used in the program. So it can be used for module initialization.

ELENA
import extensions;

preloaded onModuleUse = console.printLine("Starting");

public program()
{
    console.printLine("Hello World")
}

The output is:

Starting
Hello World

Types

In programming a data type (or simply type) defines the role of data : a number, a text and so on. In object-oriented this concept was extended with a class which encapsulates the data and operations with them. The data is stored in fields and the operations are done via methods. In ELENA both a type and a class means mostly the same and can be used interchangeably without loss of meaning (except primitive types). Classes form a hierarchy based on inheritance. It means every class (except a super one - system'Object) has a parent and a single one.

A programming language can have static or dynamic type systems. In OOP classes can be made polymorphic fixing part of the problem with static types. Dynamic languages resolve types in run-time. This affects their performance. Modern programming languages as a result of it try to combine these approaches. In ELENA the types are dynamic and are resolved in run-time. In this sense you can write your program without explicitly specifying types at all. But in most cases the types should be specified to make your code more readable and to improve its performance.

In dynamic languages the operation to invoke a method is usually called sending a message. The class reacts to the message by calling appropriate method (with the same name and a signature). In normal case it is done via searching the appropriate handler in the method table. And it takes time. To deal with this ELENA allows to declare sealed or closed classes. Methods of sealed class can be resolved in compile-time and called directly. Methods of closed classes (or interfaces) can be resolved in compile-time via a method table. But the sealed class can not be inherited. Interfaces can be inherited but no new methods can be declared (except private ones).

In ELENA typecasting are implemented via a sending a special methods - conversion handlers. Every class can be converted into another one if it handles this message. Otherwise an exception is raised.

Primitive types

In most cases a class and a type mean the same. The only exception is a primitive type. Primitive types are built-in and support only predefined operations. They are classical data types in sense that they are pure data. The following types are supported by ELENA:

Type Size Description
__float 8 A 64-bit floating-point number
__int 1 A 8-bit integer number
__int 2 A 16-bit integer number
__int 4 A 32-bit integer number
__int 8 A 64-bit integer number
__raw   a raw data
__ptr 4 a 32-bit pointer
__mssg 4 a message reference
__mssg 8 an extension reference
__subj 4 a message name reference
__symbol 4 a symbol reference
__string   an array

Primitive types can be used in the program with a help of appropriate wrappers. Every time a primitive type is used in the code (except primitive operations) it is boxed into its wrapper. After the operation it is unboxed back. Due to performance issues no validation is applied to the primitive operations, so it is up to a programmer (or a wrapper class) to handle it correctly.

Classes

Putting aside primitive types every program object is an instance of a class. A class is a structure encapsulating data (fields) with operations with them (methods). A class can specify constructors to create an object and conversion routines to convert it from another object.

Declaring and using classes is straightforward for anyone familiar with C-like object-oriented languages:

ELENA
import extensions;

// declaring a class named A
class A
{
    // declaring a field named a
    field a;
    
    // declaring a default constructor
    constructor()
    {
        a := "Some value"
    }
    
    // declaring a method named printMe
    method printMe()
    {
        console.printLine(a)
    }
}

public program()
{
    // creating an instance of A class
    var o := new A();
    
    // calling a method 
    o.printMe()
}

The output is:

Some value

The keyword class specifies a normal class. A class body is enclosed in curly brackets. A class name declared right before the class body. A field can be declared in any place inside the class body. It can starts with an attribute field. Implicit constructor is named constructor. A method can be declared with a keyword method. The code could be placed inside curly brackets.

The classes form an hierarchy. Every one (except the super one) has a parent. If the parent is not specified the class inherits system'Object (a super class). A child class can override any non-sealed method of its parent. An instance of a child class is simultaneously of a parent type (so called is-a relation). So we could assign a child to a variable of a parent type. But the overridden methods will be used (polymorphic code). In case of weak types (or type-less) this is true as well (we only assume that all variables are of the super type).

ELENA
import  extensions;

class Parent
{
    field f;
    
    constructor()
    {
        f := "some value"
    }
    
    printMe()
    {
        console.printLine("parent:",f)
    }
}

class Child : Parent
{
    printMe()
    {
        console.printLine("child:",f)
    }
}

public program()
{
    Parent p := new Parent();
    Child c := new Child();
    
    p.printMe();
    c.printMe()
}

The output will be:

parent:some value
child:some value

The parent clause should follow the class name and be introduced with a colon. To override the method we have only declare it once again with the same name and signature.

In ELENA a type (or a class) can be used directly in the code like any other symbols. The only difference that we cannot invoke the default constructor. The named constructors should be used instead. NOTE : the default constructor will be called automatically.

ELENA
import extensions;

// declaring a class named A 
class A
{
    field a;
    
    // default constructor
    constructor()
    {
        a := "a"
    }
    
    // explicit constructor named new
    constructor new()
    {
        // default constructor is called automatically
    }
}

// declaring a class named B
class B
{
    field b;
    
    // default constructor
    constructor()
    {
        b := 2
    }
    
    // explicit constructor named new
    constructor new()
    {
        // default constructor is called automatically
    }
}

// factory function
factory(class)
    // sending a message - name and returning a result of the operation 
    = class.new();

public program()
{
    // creating objects using a class directly
    var a := factory(A);
    var b := factory(B);
    
    // printing the object types
    console
        .printLine(a)
        .printLine(b);
}

The output is:

mylib'$private'A
mylib'$private'B

By default a class is declared private. It means it cannot be accessed outside its namespace. In the case of a library we would like to reuse it. So we have to provide a public attribute:

ELENA
public class B
{
}

Now the class can be accessed either outside the library or by a reference:

ELENA
public program()
{
    var b := new mylib'B()
}

A reference (or a full name) consists of a namespace (which in turn can contain sub-namespaces separated by apostrophes) and a proper name separated by an apostrophe.

Abstract classes

An abstract class is a special class used as a basis for creating specific classes that conform to its protocol, or the set of operations it supports. Abstract classes are not instantiated directly. It differs from an interface that the children can extend its functionality (declaring new methods). The abstract class can contain both abstract and normal methods. An abstract method must have an empty body (semicolon)

ELENA
import extensions;

abstract class Bike
{  
    abstract run(); 
}
  
class Honda4 : Bike
{  
    // overriding an abstract method
    run()
    {
        console.printLine("running safely")
    }
}    

public program()
{
    Bike obj := new Honda4();
    obj.run()
}        

The output will be:

running safely

Classes based on an abstract class must implement all the parent abstract methods. If a child class adds a new abstract methods or implements only some of the parent abstract methods, it should be declared abstract as well.

ELENA
abstract class BikeWithTrolley : Bike
{
    abstract pack();
}

Interfaces

An interface is a special case of an abstract class. For performance reason a new method cannot be declared for the interface children (except private ones). As a result an interface method can be called semi-directly (via a method table). An interface methods can be both abstract and normal ones (like in abstract classes).

ELENA
interface IObserver
{
    // declaring an interface abstract method
    abstract notify();
}

Though we could directly inherit the interface:

ELENA
class Oberver : IObserver
{
    notify() 
    {
        console.printLine("I'm notified")
    }
}

it is unpractical, because we cannot extends the class functionality. Instead we can use a template named interface:

ELENA
import extensions;

// declaring an interface
interface IObserver
{
    // declaring an interface method to be implemented
    abstract notify();
}

// creating a class supporting an interface
class MyClass : interface<IObserver>
{
    // implementing an interface method
    notify() 
    {
        console.printLine("I'm notified")
    }
    
    // implementing some other functionality
    doSomework()
    {
        console.printLine("I'm doing some work")
    }
}

// simple routine to invoke the interface
sendNotification(IObserver observer)
{
    observer.notify()
}

public program()
{
    // creating an intance of MyClass
    auto myObject := new MyClass();
    
    // do some work
    myObject.doSomework();
    
    // passing the interface implementation to the function by 
    // explicit typecasting the object
    sendNotification(myObject :as IObserver);
}        

The output is:

I'm doing some work
I'm notified

The class can implement several interfaces (so we can by-pass a problem with a single inheritance).

ELENA
import extensions;

// declaring an interface
interface IObserver
{
    // declaring an interface method to be implemented
    abstract notify();
}

// declaring the second interface
interface IWork
{
    abstract doSomework();
}

// creating a class supporting both interfaces
class MyClass : interface<IObserver>, interface<IWork>
{
    // implementing an interface method
    notify() 
    {
        console.printLine("I'm notified")
    }
    
    // implementing the second interface
    doSomework()
    {
        console.printLine("I'm doing some work")
    }
}

// simple routine to invoke the interface
sendNotification(IObserver observer)
{
    observer.notify()
}

public program()
{
    // creating an intance of MyClass 
    auto myObject := new MyClass();
    
    // implicitly typecast it to the second interface
    IWork work := myObject;
    
    // use the second interface
    work.doSomework();
    
    // passing the interface implementation to the function by 
    // explicit typecasting the object
    sendNotification(myObject :as IObserver);    
}

with the same result:

I'm doing some work
I'm notified

Singletons

Singletons are special classes which cannot be initialized (constructors are not allowed) and only a single instance exists. Singletons are mostly stateless (though they can have static fields). They are always sealed.

The singleton class is declared with singleton attribute. It can be referred directly using a class reference:

ELENA
import extensions;

// declaring a singleton
public singleton StringHelpers
{
    // Gets the first character of a string.
    char first(string str)
    {
        ^ str[0]
    }
}

public program()
{
    var str := "My string";
    
    // calling a singleton method
    console.printLine("Calling StringHelpers.First(""",str,""")=", 
         StringHelpers.first(str))
}

The output will be:

Calling StringHelpers.First("My string")=M

Structs

Structs are special kind of classes which are stored in-place, rather than by reference in the memory heap. Structs are sealed (meaning they cannot be inherited). They hold mostly small data values (such as numbers, handlers, pointers). All primitive data handlers are structs.

Struct fields can be either primitive or another structures. No reference types are allowed. All fields should be strong typed for that reason.

In most cases the use of structs is quite straightforward.

ELENA
import extensions;

// declaring a struct
struct Record
{
   // declaring struct fields
   int x;
   int y;
   
   // declaring struct constructor
   constructor(int x, int y)
   {
       // using this prefix to distinguish a class member from the local one
       this x := x;
       this y := y
   }
   
   printMe()
   {
       console.printLine("Record(",x,",",y,")");
   }
}    

public program()
{
    // creating a struct
    auto r := new Record(2, 4);
    
    // invoking struct method
    r.printMe()
}

The result will be:

Record(2,4)

Note that structs are stored in-place. It means that in our example the object was declared in the method stack. Every time it is used as a weak type, it will be boxed and unboxed after the operation. To improve performance we can declare a struct to be a constant one to avoid unboxing operation. For example all numbers are constant structs:

ELENA
// declaring a constant struct
public const struct IntNumber : IntBaseNumber
{
    // a field is a primitive 32-bit integer type   
    embeddable __int theValue[4];
...

Strings

Strings are special type of classes (both structs and nonstructural classes) which is used to contain the arrays. As a result the class length is variable. No default constructors (without parameters) are allowed.

ELENA
public const struct String : BaseValue
{
    __string byte[] theArray;

    constructor allocate(int size)
        = new byte[](size + 1);

It can contain only a single field marked as __string one.

Extensions

Extensions are special stateless classes used to declare extension methods. Extension methods allow you to extend the original class functionality without creating a new derived type, recompiling, or otherwise modifying the original type. Every method declared in an extension class is an extension method.

In normal cases extension classes is never used directly (except in mixins). To be used in other modules they should be declared as a public one. To start to use the extension it is enough to declare it in the same namespace as the code where it will be used. If the extension is declared in another module, the module should be included into the code (using import statement).

ELENA
import extensions;

// declaring an extension
public extension MyExtension
{
    // every member of the extension is an extension method
    printMe()
    {
        console.printLine("I'm printing ", self);
    }
}

public program()
{
    // invoking an extension method for various types
    2.printMe();
    "abc".printMe();
    2.3r.printMe()
}

The result is:

I'm printing 2
I'm printing abc
I'm printing 2.3

Extensions methods are used in ELENA in many places. For example print and printLine are extension variadic methods. If the class already have the method with the same name it will be used instead of extension one. For example if we extend our previous example with a new class containing the method printMe

ELENA
MyClass
{
    printMe()
    {
        console.printLine("I'm printing myself");
    }
}

public program()
{
    auto o := new MyClass();
    o.printMe();
}

The correct method will be invoked:

ELENA
I'm printing myself

But if the object is of weak type, the extension will be called:

ELENA
public program()
{
    var o := new MyClass();
    o.printMe();
}

The output will be:

I'm printing mytest'$private'MyClass

So it is a good practice not to mix the extension and normal method names.

Extensions can be weak one, meaning that they can extends any object (or instances of system'Object class). But we could always specify the exact extension target:

ELENA
// declaring an extension of MyClass
public extension MyStrongExtension : MyClass
{
    printMe()
    {
        console.printLine("I'm printing MyClass");
    }
}

MyClass
{
}

public program()
{
    auto o := new MyClass();
    o.printMe();
}

The output will be:

ELENA
I'm printing MyClass

It is possible to have several extension method with the same name as long as the extension targets are not the same.

The extension can be resolved both in compile and run time. The compiler tries to resolve all extensions directly. But if there are several similar extensions it will generate a run-time dispatcher.

ELENA
// declaring classes
A;
B;

// declaring several strong-typed extensions with the same name
extension AOp : A
{
    // extending an instance of A 
    whoAmI()
    {
        console.printLine("I'm instance of A")
    }
}

extension BOp : B
{
    // extending an instance of B 
    whoAmI()
    {
        console.printLine("I'm instance of B")
    }
}

public program()
{
    // declaring weak-typed variables
    var a := new A();
    var b := new B();

    // resolving an extension in compile-time is not possible
    // so the run-time dispatcher will be used
    a.whoAmI();
    b.whoAmI();
}

The output will be:

I'm instance of A
I'm instance of B

Sealed / Closed Classes

Sealed and closed attributes are used to improve the performance of the operations with the classes. A sealed class cannot be inherited. As a result the compiler can generate direct method calls for this class (of course when the type is known). All structs and singletons are sealed by default. Declaring a sealed class is quite simple:

ELENA
sealed class MySealedClass
{
}

Closed classes can be inherited but no new methods (private ones or new fields are allowed). All interfaces are closed. When the type is closed, the compiler can use a method table to resolve the method call.

ELENA
closed class MyBaseClass
{
    // declaring a "virtual" method
    myMethod() {}
}

class MyChileClass : MyBaseClass
{
    // overriding a method
    myMethod() {}
}

Basic Type Conversions

Let's learn how-to convert basic data types (int, long, real, string) between one other. We will use the extension methods declared in extensions module.

Let's convert the basic type numbers to the string.

ELENA
import extensions;

public program()
{
    int n := 20;
    real r := 3.4r;
    long l := 5000000000000l;
    
    console.writeLine("n=" + n.toString());
    console.writeLine("r=" + r.toString());
    console.writeLine("l=" + l.toString());
}

The output is:

n=20
r=3.4
l=5000000000000

We may use a different radix for integer numbers:

ELENA
import extensions;
                                                  
public program()
{
    int n := 20;
    long l := 5000000000000l;

    console.writeLine("n=0o" + n.toString(8));
    console.writeLine("l=0x" + l.toString(16));
}

The output is:

n=0o24
l=0x48C27395000

The reverse is possible too - using toInt / toLong / toReal extension methods:

ELENA
import extensions;

public program()
{
    int n := "20".toInt();                 // = 20
    long l := "5000000000000".toLong();    // = 5000000000000l
    real r := "2.3".toReal();              // = 2.3r
}

The custom radix can be used as well:

ELENA
import extensions;

public program()
{
    int n := "24".toInt(8);              // = 20
    long l := "48C27395000".toLong(16);  // = 5000000000000l
}

Operations between numeric types are possible as well (provided they are compatible):

ELENA
import extensions;

public program()
{
    int n := 200;
    
    long l := n.toLong();    // = 200l
    real r1 := n.toReal();   // = 200.0r
    real r2 := l.toReal();   // = 200.0r
}

We may skip the explicit conversion:

ELENA
import extensions;                                     :

public program()
{
    int n := 200;
    
    long l := n;       // = 200l
    real r1 := n;      // = 200.0r
    real r2 := l;      // = 200.0r
}

Conversions from the real number to the integer are possible provided the real number has no fraction part:

ELENA
import extensions;

public program()
{
    real r := 200.0r;
    
    long l := r.toLong();     // = 200l
    int n := r.toInt();       // = 200
}

We may ignore the fraction part by using Integer extension method:

ELENA
import extensions;

public program()
{
    real r := 200.2r;
    
    real r2 := r.Integer;         // = 200.0r
    long l := r.Integer.toLong(); // = 200l
    int n := r.Integer.toInt();   // = 200
}

Exception Handling

Exception handling is designed to deal with unexpected situations during the program executing. For dynamic language the typical unexpected situation is sending a message to the object which does not handle it (no appropriate method is declared). In this case MethodNotFound exception is raised. Another notable examples are : dividing by zero, nil reference and out of memory exceptions. The language provides several code templates to deal with such situations.

Typical exception in ELENA inherits system'Exception base class. A method named raise() is declared and used to raise an exception in the code. There are several exceptions in system library : OutOfRangeException, InvalidArgumentException, InvalidOperationException, MethodNotFoundException, NotSupportedException, AbortException, CriticalException and so on. A typical exception contains the error message and a call stack referring to the moment when the exception was created.

Raising an exception in ELENA is straightforward.

ELENA
divide(l,r)
{
    if(r == 0)
    {
        InvalidArgumentException.raise()
    }
    else
    {
        ^ l / r
    }
}

To handle it we have to write TRY-CATCH or TRY-FINALLY constructs.

ELENA
public program()
{
    console.print("Enter the first number:");
    var l := console.readLine().toInt();
    
    console.print("Enter the second number:");
    var r := console.readLine().toInt();

    try
    {
        console.printLine("The result is ", divide(l,r))
    }
    catch(InvalidArgumentException e)
    {
        console.printLine("The second argument cannot be a zero")
    };
}

The output will be:

Enter the first number:2
Enter the second number:0
The second argument cannot be a zero

An anonymous function declared after catch token is invoked if the raised exception matches the function argument list. If we want to handle any standard exception we can use a base exception class:

ELENA
catch(Exception e)
{
    console.printLine("An operation error")
};

Several exception handlers can be declared inside the nested class:

ELENA
catch::
{
    function(InvalidArgumentException e)
    {
       console.printLine("The second argument cannot be a zero")
    }
    
    function(Exception e)
    {
        console.printLine("An operation error")
    }
}

In our case both handlers are matched to InvalidArgumentException exception (because InvalidArgumentException is a child of Exception) but the proper exception handler will be raised because it is described early.

If we need to execute the code whenever an exception is raised or the code works correctly we have to use TRY-FINALLY-CATCH construct. The typical use-case is a resource freeing.

ELENA
import extensions;

public program()
{
    console.printLine("Try");
    try
    {
        var o := new Object();
        o.fail();
    }
    finally
    {
        console.printLine("Finally");
    }
    catch(Exception e)
    {
        console.printLine("Error!");
    }
}

The output will be:

Try
Finally
Error!

If the second line is commented out the output will be:

ELENA
Try
Finally

If we want to execute the code after the operation whenever an exception is raised, we can skip catch part:

ELENA
console.printLine("Try");
try
{
    var o := new Object();
    o.fail();
}
finally
{
    console.printLine("Finally");
}

And the result is:

Try
Finally
system'Object : Method fail[0] not found
Call stack:
system'Exception#class.new[1]:exceptions.l(96)
system'MethodNotFoundException#class.new[1]:exceptions.l(1)
system'MethodNotFoundException#class.new[2]:exceptions.l(190)
mytest'program.#invoke[0]:test.l(14)
mytest'program.#invoke[0]:test.l(9)
system'$private'entry.#invoke[0]:win32_app.l(37)
system'#startUp:win32_app.l(55)

Using Statement

Using pattern can be used if we want to garantee that the object will always be correctly closed:

ELENA
import extensions;

class A
{
    constructor()
    {
        console.printLine("opening");
    }
    
    do()
    {
        console.printLine("doing");
    }
    
    close()
    {
        console.printLine("closing");
    }
}

public program()
{
    using(var a := new A())
    {
        a.do();
    }
}

The result is:

opening
doing
closing

Operations With Files

Let's read a text file line by line:

ELENA
import system'io;
import extensions;

public program()    
{
    // getting a file name from the command line argument
    var fileName := program_arguments[1];

    // opening a text file reader
    using(auto reader := File.assign(fileName).textreader())
    {
        // repeating until all the lines are read 
        while (reader.Available)
        {
            // read the line
            string line := reader.readLine();
            
            // print the line
            console.printLine(line);            
        };
    }    
}

Let's write into the file:

ELENA
import system'io;
import extensions;

public program()    
{
    // getting a file name from the command line argument
    var fileName := program_arguments[1];

    // opening a text file writer
    using(auto writer := File.assign(fileName).textwriter())
    {
        // repeating until the empty line is entered
        for(string line := console.readLine(), line != emptyString, line := console.readLine())
        {
            // save to the file 
            writer.writeLine(line)
        }
    }    
}

If the file with the specified name exists, it will be overwritten.

If we want to append to the existing file, then we should use logger() method, e.g.:

ELENA
using(auto writer := File.assign(fileName).logger())

Let's read a binary file:

ELENA
import system'io;
import extensions;
import system'collections;

public program()    
{
    // getting a file name from the command line argument
    var fileName := program_arguments[1];

    // creating a list to hold the read data
    List<byte> dump := new List<byte>();

    // opening a binary file reader
    using(auto reader := BinaryFileReader.new(fileName))
    {
        // repeating until all the file is read 
        while (reader.Available)
        {
            // read the current byte into a variable b
            // note the variable is declared just-in-place
            reader.read(ref byte b);
            // add the read byte to the dump 
            dump.append(b);
        };
    };
    
    // print the file content as a list of bytes
    console.printLine(dump.asEnumerable());
}

And finally, let's search for the files and directories:

ELENA
import system'io;
import extensions;
   
public program()
{
    // specifying the target directory
    var dir := Directory.assign(".");
    
    var files := dir.getFiles();
    var dirs := dir.getDirectories();

    console.printLine("Directory ", dir);
    console.printLine("files:", files.asEnumerable());
    console.printLine("directories:", dirs.asEnumerable());
}

Lazy Expressions

A lazy expression is an expression which is evaluated only when it is accessed.

ELENA
import extensions;

public program()
{
    var expr := (lazy:console.write("Reading a number:").readLine().toInt());
    
    // the expression is invoked only when it is used
    var sum := expr + expr;
    
    console.printLine("the result is ", sum);
}

The output is:

Reading a number:2
Reading a number:3
the result is 5

Working With Enumerable Objects

ELENA supports several different types of enumerable objects: arrays, lists, dictionaries, ranges and so on.

To be used as enumerable one, object should handle enumerator message.

Let's consider a simple example:

ELENA
import extensions;

public program()
{
    var list := new{1,2.3r,"String",3l};
    
    var it := list.enumerator();
    while (it.next())
    {
        console.printLine(it.get())
    }
}

In the output, every member of collection will be printed:

1
2.3
String
3

In this code, we declare a static array - list. We send enumerator message to it and assign the returned enumerator to the variable it. Then we repeat the code until next message returns false value. get message returns a current enumeration member.

The same pattern can be applied for any enumerable object:

ELENA
import extensions;
import system'routines;

public program()
{
    var range := new Range(1, 9);
    
    var it := range.enumerator();
    while (it.next())
    {
        console.printLine(it.get())
    }
}

Here, we generate and print a range of natural numbers from 1 till 10. The result is:

1
2
3
4
5
6
7
8
9

Similar to C#, there are a lot of extension methods which can be used for searching, counting, filtering and so on.

Let's start with a simple one - forEach - executes a code for each enumeration member. The code above can be rewritten using forEach extension method (declared in system'routines module) and a closure:

ELENA
import extensions;
import system'routines;

public program()
{
   new Range(1, 9).forEach:(item){ console.printLine(item) }
}

where (item){ <...> } is a general closure with a single parameter.

We may further simplify our code using existing closure extensions'routines'printingLn.

ELENA
import extensions;
import system'routines;
import extensions'routines;

public program()
{        
    new object[]{1,2.3r,"String",3l}.forEach:printingLn
}

In both cases, the output will be similar to our first two examples.

We may combine several extension methods in a row. For example, filterBy is used to filter an enumeration based on the parameter output. Only the members for which filter function returns true will be passed further. It may be used in combination with other extension methods like forEach.

ELENA
import system'routines;
import extensions'routines;
import system'math;

public program()
{
    new Range(1, 9).filterBy:(i => i.mod:2 == 0).forEach:printingLn
}

filterBy will return only even numbers. The output will be:

2
4
6
8

Note that (item => <...> ) is a lambda closure which is shortcut form of (item){ ^<...> }.

summarize extension is used to summarize all the members of the collection:

ELENA
import extensions;
import system'routines;
import extensions'routines;
import system'math;

public program()
{
    console.printLine(new Range(1, 9).filterBy:(i => i.mod:2 == 0).summarize())
}

The result will be a sum of first 10 even natural numbers:

20

Using toArray extension, we may save our enumeration as an array:

ELENA
import extensions;
import system'routines;
import extensions'routines;
import system'math;

public program()
{
    var evens := new Range(1, 9).filterBy:(i => i.mod:2 == 0).toArray();

    console.printLine("sum(",evens,")=", evens.summarize())
}

toArray will collect the enumeration member into the array. And the output is:

sum(2,4,6,8)=20

We may limit our output using top extension:

ELENA
import system'routines;
import extensions'routines;
import system'math;

public program()
{
    new Range(1,100).filterBy:(i => i.mod:2 == 0).top:10.forEach:printingLn
}

The result will be the first 10 even numbers:

2
4
6
8
10
12
14
16
18
20

We may combine several enumerable objects into a single collection using zip:by extension:

ELENA
import extensions;
import system'routines;
import extensions'routines;

symbol list = new string[]{"a","b","c","d"};

public program()
{
    list
       .zipBy(new Range(1, list.Length), (ch,i => i.toPrintable() + " - " + ch.toPrintable()))
       .forEach:printingLn
}

And the output is:

1 - a
2 - b
3 - c
4 - d

where (ch,i => <...>) is a lambda closure with two parameters.

selectBy extension can be used to generate a new collection based on the previous one. orderBy extension will sort a collection:

ELENA
import extensions;
import system'routines;
import extensions'routines;

public program()
{
    var list := new Range(1,5).selectBy::(n => randomGenerator.nextInt(100)).toArray();
    
    console.printLine("sort(",list,")=",list.orderBy::(p,n => p < n))
}

The result will be a randomly generated list of numbers, ordered in assenting order:

sort(50,94,40,78,93)=40,50,78,93,94

groupBy may be used to group the enumeration members into a sub collections:

ELENA
import extensions;
import system'routines;
import extensions'routines;

public program()
{
    var list := new Range(1,20).selectBy::(n => randomGenerator.nextInt(10)).toArray();
    
    list
        .groupBy::(x => x)
        .selectBy::(sub_list => 
                      sub_list.Key.toPrintable() + ":" 
                      + sub_list.countMembers().toPrintable() + " times")
        .orderBy(ifOrdered)
        .forEach(printingLn)
}

The code will count how many times a random number is encountered:

ELENA
0:2 times
1:4 times
2:6 times
3:2 times
4:2 times
5:4 times
6:10 times
7:6 times
8:2 times
9:2 times

Enumeration type

An enumeration type is a special case of the object which can have only enumerated values.

Though ELENA does not support this type directly (being a pure object-oriented, it supports only classes). But starting from ELENA 6.2.1 it will be possible to use a special postfix template : enum. 

The declaration is straight forward, we declare the type and enumerate the list of possible values in the brackets:

public const struct Color : enum<int>(Red = 1,Green = 2,Blue = 3);

It is very simple to use it in the code:

Color red := Color.Red;
Color green := Color.Green;

Conversion to the string can be done using **toPrintable** method:

console.printLine(red.toPrintable());
console.printLine(green.toPrintable());

The output will be:

Red
Green

It is possible to list all possible values using **enumerator** method or **foreach** operation:

foreach(Color c; in Color)
{
   console.writeLine(c);
}

The result will be:

Red
Green
Blue

Dynamic Dispatching, Generic Handlers and Mixins

Let's start with multi-methods. We may declare several methods with the same name but with different signatures. It is also possible to declare an explicit multi-method dispatcher.

ELENA
class MyClass
{
    // accepts the integer
    testMe(int n)
    {
        console.writeLine("It is a number")
    }

    // accepts the string
    testMe(string s)
    {
        console.writeLine("It is a string")
    }

    // default handler
    testMe(o)
    {
        console.writeLine("Unsupported parameter")
    }
}

public program()
{
    object o := new MyClass();

    o.testMe(2);
    o.testMe("s");
    o.testMe(3l)
}

The output is:

It is a number
It is a literal
Unsupported parameter

In some cases, opposite can be done as well, we may declare generic handlers which will accept any incoming messages:

ELENA
import extensions;
 
class Example
{
    generic()
    {
        // __received is an built-in variable containing the incoming message name
        console.printLine(__received," was invoked")
    }
 
    generic(x)
    {
        console.printLine(__received,"(",x,") was invoked")
    }
 
    generic(x,y)
    {
        console.printLine(__received,"(",x,",",y,") was invoked")
    }
}
 
public program()
{
    var o := new Example();
 
    o.foo();
    o.bar(1);
    o.someMethod(1,2)
}

Output:

ELENA
foo was invoked
bar(1) was invoked
someMethod(1,2) was invoked

We may declare a custom dispatcher which will redirect all unmapped incoming messages to another object effectively overriding it (some kind of dynamic mutation / code injection).

ELENA
import extensions;

class Extender
{
    object theObject;
    
    // the injected property
    prop object Foo;
    
    constructor extend(o)
    {
        theObject := o
    }
    
    // redirect Object.Printable method to foo one
    string toPrintable() => theObject;

    // custom dispatcher
    dispatch() => theObject;
}

public program()
{
    var o := 234;
  
    // adding a field
    o := Extender.extend(o);

    // setting a field value
    o.Foo := "bar";

    console.printLine(o,".foo=",o.Foo)
}

The output is:

234.foo=bar

The message may be dynamically dispatched.

ELENA
class MyClass
{
    eval()
    {
       console.writeLine("eval method")
    }                                                    

    state0()
    {
       console.writeLine("state0 method")
    }
}  

public program()
{
   var o := new MyClass();

   var mssg := mssgconst state0[1];  // a message literal

   mssg(o)                           // dynamically dispatching the message
}

The output is:

eval method
state0 method

Though ELENA does not support multiple inheritance, using custom dispatcher, we may simulate it:

ELENA
singleton CameraFeature
{
    cameraMsg
        = "camera";
}
 
class MobilePhone
{
    mobileMsg
        = "phone";
}
 
class CameraPhone : MobilePhone
{
    dispatch() => CameraFeature;
}
 
public program()
{
   var cp := new CameraPhone();
 
   console.writeLine(cp.cameraMsg);
   console.writeLine(cp.mobileMsg)
}

The output is:

camera
phone

Using ELENA code from external program

The language compiler supports two types of output : standalone and a VM client. The later loads a virtual machine into its memory and starts the program. But the virtual machine can be used without any client at all. It provides a special interface allowing to communicate with ELENA code directly. So it can be used to execute ELENA code from an external app.

Let's start with a simple ELENA code. We will declare a function which will print a message:

public printingOut(arg)
{
   console.writeLine("Hello from " + arg);
}

The code is quite straight-forward, the argument is combined with a literal string and printed in the console. The argument type is not provided for simplicity.

Because our code is not going to be used standalone, we have to create a library - embedded1.prj and compile it:

>elena-cli.exe embedded1.prj

The result of this compilation is a compiled library file - embedded1.nl. We can call its code from another language. We will do it with C# console application.

For this we will need a helper class as well:

C#
public class ELENAVMWrapper
{
    [DllImport(@"elenavm60.dll")]
    public static extern int ExecuteVMLA(string target, string arg, IntPtr output, int maxLength);

    [DllImport(@"elenavm60.dll")]
    public static extern int PrepareVMLA(string configName, string ns, string path, string exceptionHandler);

    public const string CONFIG_NAME = "vm_client";
    public const string EXCEPTION_HANDLER = "system'core_routines'critical_exception_handler";

    public static void Prepare(string ns, string path)
    {
        if (PrepareVMLA(CONFIG_NAME, ns, path, EXCEPTION_HANDLER) == -1)
            throw new Exception("The operation is failed");
    }

    public static void Execute(string target, string arg)
    {
        if (ExecuteVMLA(target, arg, , 0) == -1)
            throw new Exception("The operation is failed");
    }

    public static string ExecuteAndReturn(string target, string arg, int maxLength = 1024)
    {
        var ptr = Marshal.AllocHGlobal(maxLength);
        try
        {
            int copied = ExecuteVMLA(target, arg, ptr, maxLength);

            return Marshal.PtrToStringUTF8(ptr, copied);
        }
        finally
        {
            Marshal.FreeHGlobal(ptr);
        }
    }
}

In the main program, we write a simple code:

C#
try
{
    ELENAVMWrapper.Prepare("embedded1", ".");
    ELENAVMWrapper.Execute("embedded1'printingOut", "C#");
}
catch (Exception e)
{
    Console.WriteLine(e);
}

Let's compile and run it as well:

C#
ELENA VM 6.0.36 (C)2022-2024 by Aleksey Rakov
Initializing...
Hello from C#

 In the next example we will sort an array passed from outside ELENA program and return it back.

Our ELENA code will consists once again from the function with a single argument. But this time we will deserialize it from Json, sort the array and return as Json again:

import extensions'dynamic;
import algorithms;

public string sorting(string json)
{
   var array := json.fromJson();

   var sortedArr := array.quickSort();

   ^ sortedArr.toJson();
}

Now let's write C# code. To parse the data into Json we will use System.Text.Json:

using System;
using System.Text.Json;

Console.WriteLine("Calling ELENA library from C#, Sample 3");

try
{
    ELENAVMWrapper.Prepare("embedded3", ".");

    var array = new int[] { 1, 5, -2, 56, -23 };

    var json = JsonSerializer.Serialize(array);

    string result = ELENAVMWrapper.ExecuteAndReturn("embedded3'sorting", json);

    Console.WriteLine($"{json} >> {result}");
}
catch (Exception e)
{
    Console.WriteLine(e);
}

The output will be similar:

Calling ELENA library from C#, Sample 3
ELENA VM 6.0.36 (C)2022-2024 by Aleksey Rakov
Initializing...
[1,5,-2,56,-23] >> [-23,-2,1,5,56]

Grammar

ELENA uses LL(1) grammar. It is an analytical grammar, meaning that the role of the terminal token is defined by its position in the statement. As a result, the grammar lacks the keywords (instead user-defined attributes are used). For example, it is possible to write the code without attributes at all:

ELENA
class
{
   field;

   method(param)
   {
   }
}

where a class with a name class is declared. It has one field with a name field and one method - method[1].

But in most cases, it is required to provide additional info. So the token role is defined by its position.

ELENA
class class;

singleton Tester
{
    do(var class class)
    {       
    }
}

public program()
{
    var class class := new class();

    Tester.do(class);
}

where class is used as an attribute to declare a new class, as a identifier type and as an identifier.

More Examples

You can get more examples and learn more about ELENA language in Rosseta Code:

Any Questions?

Feel free to ask!

Who Uses ELENA Language and for What?

That is a question that has been on everyone's mind, when they see something about a new language .

Well, I use the language, and develop business programs in ELENA language.

The programs that I developed in ELENA control all payments received by automakers banks such as Ford, GM, (Mercedes) Fundo Estrela, (Citroën and Peugeot) PSA Bank, (Fiat, Chrysler and used cars by Fiat) FIDIS Bank, this means that if you live in Brazil and bought a car from one of these manufacturers, their payment has been processed by a program written in ELENA.

And I wrote some prograns to Bradesco Bank and HSBC bank to process and generate payment data.

Open Source Projects in Elena

History

  • 10-Jul-2024
    • Updated to be compatible with ELENA 6.2
    • Adding a new section to call the code from the external code
    • new template statements : if::is & foreach::in
  • 11-Feb-2024
    • Updated to be compatible with ELENA 6.0
  • 25-Sept-2022
    • Update article with current ELENA status
  • 28-Mar-2020
    • Updated to be compatible with ELENA 5.0
  • 27-Dec-2019
    • Article updated
  • 24-Jun-2019
    • New version - 4.1.0
    • Some article editing
  • 4-Apr-2019
    • Adding new topics: working with files, conversions, .? and .\ operators
  • 9-Jan-2019
    • Updated to be compatible with ELENA 4.0
  • 6-Aug-2018
    • Adding new topics: working with enumerable objects, mixins
  • 13-Jun-2018
    • Updated to be compatible with ELENA 3.4
    • Fixing spelling
  • 26-Jun-2017
    • Added community group
  • 19-Jun-2017
    • Added new version of compiler to download
  • 09-Jun-2017
    • Updated all the article to the new version of Elena language
    • Added the new release of Elena langauge to download
    • Added section "if-else template"
    • Added section "Open source projects in Elena"
  • 17-Jan-2017
    • Added link to the new Elena article in Code Project
  • 12-Dec-2016
    • Updated Elena compiler to the last version
  • 22-Sep-2016
    • Added link to rosseta code
  • 12-Sep-2016
    • Article created

License

This article, along with any associated source code and files, is licensed under The MIT License