-
-
Notifications
You must be signed in to change notification settings - Fork 26
ELENA in a nutshell
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.
Here we will learn ELENA in details. Let's start!
To create a simple program in ELENA we have to create a source file (with an extension .l) and write the following code:
public program()
{
console.writeLine("Here my first program in ELENA!")
}
We may compile it without creating a project file (where elc is a command-line compiler):
elc program1.l
The output is:
ELENA Command-line compiler 5.2.404 (C)2005-2020 by Alex Rakov
Project : program1, Platform: STA Win32 Console
Cleaning up...
Compiling...
Parsing unnamed
Compiling unnamed
Successfully compiled
Linking...
Successfully linked
It will create program1.exe file which we can execute:
program1
with the following result:
Here my first program in ELENA!
Beside the executable file a module program1.nl was created. Let's look what inside. We could use Bycode Viewer:
ecv program1.nl
The output will be:
ELENA command line ByteCode Viewer 5.2.81 (C)2011-2020 by Alexei Rakov
program1.nl module loaded
>
Now let's look at the generated classes:
>?
class program
>
As you can see the program entry was compiled as a class. Let's look inside:
>program
@parent system'Object
@flag elSealed
@flag elStateless
@flag elRole
@method @function program.#invoke
>
Our function was compiled as a singleton with a single anonymous method #invoke. Let's dive more deep:
>program.1
@method program.#invoke
open 1h
pusha
pushr 0
pushr 0
callr system'console
storesi 0
movr const : "Here my first program in ELENA!"
storesi 1
peeksi 0
movm mssgconst : "writeLine<system'String>[2]"
callrm system'$private'Console mssgconst : "writeLine<system'String>[2]"
peekfi 1
close
quit
@end
>
What do we see? ELENA byte-codes are quite simple. Let's review some of them
opcode | Description |
---|---|
pusha | puts the object accumulator on the top of the function stack |
pushr r | puts a constant on the top of the function stack |
callr r | invokes a symbol |
storesi i | saves an object accumulator in the function stack at specified relative position |
movr r | assigns a constant to the object accumulator |
peeksi i | loads an object from the function stack at specified relative position to the object accumulator |
movm m | assigns a message constant to the data accumulator |
callrm r m | invokes a method m in r class |
Once again let's look at our code. Now it is quite clear. We put console symbol and a string literal constant into the function stack and directly invokes system'$private'Console.writeLine<system'String>[2] method.
Is our program executed at the executable start? Let's look into the linker code:
// create the image
ident_t entryName = project->resolveForward(SYSTEM_ENTRY);
_entryPoint = emptystr(entryName) ? LOADER_NOTLOADED : linker.resolve(entryName, mskSymbolRef, true);
if(_entryPoint == LOADER_NOTLOADED)
throw JITUnresolvedException(ReferenceInfo(SYSTEM_ENTRY));
where SYSTEM_ENTRY is defined as:
constexpr auto SYSTEM_ENTRY = "$system_entry"; // the system entry
So the answer is no. There is a special entry which executes initialization code and invokes our program. The system entry is a symbol. Can I redefine it? Yes of course. Let's look at our code once again: $system_entry is resolved as a forward. What is a forward? In ELENA the forward reference is a reference which is resolved in linking-time. It means that this information should be provided when we compile the project.
Where $system_entry is defined? To find it out we have to look into how the project settings are configured in ELENA. When we compile it once again we will see that the following info:
ELENA Command-line compiler 5.2.408 (C)2005-2020 by Alex Rakov
Project : program1, Platform: STA Win32 Console
STA Win32 Console is a project template which settings we are copying from. This template can be based on another one and so on. The available templates are listed in elc.cfg configuration file:
<templates>
<template key="console">templates\win32_console.cfg</template>
<template key="mta_console">templates\win32_consolex.cfg</template>
<template key="gui">templates\win32_gui.cfg</template>
<template key="mta_gui">templates\win32_guix.cfg</template>
<template key="vm_console">templates\vm_win32_console.cfg</template>
<template key="lib">templates\lib.cfg</template>
</templates>
So let's look into the forward section of win32_console.cfg :
<forwards>
<forward key="$program">system'startUp</forward>
<forward key="$system_entry">system'core_routines'sta_start</forward>
<forward key="program">$elena'@rootnamespace'program</forward>
<forward key="program'arguments">extensions'program_arguments</forward>
<forward key="program'output">system'console</forward>
<forward key="newLine">system'newLine</forward>
<forward key="onStart">system'onConsoleStart</forward>
</forwards>
Here we are : $system_entry* is resolved as system'core_routines'sta_start reference. Let's look what inside:
>ecv system'core_routines
ELENA command line ByteCode Viewer 5.2.81 (C)2011-2020 by Alexei Rakov
C:\Alex\ELENA\lib50\system.core_routines.nl module loaded
>#sta_start
>@symbol sta_start
pushs 0
callextr extern : PrepareEM
freei 1h
open 1h
reserve 5h
pushs -2
callextr $native'coreapi'initProgramHeader
pushr $forwards'$program
pushr $native'coreapi'seh_handler
pushr $native'coreapi'default_handler
alloci 1h
loadenv
savesi 0
movf -1
callextr extern : InitializeSTA
freei 4h
restore 7h
close
quit
@end
>
The relevant part of the code is invoking InitializeSTA function. This function initializes system environments and calls another forward symbol - $forwards'$program. Let's look once again into win32_console.cfg. $forwards'$program is defined as system'startUp. It is already manageable code. If it is redefined we will have our own system entry. Let's do it! We will provide a default exception handler to gracefully end the program in case of an error and wait until the user press any key
// system entry
public mySystemEntry = startUp();
// system boot code
startUp()
{
try
{
// invoking a program entry
program()
}
catch(Expression ex)
{
console.writeLine(ex.toPrintable())
};
// wait for any key
console.readChar()
}
// a program entry
public program()
{
console.writeLine("Here my first program in ELENA!")
}
Now we could compile the program:
elc program2.l -f$program=program2'mySystemEntry
When we execute the program it will wait until a user presses any key.
Let's look into the system entry code:
>ecv program2.nl
ELENA command line ByteCode Viewer 5.2.82 (C)2011-2020 by Alexei Rakov
program2.nl module loaded
>$private'startUp.1
@method $private'startUp.#invoke
open 1h
pusha
hook Lab00
movr const : 'program
movm mssgconst : "#invoke"
callrm 'program mssgconst : "#invoke"
unhook
jump Lab01
Lab00: nop
popa
flag
and 200000h
ifn Lab02 0h
load
peeksi 0
callvi 0
Lab02: nop
unhook
pushr 0
pushr 0
storesi 0
movr const : '$inline0
storesi 1
peeksi 0
movm mssgconst : "on[2]"
callvi 0
Lab01: nop
callr system'console
pusha
movm mssgconst : "readChar[1]"
callrm system'$private'Console mssgconst : "readChar[1]"
peekfi 1
close
quit
@end
The part, we are interested in, is the following:
movr const : 'program
movm mssgconst : "#invoke"
callrm 'program mssgconst : "#invoke"
As you see the reference is resolved directly. This is not practical if we would like to reuse the system entry in different projects.
This is a typical use-case for a forward. First we have to rewrite the code slightly - by adding a forward attribute to the program reference :
// ...
startUp()
{
try
{
// invoking a program entry
forward program()
// ...
When we compile the code the result will be the same as before. Let's look once again in startUp:
@method $private'startUp.#invoke
open 1h
pusha
hook Lab00
callr $forwards'program
movm mssgconst : "#invoke"
callvi 0
As you see the reference was changed. It is no longer the direct one. But I do not define the forward. Why there was no error? The trick is in win32_console.cfg forward section. Look:
<forwards>
<forward key="$program">system'startUp</forward>
<forward key="$system_entry">system'core_routines'sta_start</forward>
<forward key="program">$elena'@rootnamespace'program</forward>
First of all, program forward is already defined, it refers to $elena'@rootnamespace'program. Here comes the compiler magic - the namespace $elena'@rootnamespace is automatically replaced with the current project default namespace. In our case it is program2. Still we could easily redefine it. Let's implement alternative program:
public alt_program()
{
console.writeLine("Here my second program in ELENA!")
}
And compile the project as follow:
elc program2.l -f$program=program2'mySystemEntry -fprogram=program2'alt_program
After the successful compilation the program will generate the following output:
Here my second program in ELENA!
Here we are. As you see it is quite easy to declare and resolve forward references in any ELENA program!