Objects

In Evie everything is based on the consept of objects.

To construct an object, you need at least one defined word (these can be keywords, already defined object names or both) and a non defined word.

This non defined word is going to represent the name of that object.

Here's an example of what i mean:

                        
    type A  #Name of the object is 'A' and it's type is 'type'
    A B     #Name of the object is 'B' and it's type is 'A' 
    B ptr C #Name of the object is 'C' and it's type is 'B ptr'
                        
                    
When the object has been defined the inheritted words are removed from the source code list ans only the name remains there. The defined object is also pushed into the difinition list if the current scope.

Classes

Now that everything has been parsed into objects. The compiler looks for context so that it can start adding to the AST more complex structures.

These complex structures contain classes.

But how does the compiler tell apart classes from functions?

here is the syntax of both of them:

                        
    int A{
        ..
    }
    int B(){

    }
                        
                    
The above intails one class that inheriths int and is named 'A'. The other one is a function that return int and is named 'B'.

The compiler tells them from apart by knowing which one takes what amount of paranthesis and what type'd are those parenthesis.

Evie adds many new features that can be problem when trying to use Evie output with other languages.
You can disable these inserted features by giving the class a inheritant named: "plain"
                        
    plain type A{
        ..
    }

                        
                    
So, what horrible non-standard features are then applied if i DONT use the 'plain' keyword?
First of all, Evie re-arranges the members to size order, so that moving members around would be little bit more faster.
Second, Evie adds padding to the end of the class, so that objects in the cache are aligned to the same cache row.

Base types

In Evie there are no pre defined base types in the compiler source code. But don't fret, you can make your own base types.

The compiler gives us access to modify some atributes of a sertain class. These atributes are: - format Format can be integer or decimal. - size Size tell's the compiler what size is the class in guestion. The Size is expressed in bytes.

                        
    type integer{
        size = 4
        format = integer
    }
                        
                    
The current implementation of the base types are located in STD/Types.e
Note: All classes that only contain as member variables the 'size' and 'format' are categorized as Base Types. member functions will not affect this classification.

Functions imports

Because of how Evie typing works Evie will generate function prototypes like this:

                        
    
    (4 integer) A(1 integer, 4 decimal)

                        
                    
Note: You as the user don't have to write this kind of syntax.
When including a DLL, ASM, LIB, OBJ, etc... files that are not your usual txt files, instead with the power of symbol table we can extract the function prototype information.
When the symbol table has been analyzed we will extract the mangled symbols. From mangled names following '_Z' we will deploy C++ mangler, which will extract the following:
- Name
- Parameters
Note: Evie supports C++, C, Evie, Vivid symbol mangling and de-mangling.
Now that the information has been extracted from the mangled symbol, we need to re-assign the typings to the correct typings of Evie. And this is where the:
                        
    
    (4 integer) A(1 integer, 4 decimal)

                        
                    
Syntax was made, here only the most important information is forwarded to Evie post-processor. The first parenthesis represent the return type, although C++ mangling doesn't support return types, this is usually generated for those mangling types that have return types. The number represents the size in bytes and the word "integer" represents the format of the class type. The letter 'A' in this case is just the name of the function. The parenthesis following the name contains the parameters. At post-processor when all the classes and their sizes are known, we replace the function prototypes parameter and returning information that was stored as sizes, and formats with the corresponding class types.
                        

    int A(char, float)

                        
                    
Note: Only Base types are searched for suitable candidate to fill the '(size, format)' format. To see what defines a Base type see: Base types

Evie has an internal database where it has the corresponding information about the mangling identifiers for every mangling standard that it supports.

                        

    i -> 4, integer
    c -> 1, integer
    f -> 4, decimal
    P -> ptr
    ...

                        
                    

Function exports

When a function is exported it is mangled by default with the C++ standard.

If you want to give this function to some other language, you can give it the 'plain' keyword.
This keyword keeps the final label name of the function to be same as the declared name.

There are also other keywords and mangling options.
The mangling options are: 'evie', 'vivid'

What the 'evie' mangling does is, it adds the returniong information to the final label name.
This in turn gives us the ability to make function overloads that differ just at the returning information.

File include

