Syntactical Overview "Tokenizers, Parsing, and statements"Version: ep1x2
Expression Parser works by converting a sequence of characters into a better defined sequence of tokens, these tokens represent simple syntactical terms such as an identifier, keyword, or operator. If we were to run the the tokenizer of ep on this particular sequence:
Code:
auto a = 0xab + 25;
We'd get an output of 7 tokens, it would be something along the lines of this:
- block='auto', keyword=true
- block='a', identifier=true
- block='=', operator=true
- bloc='0xab', hexadecimal=true
- block='+', operator=true
- block='25', decimal=true
- block=';', operator=true
The above output would be parsed into a statement represenation of the following:
Code:
statement:
flag = var_decl_with_initalizer
lhs identifier = 'a'
lhs operator = '='
expression:
{ value( '171' ), value( '25' ), operator( '+' ) }
You're probably slightly confused by how much the expression has changed, that's because at this point the expression has been converted into Reverse Polish Notation.
Practical Overview "Using the language"Version: ep1x2
In the practiacl overview I will go over on how to use the language to it's best ability, I will also go over some limitations that the currently implemented Shunting-yard algorithm causes for multiple '->' operations. It's unfortunate but I've yet to figure out how to fix it, I wish I could though because it really annoys me having to create temporary variables just to store something that should be kept as an rvalue, sigh.
Defining methods
Currently, inside of a ep script you can only define global methods, basically methods you can always call no matter the circumstances; However, you can still call methods that correlate to a variable, as long as it has said method. This is what it looks like defining a method in ep:
Code:
method theoretical_introduction()
{
// Do something here
// Perhaps return something here.
};
Calling methods
Calling a method is simple, all you need to do is mix the identifier of the method and wrap it's arguments within parentheses, as any other language does it.
Code:
theoretical_introduction( );
Pretty simple, huh?
Defining variables
When defining a variable, there's a lot of stuff going on in the background. For example, you as the programmer doesn't see when or how they're uninitalized or erased from memory, mainly becuase you don't actually need to see that information, but that doesn't mean you don't need to know it exists. Variables can of course be defined in and outside of scopes, when you declare a variable inside of a scope it's a variable with local storage duration (It's alive untill the end of it's corresponding '}' block). However, when defining a variable in the global scope it's storage duration is for ever (For ever meaning as long as the program runs, said variable is alive).
Code:
// There are 3 different ways to define a variable
var a;
var a = 1;
auto a = 1;
As already noted, there are 3 different ways to define a variable, the third example being the same as the second example; However, the first example is only possible with the keyword var. That's because auto is used for automatic type deduction, it needs an initalizer to be able to initalize a with type info and a value.
Logical statements
Code:
if ( a )
{
[...]
}
else
{
[...]
}
As you can see in the above statement, we are checking if a is true, where true is any value above zero, if a is not true, it will enter the scope of it's corresponding else statement.
Code:
while( a )
{
[...]
}
The above statement is basically saying, as long as a is true, repeat this process, where the process is defined as whatever is inside of it's scope (brackets). It's like an if statement, except everything will be continuously repeated untill the deciding expression is no longer true.
Combining all of this, we can write a simple small script that goes from x to n, printing each number inbetween the two.
Code:
import bo; // < + == etc
import ios; // cout
auto x = 0;
auto n = 10;
while( x < n )
{
if ( x + 1 == n )
cout->putln( x );
else
{
cout->put( x );
cout->put( ", " );
}
x = x + 1;
}
The above code will output the following sequence to the console:
Code:
0, 1, 2, 3, 4, 5, 6, 7, 8, 9
C++ Portability
Because of a few recent long nights, I have now developed a more elegeant way to write code in C++ for ep, this meaning things like writing a library for ep. It works by getting the signature of a function at compile time, and as such using tuple's to determine the type at each place of the parameters, it gives you a major headache. But it does make things for you developers, and me as well.
Here's an example of how you as a developer may end up writing code for ep:
Code:
auto module = std::make_shared<ep::module>( );
struct Type
{
int x;
};
ep::add_type( module,
"Type",
// Constructor
[module]( /* put any types you wish */ const int &x )
{
auto p = new Type( );
p->x = x;
return std::make_shared<ep::value_reference>( p, module->get_engine()->get_type_info( "Type" ) );
},
// Destructor
[]( ep::value_reference *ptr )
{
delete ptr->get_pointer<void>( );
},
// Copy constructor, or clone constructor.
[]( ep::value_reference *ptr )
{
// clone ptr
auto clone = new Type( );
clone->x = ptr->get_pointer<Type>( )->x;
return std::make_shared<ep::value_reference>( clone, ptr->type_info( ) );
},
// The below code can be repeated about 10^3 times,
// meaning you can around a 1000 methods in this simple function,
// I'm pretty sure that should be MORE than enough.
// As long as it follows this pattern of ( Str, Callable, Pattern... )
// Where Str is a type that a std::string can be constructed with,
// where Callable is a callable object, and
// Where Pattern... is the above pattern repeated an even amount of times.
"get_x",
[module]( Type *thisptr )
{
return std::make_shared<ep::value_reference>( new int( thisptr->x ), module->get_engine( )->get_type_info( "integral" ) );
} );
Example of how the construtor will be called from an ep::method object:
Code:
constructor( *arguments.at( 0 )->get_pointer<const int>( ) );
Note that it's imporant you always have a thisptr being passed to a member function, otherwise the argument deduction will collapse and you'll be getting the wrong types everywhere.
Sincerely,
Yamiez.