Click here to Skip to main content
14,545,020 members

Introduction to ELENA Programming Language

,
Rate this:
4.52 (56 votes)
Please Sign up or sign in to vote.
4.52 (56 votes)
28 Mar 2020MIT
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

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.

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:

  • recompile19.bat

or, to generate release files:

  • install\build_package.bat %cd%

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 conceps 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 differents 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.

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

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.

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:

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:

import system;

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

Second:

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

Like in Smalltalk, in ELENA, everything is a object, so, to create variables, we just need to use an attribute var.

The syntax of this keyword is, basically:

var <variable name> := <Class instance>;

So, as an example, let's write a simple program to read something from the keybord.

If you prefer, you can create a new console project for this example.

import extensions;

public program()
{
    var number := console.print("Type a number: ").readLine().toInt();
    console.printLine("You typed ", number);
    console.readChar()
 }

Really simple, right?
So, in this example, we need to import one more namespace, the namespace extensions contain the definition for the extension method "loadLineTo[2]".

A variable can contain any object type, for example:

var x := new IntNumber();
var y := 0;
var k := new system'calendar'Date();
var n := k.Year;
var s := "Hello World";
var u := 0fffffh;
var l := 3.1415r;
var m := new RealNumber(3.14r);
var j := true;
var h := false;

Just-in-place variable assignment is supported, as well as assigning several variables in a row:

import extensions;
    
singleton Tester
{
    printMe(s)
    {
        console.printLine(s)
    }
}

public program()
{
    var a;
    var b := a := 2;
    
    Tester.printMe(a);
    Tester.printMe(b);
    Tester.printMe(b := 3);
}

The output is:

2
2
3

It is possible to declare and pass by ref parameter directly in the expression:

import extensions;

singleton Tester
{
    printMe(s)
    {
        console.printLine(s)
    }
    
    assignMe(ref retVal, val)
    {
        retVal := val
    }
}

public program()
{
    Tester.assignMe(ref var i, 2);
    Tester.printMe(i);
}

The output will be:

2

Conditional Operator, If and Else

Branching statements in ELENA can be declared using a code template - if. Let's take a look:

var a1 := true;

if(a1 == true)
{
     console.printLine("a1 == true").
}
else
{
    console.printLine("a1 != true").
}

Let's implement an example program containing many conditions and variable statements.

import extensions;

public program()
{
  console.writeLine:"Hi!! :)";
  var yourName := console.writeLine("Whats your name ?").readLine();
  console.writeLine:"How old are you ?";
  var yourAge := console.loadLineTo(new Integer());
 
  var numberOfChildrens := 0;
  if(yourAge >= 0 && yourAge <= 10)
  {
    console.writeLine:"You're still very young!!"
  }
  else if(yourAge >= 11 && yourAge <= 20)
  {
    console.writeLine:"Adolescence, come a lot of people angry?"
  }
  else if(yourAge >= 21 && yourAge <= 30)
  {
    console.writeLine:"Do you already have children ? ( yes/no )";
    string haveChildren := console.readLine();
    if(haveChildren.lowerCase() == "yes")
    {
      numberOfChildrens := console.writeLine("Cool!! how many kids do you have ?")
                                     .loadLineTo(new Integer())
    }
  }
  else if(yourAge >= 31 && yourAge <= 40)
  {
    console.writeLine:"The first 40 years of childhood are the most difficult !!!"
  }
  else if(yourAge >= 41 && yourAge <= 50)
  {
    console.writeLine:"Life begins at 40!"
  }
  else if(yourAge >= 51 && yourAge <= 60)
  {
    console.writeLine:"Enjoy your LIFE !!!"
  }
  else if(yourAge >= 61)
  {
    console.writeLine:"You are at the age to enjoy and relax !!"
  };
  console.readChar()
}

Loops

Like in other programming languages, ELENA has a support for loops, let's take a look at the while loop, this loop is implemented using templates while and for.

while(<condition>)
{ /* ... loop code... */ };
for(<init>,<conditon>,<step>)
{ /* ... loop code... */ };

So, to make clear the usage of loop, let's write a simple program to:

import extensions; 

public program()
{
    var array := Array.allocate:200;
        
    var i := 0;
    while(i < array.Length)
    {
       array[i] := i * 10;
       i += 1
    };
      
    for(int i := 0, i < array.Length, i += 1)
    {
      console.printLine("At index ", i, " has the value:", array[i]);
    };
    
    console.readChar()
}

Note that for statement uses comma as a parameter separator unlike C.

.? 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.

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:

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

The output is:

Fired 'f1'

Classes and Methods and Fields

ELENA is an object-oriented language. To create a program, we have to declare new classes.

A class encapsulates data (fields) with code (methods) to access it. In most cases, it is not possible to get a direct access to the class content. Usually, the field refers to another class and so on until we reach "primitive" ones which content are considered as raw data (e.g., numeric or literal values).