File including is another advanced feature in Evie. The file include system can import functions from:
- Dll files
- Lib files
- Evie files
- Assembly files
- URL

The system that handles these file includes is called Docker. The Docker tell's the difference between those file types above by reading the magic number in the header of that file.

Normal and assembly files dont have any magic numbers, so for assembly files to work you need to put at the start of the assembly file this text:
"#analyze".
In the future, Docker is going to fix this need of "#analyze" at the start of the file by reading the file type from the file name, as well as the magic number in the header of that file.

Pre-Compiled Source handling (PCS)

PCS are Pre-Compiled Source files, which are whether imported to the Evie project by:

                        

    use "some_lib.dll"

                        
                    
PCS files which are imported in the Evie source files, alway get Liquefied. On the other hand PCS files, which are not mentioned in any Evie source (*.e) file are only Linked and NOT Liquefied! This means the user can specifically request Evie to either Link or Liquify the PCS file from where the specific symbol arises from.

Templates

At Parser time the template functions and template classes are stored as template users. If however the template class is defined during a object definition, the template class will be copied and this copied version will be overwritten it's all template usages by the new template type.

                        
    type A<T>{
        T ptr Member1 = Allocate<T>(123)
    }

    int main(){
        #at this X variable definition stage the template class has been copied,
        #and the copied version is going to be overwritten by the new template type.
        A<int> X.A()
        retrun X.Member1[0]
    }
                        
                    
But if there is a function that is not inside a template type class, this template function is going to be copied after the parsing sequense at post-prosessor. This because wemight not be able to tell what function to call if there are multiple function overloads.

Pointers

Pointers are yet another dynamic feature in Evie. The compiler wrapr and un-wraps the pointer layers automatically for you.

                        
        int Foo(int ptr X){
            #Here the pointer layer is automatically un-wrapped
            #and returned the value the pointer points to.
            return X
        }

        int ptr Bar(int X){
            #Here we load the address of the variable X and return the address.
            return X
        }
                        
                    
The compiler also supports wrapping and un-wrapping nested pointers.
                        
        int Foo(int X){
            #This wont work because the second layer of loading loads nothing.
            #int ptr ptr ptr Y = X

            #This will work.
            int ptr Y = X

            #This in turn will work because you can un-wrap the pointers infinitely.
            int Z = Y


        }
                        
                    
Every time you load an address of a certain variable, this will create a that is usually passed to the loader.

When the previous 'ptr' has caused a temporary value the next 'ptr' keyword can not operate.

You can of course also do this and it is valid:
                        
    int Foo(int X){
        int ptr A = X
        int ptr ptr B = A
        int ptr ptr ptr C = B
        int ptr ptr ptr ptr D = C

        #this will return the value of X
        return D
    }
                        
                    

Preprocessor

The preprocessor in Evie offers us with compile time variables.
Here is a list of the compile time variables:

                        
    char ptr SOURCE_FILE            //This gives the name of the source file that is compiled.

    char ptr DESTINATION_FILE       //This gives the name of the file that the output is going to be written to.

    char ptr OS                     //This tells to witch operating system the code will be compiled. (win, unix)

    char ptr HOST_OS                //This tells in what operating system the compiler is running. (win, unix)

    char ptr ARCHITECTURE           //This gives the architecture that is used. (x86, arm)

    char ptr FORMAT                 //This gives the format that the output file is formatted to. (exe, dll, lib)

    int BYTES_MODE                  //This gives the max architecture bytes size. (8, 4, 2, 1) (8 is same as 64 and 4 is same as 32, etc)

    int true                        //This gives the value of true. (1)

    int false                       //This gives the value of false. (0)
                        
                    
The preprocessor also brings some useful tools, like preprocessor if statements.
Here is an example:
                        
    if (OS == "win")                    //check is this code is compiled in windows.
    {                  
        if (ARCHITECTURE == "x86")      //chek if the architecture is x86.
        {
            if (BYTES_MODE == 8)        //check if the architecture size can reach 64 bits.
            {
                if (FORMAT == "exe")    //check to what format the compiled code will be written to.
                {
                    #This is the code that will be executed if the preprocessor conditions are met.
                }
            }
        }
    }
                        
                    

