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#:
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:
(def funcs (map #(fn [] (* % %)) (range 11)))
(printf "%d\n%d\n" ((nth funcs 3)) ((nth funcs 4)))
In Smalltalk:
funcs := (1 to: 10) collect: [ :i | [ i * i ] ] .
(funcs at: 3) value displayNl .
In 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:
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:
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:
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:
- "
system
": It is a namespace, the namespace itself may contain sub elements separated by apostrophes - "
console
": It is a class - "
writeLine
": It is a message - "
Hello World!
": It is the parameter of the message - "
;
": 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
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.
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:
public program()
{
var myFirstVariable;
myFirstVariable := "Hello";
var Δ := 1;
Δ := Δ + 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.
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:
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:
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:
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:
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:
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
A 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:
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:
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:
console.printLine("not ",true,"=",true.Inverted)
and the result is:
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.
import extensions;
public program()
{
var n := 2;
(n == 2).if({ console.printLine("true!") },{ console.printLine("false!") });
}
with the result:
true!
iif message is supported as well: true value will return the first argument, false - the second one
var n := 2;
console.printLine((n != 2).iif("true", "false") )
The output is as expected:
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:
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:
n==2
n!=3
If right operands are not functions the operator returns true-part if the loperand is true and false-part otherwise.
var n := 2;
console.printLine(n == 2 ? "n==2" : "n!=2");
with the similar result:
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.
var n := 2;
if (n == 2)
{
console.printLine("n==2")
}
else
{
console.printLine("n!=2")
}
the result is
n==2
if we need only true-part, we can skip the else block.
if (n == 2)
{
console.printLine("n==2")
}
Alternatively only else part can be written:
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:
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:
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:
0
1
2
3
4
Similar if the loop body contains only single statement we can omit the brackets:
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.
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:
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:
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:
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:
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:
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.
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()
{
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:
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:
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
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).
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:
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.
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:
import extensions;
class A
{
field a;
constructor()
{
a := "Some value"
}
method printMe()
{
console.printLine(a)
}
}
public program()
{
var o := new A();
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).
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.
import extensions;
class A
{
field a;
constructor()
{
a := "a"
}
constructor new()
{
}
}
class B
{
field b;
constructor()
{
b := 2
}
constructor new()
{
}
}
factory(class)
= class.new();
public program()
{
var a := factory(A);
var b := factory(B);
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:
public class B
{
}
Now the class can be accessed either outside the library or by a reference:
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)
import extensions;
abstract class Bike
{
abstract run();
}
class Honda4 : Bike
{
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.
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).
interface IObserver
{
abstract notify();
}
Though we could directly inherit the interface:
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:
import extensions;
interface IObserver
{
abstract notify();
}
class MyClass : interface<IObserver>
{
notify()
{
console.printLine("I'm notified")
}
doSomework()
{
console.printLine("I'm doing some work")
}
}
sendNotification(IObserver observer)
{
observer.notify()
}
public program()
{
auto myObject := new MyClass();
myObject.doSomework();
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).
import extensions;
interface IObserver
{
abstract notify();
}
interface IWork
{
abstract doSomework();
}
class MyClass : interface<IObserver>, interface<IWork>
{
notify()
{
console.printLine("I'm notified")
}
doSomework()
{
console.printLine("I'm doing some work")
}
}
sendNotification(IObserver observer)
{
observer.notify()
}
public program()
{
auto myObject := new MyClass();
IWork work := myObject;
work.doSomework();
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:
import extensions;
public singleton StringHelpers
{
char first(string str)
{
^ str[0]
}
}
public program()
{
var str := "My string";
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.
import extensions;
struct Record
{
int x;
int y;
constructor(int x, int y)
{
this x := x;
this y := y
}
printMe()
{
console.printLine("Record(",x,",",y,")");
}
}
public program()
{
auto r := new Record(2, 4);
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:
public const struct IntNumber : IntBaseNumber
{
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.
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).
import extensions;
public extension MyExtension
{
printMe()
{
console.printLine("I'm printing ", self);
}
}
public program()
{
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
MyClass
{
printMe()
{
console.printLine("I'm printing myself");
}
}
public program()
{
auto o := new MyClass();
o.printMe();
}
The correct method will be invoked:
I'm printing myself
But if the object is of weak type, the extension will be called:
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:
public extension MyStrongExtension : MyClass
{
printMe()
{
console.printLine("I'm printing MyClass");
}
}
MyClass
{
}
public program()
{
auto o := new MyClass();
o.printMe();
}
The output will be:
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.
A;
B;
extension AOp : A
{
whoAmI()
{
console.printLine("I'm instance of A")
}
}
extension BOp : B
{
whoAmI()
{
console.printLine("I'm instance of B")
}
}
public program()
{
var a := new A();
var b := new B();
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:
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.
closed class MyBaseClass
{
myMethod() {}
}
class MyChileClass : MyBaseClass
{
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
.
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();
long l := "5000000000000".toLong();
real r := "2.3".toReal();
}
The custom radix can be used as well:
import extensions;
public program()
{
int n := "24".toInt(8);
long l := "48C27395000".toLong(16);
}
Operations between numeric types are possible as well (provided they are compatible):
import extensions;
public program()
{
int n := 200;
long l := n.toLong();
real r1 := n.toReal();
real r2 := l.toReal();
}
We may skip the explicit conversion:
import extensions; :
public program()
{
int n := 200;
long l := n;
real r1 := n;
real r2 := l;
}
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();
int n := r.toInt();
}
We may ignore the fraction part by using Integer extension method:
import extensions;
public program()
{
real r := 200.2r;
real r2 := r.Integer;
long l := r.Integer.toLong();
int n := r.Integer.toInt();
}
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.
divide(l,r)
{
if(r == 0)
{
InvalidArgumentException.raise()
}
else
{
^ l / r
}
}
To handle it we have to write TRY-CATCH or TRY-FINALLY constructs.
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:
catch(Exception e)
{
console.printLine("An operation error")
};
Several exception handlers can be declared inside the nested class:
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.
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:
Try
Finally
If we want to execute the code after the operation whenever an exception is raised, we can skip catch part:
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:
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())
import system'io;
import extensions;
import system'collections;
public program()
{
var fileName := program_arguments[1];
List<byte> dump := new List<byte>();
using(auto reader := BinaryFileReader.new(fileName))
{
while (reader.Available)
{
reader.read(ref byte b);
dump.append(b);
};
};
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());
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
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.
class MyClass
{
testMe(int n)
{
console.writeLine("It is a number")
}
testMe(string s)
{
console.writeLine("It is a string")
}
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()
{
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;
prop object Foo;
constructor extend(o)
{
theObject := o
}
string toPrintable() => theObject;
dispatch() => theObject;
}
public program()
{
var o := 234;
o := Extender.extend(o);
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];
mssg(o)
}
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
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:
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:
try
{
ELENAVMWrapper.Prepare("embedded1", ".");
ELENAVMWrapper.Execute("embedded1'printingOut", "C#");
}
catch (Exception e)
{
Console.WriteLine(e);
}
Let's compile and run it as well:
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:
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
- 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
- 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
- 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