Classes form the inheritance tree. There is the common super class - system'Object. ELENA does not support multiple inheritance, though it is possible to inherit the code using a dispatch handler (mixins / group objects) or interfaces. When the parent is not provided, the class inherits directly system'Object (the super class).

A class instance can be created with the help of the special methods - constructors. A constructor is used mostly to initialize the class fields. There are special types of classes which do not have constructors and can be used directly (nested classes, extensions, closures). A class itself is considered as a stateless object.

To understand this information, we will write some classes, to make clearer its implementation and use.

For the first example, we will create a simple class.

import system;
import extensions;

class MyFirstClass
{
    constructor()
    {
        console.printLine("MyFirstClass instancied!!! ehhhhhhh!!")
    }
}

public program()
{
    var myFirstClass := new MyFirstClass();
    console.readChar()
}

So, when you execute the program, you will see the message on console:

MyFirstClass instancied!!! ehhhhhhh!!

Really simple, right ? (:

One of the basic principles of object-oriented programming is inheritance between classes, so let's implement two classes, a base class and a derived class.

import system;
import extensions;

class MyFirstBaseClass
{
    string theField1;
    string theField2;
    
    constructor new()
    {
        theField1 := "";
        theField2 := ""
    }
    
    constructor new(string field1)
    {
        console.printLine("fieldBaseClass1 = ", field1);
        
        theField1 := field1;
        theField2 := "";
    }
    
    constructor new(string field1, string field2)
    {
        console.printLine("fieldBaseClass1 = ", field1);
        console.printLine("fieldBaseClass2 = ", field2);
        
        theField1 := field1;
        theField2 := field2
    }
    
    printFields()
    {
        console.printLine(self.Field1);
        console.printLine(self.Field2)
    }
    
    Field1 = theField1;  // property Field1
    Field2 = theField2;  // property Field2
}

class MyFirstDerivedClass : MyFirstBaseClass
{
    constructor new()
    {
        console.writeLine:"Constructor of MyFirstDerivedClass called with no parameters"
    }
    
    constructor new(string field1)
        <= new(field1 + " -- Called from DerivedClass")
    {
        console.printLine("Constructor of MyFirstDerivedClass called with 1 parameters");
        console.printLine("field1 = ", field1)
    }
    
    constructor(string field1, string field2)
        <= new(field1 + " -- Called from DerivedClass", 
               field2 + " -- Called from DerivedClass")
    {
        console.printLine("Constructor of MyFirstDerivedClass called with 2 parameters");
        console.printLine("field1 = ", field1);
        console.printLine("field2 = ", field2)
    }
}

public program()
{
    var myFirstDerivedClassInstance := nil;
    
    var numberOfArgsToInitInInstance := 0;
    var continueLoop := true;
    while(continueLoop)
    {
        numberOfArgsToInitInInstance :=
            console.printLine("
Enter with how much variables you want 
to use in instance init ? (0/1/2)") 
                   .readLine().toInt();
            
        if(numberOfArgsToInitInInstance >= 0 && numberOfArgsToInitInInstance <= 2)
           { continueLoop := false }
    };
    
    // This is a 'switch'...
    numberOfArgsToInitInInstance =>
        0 { myFirstDerivedClassInstance := new MyFirstDerivedClass() }
        1 { myFirstDerivedClassInstance := new MyFirstDerivedClass("Argument1 from program") }
        2 { myFirstDerivedClassInstance := new MyFirstDerivedClass("Argument1 from program", 
                                                                   "Argument2 from program") }
        : { console.writeLine:"invalid option..." };
          
    myFirstDerivedClassInstance.printFields();
    
    console.readChar()
}

It is possible to declare the private methods which cannot be called outside the class.

import extensions;

class MyClass
{
    private printPrivate()
    {
        console.printLine("private print.")
    }
    
    printPublic()
    {
        console.print("Calling from public print - ");

        // self is a reference to the current object
        self.printPrivate()
    }
}

public program()
{
    // Note that if the constructor explicitly is not declared 
    // the system'Object one (without input parameters) is inherited
    var myClass := new MyClass();
    
    myClass.printPublic();
    myClass.printPrivate()
}

In normal case, the class fields cannot be accessed outside the class. That's why we may declare a special method to access it:

import extensions;

class MyClass
{
    int _x;

    get int x() = _x;  // get accessor

    set x(int o)       // set accessor 
    {
       _x := o
    }
}

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

    myClass.x := 2;

    console.printLine("MyClass.x=", myClass.x)
}

We may simplify our code if we will use prop field template:

import extensions;

class MyClass
{
    prop int x;
}

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

    myClass.x := 2;

    console.printLine("MyClass.x=", myClass.x)
}

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.

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:

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:

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:

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):

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:

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:

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:

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

We may use try-catch statement to handle the possible exceptions:

import extensions;

public program()
{
    try
    {
        new Object().nonExistingMethod();
    }
    catch::
    {
        function(MethodNotFoundException e)
        {
           console.printLine("Method not found")
        }
        function(Exception e)
        {
           console.printLine("Unknown error")
        }
    }
}