Function pointters

Function pointters are one of the dynamic features Evie has. One of the main reasons i wanted to create this document. You see, function pointters in Evie are not same as in for example. C++. First let's create some functions for our example.

                        
    int Sum(int x, int y) {
        return x + y 
    }

    float Sum(float x, float y) {
        return x + y
    }
                        
                    
We have to create a function pointters object:
                        
    func ptr A = Sum
                        
                    
You may realize by now that something doesn't seem right. And you are right, this is where Evie will differ from most programming languages. Byt how does this even work? Well it doesn't (: For this to work the assing operator to needs to find the right suitable function to get it's address loaded, this happends when you call the function pointter.
                        
    int Result = A(1, 2)
                        
                    
Think what just happened like this. The name 'Sum' represents all functions that are named 'Sum'. This 'Sum' is like a branch and the different function overloads are like leaves on that branch. By calling the function pointter we state what leave from the branch we want to call.
                        
    #this is where we tell function pointter A points into function branch 'Sum'. 
    #But no address is loaded yet.
    func ptr A = Sum  
    
    #Now we fetch the function from the branch we just set earlier.
    int Result = A(1, 2)
    
    #At this point we know that the function we load is the 'int Sum(int, int)'.
    #Now we go back to the load moment and set the right function overload.

                        
                    

Temporary value

A temporary value represents a value that has been loaded into a register.

Where can this temporary value be generated?
Here is an example:

                        
    func Foo(int x, int y){
        int a = x + y
    }
                        
                    
Here the 'x + y' are first added together this summed value is stored into a register. This register can act as a temporary value. As you already might know you cannot load a registers address, simply because they do not have a memory location that we can access.

And that is why you cannot load a temporary value in a scenario like this here:
                        
    func Foo1(int x){
        int ptr a = x
    }

    func Foo2(int x){
        int ptr ptr a = x
    }
                        
                    
In the function Foo1, the 'x' address is loaded into a register, and it is stored into the pointer 'a'.

But in Foo2, the address of 'x' is first loaded into a register, This register is going to be loaded as it would have a memory address, but it does not. And this creates a big problem when you try to load a variable address multiple times in a row.

Const meaning

Const is a internal feature of Evie, the workings of a consted variable is basically the same thing as a const in C++.
In other words, a consted variable is a variable that can not be changed after initialization.
Here is an example:

                        
    type int{
        size = 4
    }
                        
                    
In the example above the class int is created with a size of 4 bytes.
This size variable is an actual member inside the int class,
the difference is that the size variable cannot be changed after initializing the value of 4 to it.

Same applies to strings that point to a data section.
Here is an example:
                        
    char ptr Foo = "ABCDEFG"
                        
                    
In the example above the string is first made it's own label in the data section with it's data that is the "ABCDEFG" string.
After the string is made it's own label, the assing operator right side is a pointer to that label.

This pointer is usually named like this: 'S0', 'S1', 'S2' etc.
This pointer to the label is a const char ptr that cannot be changed.

Static meaning

Static is same as in C++. Where a static variable is declared once and it's value is the same for all instances of the class.

Namespaces are just classes that are static typed.

A static class gives all it's members the static keyword, making all of them static.
Here is an example:

                        
    static Foo{
        int x = 1
        int y = 2
    }
                        
                    
In the example above the Foo class is static. This makes the Foo class a namespace.
This means that members x and y are also inheriting static keyword.

Const char pointters

Const char pointters are a similiar feature to C++'s const char* pointers.

Const char pointters are used to point to a string in the data section. That is if the const char pointer is either longer than 4 characters or it's address is loaded.

Here is an example:

                        
    char ptr Foo = "ABCDEFG"    //Now the string is made it's own label in the data section, and its address is stored in the Foo pointer.

    char Bar = "a"              //Now the string can be compressed into a number with the type of a char.
    
    int Baz = "1234"->int       //Because the lenght of the string here is less than 5, the string is compressed into a number.
                        
                    
In the example above the char ptr Foo, loads the address of the "ABCDEFG" string into it.

The char Bar is a char that is compressed into a single byte number.

The int Baz is a int that is compressed into a single 4 byte integer number.