Finally block can be used as well:

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 is:

Try
Finally
Error!

Using Statement

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

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:

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:

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.:

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

Let's read a binary file:

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:

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.

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:

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:

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:

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.

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.

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:

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:

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:

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:

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:

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:

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:

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

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.

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:

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:

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).

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.

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:

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

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:

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.

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

  • 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

Share

About the Authors

No Biography provided

Alex Rakov
Software Developer (Senior)
Russian Federation Russian Federation
No Biography provided

Comments and Discussions

 
QuestionWill compile but generate run time errors? Pin
Mr. Angular31-Mar-20 2:24
MemberMr. Angular31-Mar-20 2:24 
AnswerRe: Will compile but generate run time errors? Pin
Alex Rakov31-Mar-20 3:28
MemberAlex Rakov31-Mar-20 3:28 
GeneralRe: Will compile but generate run time errors? Pin
Mr. Angular31-Mar-20 3:50
MemberMr. Angular31-Mar-20 3:50 
QuestionWith all the C/CPP-like languages... Pin
rjmoses30-Aug-19 11:47
professionalrjmoses30-Aug-19 11:47 
AnswerRe: With all the C/CPP-like languages... Pin
Alex Rakov1-Sep-19 2:56
MemberAlex Rakov1-Sep-19 2:56 
GeneralRe: With all the C/CPP-like languages... Pin
rjmoses1-Sep-19 9:26
professionalrjmoses1-Sep-19 9:26 
GeneralRe: With all the C/CPP-like languages... Pin
Alex Rakov1-Sep-19 18:45
MemberAlex Rakov1-Sep-19 18:45 
QuestionMy vote of #5 Pin
BillWoodruff30-Aug-19 1:53
mveBillWoodruff30-Aug-19 1:53 
AnswerRe: My vote of #5 Pin
Alex Rakov30-Aug-19 2:28
MemberAlex Rakov30-Aug-19 2:28 
GeneralRe: My vote of #5 Pin
BillWoodruff30-Aug-19 5:04
mveBillWoodruff30-Aug-19 5:04 
QuestionCreado para mayor seguridad Pin
Agustín Cordero15-Feb-19 1:14
MemberAgustín Cordero15-Feb-19 1:14 
AnswerRe: Creado para mayor seguridad Pin
Alexandre Bencz25-Mar-19 8:08
MemberAlexandre Bencz25-Mar-19 8:08 
QuestionWith the opportunity to create a new language, why did you make it so C like? Pin
Member 1414149814-Feb-19 23:29
MemberMember 1414149814-Feb-19 23:29 
AnswerRe: With the opportunity to create a new language, why did you make it so C like? Pin
Alex Rakov15-Feb-19 1:28
MemberAlex Rakov15-Feb-19 1:28 
GeneralRe: With the opportunity to create a new language, why did you make it so C like? Pin
Member 1414149815-Feb-19 15:26
MemberMember 1414149815-Feb-19 15:26 
GeneralRe: With the opportunity to create a new language, why did you make it so C like? Pin
Tokinabo6-Apr-19 17:28
professionalTokinabo6-Apr-19 17:28 
GeneralRe: With the opportunity to create a new language, why did you make it so C like? Pin
Alex Rakov6-Apr-19 23:55
MemberAlex Rakov6-Apr-19 23:55 
GeneralRe: With the opportunity to create a new language, why did you make it so C like? Pin
Member 141414987-Apr-19 0:46
MemberMember 141414987-Apr-19 0:46 
GeneralRe: With the opportunity to create a new language, why did you make it so C like? Pin
Alex Rakov7-Apr-19 1:28
MemberAlex Rakov7-Apr-19 1:28 
GeneralRe: With the opportunity to create a new language, why did you make it so C like? Pin
Member 141414987-Apr-19 2:29
MemberMember 141414987-Apr-19 2:29 
GeneralRe: With the opportunity to create a new language, why did you make it so C like? Pin
Alex Rakov7-Apr-19 2:37
MemberAlex Rakov7-Apr-19 2:37 
GeneralRe: With the opportunity to create a new language, why did you make it so C like? Pin
Tokinabo7-Apr-19 8:50
professionalTokinabo7-Apr-19 8:50 
GeneralRe: With the opportunity to create a new language, why did you make it so C like? Pin
Alex Rakov7-Apr-19 9:08
MemberAlex Rakov7-Apr-19 9:08 
GeneralRe: With the opportunity to create a new language, why did you make it so C like? Pin
Tokinabo7-Apr-19 10:05
professionalTokinabo7-Apr-19 10:05 
SuggestionArticle is still written as if the language is just created Pin
City17Citizen14-Jan-19 0:05
professionalCity17Citizen14-Jan-19 0:05 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Article
Posted 12 Sep 2016

Stats

143.4K views
1.4K downloads
71 bookmarked