SnowCron

.COM


SnowCron.com








Learn Touch Typing

SLANG: Built-in Scripting Language User's Guide

Introduction

Some programs on this site have their own built-in Scripting Language (SLANG). This language can be used to create tasks, that can run in the automated mode, to import and export data, to draw charts and much more.

See the Reference guide for the complete list of functions.

In this tutorial we will walk through couple of examples, each designed both as a tutorial and a starting point for your own programs.

Your first SLANG program

From the FILE menu select New Script File. A text editor window will open. Select File-Save(or Save As) and save this file as sample_01.tsc. Alternatively, you may open existing sample_01.tsc file that comes with the program, it is located in data\samples\scripts subdirectory.

Type in the following code:

void main()
{
    PRINT("%s\r\n", "Hello, World!");
}

Click "Run" icon (the exclamation mark) on the Toolbar. The script you have just created will automatically be saved, and executed. A new (Output) window will open, and the "Hello, World!" text will be printed in it.

The first line of our script contains the "main" function. Script may have more then one function, but when you run it, it will always look for the function called "main", as a starting point.
Note, that currently main() function (as well as timer(), init() and any other function that is called by the system, rather than from your script) can't have arguments (parameters).

The "void" in front of the function name stands for its return type. We will discuss it later. The body of the function is embraced with the figure brackets { and }. When the "}" of the "main" function is reached, the scripting engine knows, that the program is over. The PRINT is one of the built-in functions of the scripting language, it is used to produce output to the Output text window.

The "%s\r\n" is a format string. %s stands for "string" (as opposed, for example, to a number), it states, that the information to be printed (in our case, the "Hello, World!) is a string.
The "\r\n" part is not required for the script to work, what it does is adding a new line to the output. Try running the script few times with and without it, to see the difference.

See the Reference guide for a complete list of functions.

Save your file as sample_02.tsc, or open the existing one in the samples/scripts directory. Modify the code, to introduce an error in it:

void main()
{
    XPRINT("%s\r\n", "Hello, World!");
}

When you run it, instead of the "Hello, World!" you will see an error message, something like
"line: 3, C:\S_Projects\CortexPro\data\samples\scripts\_sample_02.tsc, Undefined subroutine"

Cycles

Often, we need to do the same operation more than once. For example, we may want to do something for ALL records in the file. Or just to print few lines, each with its own line number.

Save your work in the sample_03.tsc file, and add the following modifications:

void main()
{
    for(double i = 0; i < 10; i = i + 1)
    {
        PRINT("Line number = %.0f", i + 1, ", 
            %s\r\n", "Hello, World!");
    }
}

The output will look like this:

Line number = 1, Hello, World!
Line number = 2, Hello, World!
Line number = 3, Hello, World!
Line number = 4, Hello, World!
Line number = 5, Hello, World!
Line number = 6, Hello, World!
Line number = 7, Hello, World!
Line number = 8, Hello, World!
Line number = 9, Hello, World!
Line number = 10, Hello, World!

Lets walk through this example, as there are some important little details here.

First of all, the for(...) construction is called the "for cycle". The code within the figure brackets after it is called the cycle's BODY.

The "double" stands for numbers. People familiar with C may expect the "int" instead, but there are no integers, at least in this version of the scripting language.

Any variable ("i" is a variable in our example) need to be DECLARED. We can declare a numeric variable, by placing the "double" in front of is. We only need to do it once, for the first time. For example:

double i;
for(i = 0; i < 10; i = i + 1)
....

for(double j = 0; ...

Note, that in the example above, "i" was declared before the cycle, so we don't have to do it in the cycle.

At the beginning of the processing of the "for" directive, the program assigns "i" (a cycle variable) the initial value of 0: for(double i = 0; ...

Then, every cycle, its value is increased by 1 (i = i + 1), until it reaches 10 ("i < 10;" part). It means, that the cycle's body will be executed 10 times, with i equal to 0, then 1, and so on. When the value of i becomes 10, the program leaves the cycle, and goes to the next command.

Therefore, the maximum value of i is 9, not 10. However, we wanted line numbers to begin from 1 and to end at 10, so we printed not i, but i + 1:

PRINT("Line number = %.0f", i + 1, ", %s\r\n", "Hello, World!");

Also notice the way we used format strings, to make the output of the PRINT function "readable". The first format string contains not only "%f" (that stands for "printing numeric data"), but also some text ("Line number = "). The second format string, in addition to "%s", contains the comma and extra space, to separate the output from the preceding text.

See the Reference guide for a complete list of format specifiers.

Conditions

Let's say we need to count from one to ten, just as in the previous example. However, we need to find out if the value is even or odd. Create a new file, save it as sample_04.tsc, or use the existing sample:

void main()
{
    for(double i = 0; i < 10; i = i + 1)
    {
        PRINT("Line number = %.0f", i + 1);

        if((i + 1) % 2 != 0)
        {
            PRINT(" %s\r\n",  " is odd");
        }
        else
        {
            PRINT(" %s\r\n",  " is even");
        }
    }
}

The if(condition) ... else construction is used to execute different parts of the code, depending on the condition. In our example, the condition is if((i + 1) % 2 != 0), where % is an operation of taking the remainder after the division, and != means "not equal". Therefore our condition reads as "if the remainder of i + 1 divided by 2 is not zero".

If the condition is satisfied, the part of the code that is located in the figure brackets right after "if" will be executed, otherwise, the code located in the figure brackets right after "else". If thete if no "else" part (just "if"), the control will be passed to the code AFTER the body of the "if" statement.

Note, that you can make complex "if" statements, by nesting one "if" inside another. As was mentioned in the previous paragraph, you can also use a simplified form, without the "else" part.

Also note, that conditions can be combined, for example, the "if i is odd and i is less then 5" looks like if((i % 2) != 0 && i < 5)

Here && stands for "and", you can also use || (or) and ! (not).

More cycles

Now we can check for conditions within the cycle, and if necessary, break from the cycle. We can also skip particular cycles. For example, lets say we are reading data from the file. If the data is not numeric, we skip it. If the data IS numeric, we process it. And finally, if there is no more data to read, we break from the cycle, even if the cycle variable is still too small.

For our next example, let's type first ten odd numbers.

sample_05.tsc

for(double i = 1; i < 100; i = i + 1)
{
    if((i % 2) != 0)
    {
        continue i;
    }
    else
    {
        if(i >= 20)
        {
            break i;
        }
    }
    PRINT("Line number = %.0f\r\n", i);
}

First of all, we have the cycle limit equal 100, but we don't need that many numbers to be printed, so we break at i more or equal 20 (>=).

Second, we don't use (i + 1) anymore, instead, we started from i = 1. You can, as a matter of fact, have more control over the way cycle is evaluated. In its full form, it looks like for(FROM; CONDITION; STEP) Here "FROM" is the initial value, "CONDITION" is the exit check, and "STEP" is the code that is altering the cycle variable every cycle. So you can write for(i = 100; i >= 1; i = i - 1), and i will decrease, until it reaches 1. Be careful, it is possible to create infinite cycles, for example, for(i = 10; i < 2; i = i + 1) will never end, unless, of course, you break it from within, using the "break" statement.

The "break" and "continue" commands take one parameter, which is a cycle variable. It is very convenient, when you need to get out from nested cycles.

Variables

There are four types of variables in SLANG: number, string, array of numbers and array of strings. We need to declare them, before we use them:

double x;
double y = 0;
string str = "Hello";
array arr = CREATE_ARRAY(0);
array_ arr_s = CREATE_ARRAY_S(0);

Notice, that we can create numeric or string variables, and later assign the values to them.

As for arrays, they have to be created by calling the CREATE_ARRAY or CREATE_ARRAY_S functions. The reason for it is rather simple - in our scripting language we have three different types of arrays: not sorted, sorted and sorted with no duplicated values. We create them by passing 0, 1 and 2 to the CREATE_... function.

Be careful with the sorted arrays - there are things you can not do with them. Let's say, you want to alter 5th element of an array:

arr[5] = 3;

It is OK if an array is not sorted, but what if it was sorted? For example, what if it had 10, 20, 30, and 40? By inserting "3" into the 5th array's element, you will ruin the sorting!

To avoid these sort of problems, use the following approach. If the array is unsorted (0 was passed when it was created), you can assign values to its elements using assignment, like in

arr[5] = 3;

If an array is sorted, you have to use built-in functions of the scripting language. For example, you can add an element to the sorted array by calling

double nPos = ARRAY_ADD(arr, dValue);

This function will add dValue to the correct (according to sorting) position, and return the index of a new element in array. For example, if we want to insert 3 into the sorted array that already has elements 2, 4, 5, the resulting array will be 2, 3, 4, 5 and the nPos, returned by the ARRAY_ADD function will be equal 1 (indices in arrays are zero-based, 0, 1, 2...).

An important advantage of using arrays is the fact that you can use them in cycle, using the cycle variable as an array index.

for(i = 0; i < 10; i = i + 1)
{
    arr[i] = 2 * i;
}

Functions

Sometimes the same code is called from more than one part of your program. In this case, you can move it to the FUNCTION, and use function calls, rather than retyping the code many times. Also, by passing the so-called parameters to the function, you can alter the way it behaves.

Let's say you need to print the list of odd numbers, in the left column, and even numbers in the right (this is just an example, and NOT an easiest way to do it).

sample_06.tsc

void main()
{
    for(double i = 0; i < 10; i = i + 2)
    {
        PrintTable(i);
    }
}
// ---------------------
void PrintTable(double n)
{
    if((n % 2) != 0)
    {
        PRINT("%.0f", i - 1, "    %.0f\r\n", i);
    }
    else
    {
        PRINT("%.0f", i, "    %.0f\r\n", i + 1);
    }
}

Generally speaking, we didn't do anything new. Before we had one user-defined function, called "main", now we have added another one, called "PrintTable".

Our new function takes one parameter, and does some printing.

Note, that the string after // is not part of a program, it is called a comment, and the program will ignore it. There are two types of comments in SLANG, one to comment the remaining part of a line, and another one to comment a block of code, possibly with many lines. This second type of comment looks like /* ... */, the code between the /* and */ is commented.

It is possible to return values from the user-defined functions, and to use them in expressions. For example:

void main()
{
    double dSum = 0;
    for(double i = 0; i < 10; i = i + 2)
    {
        dSum = dSum + GetFive();
    }

    PRINT("%.0f\r\n", dSum);
}
// ---------------------
double GetFive()
{
    return 5;
}

This function has a non-void (double) return value. It RETURNS the numeric value, using a "return" operator. Of course, it can take parameters and perform calculations, too.

Charts

In this chapter we will discuss SLANG's charting. Open the sample_07.tsc and run it. In addition to the output window, that you have already seenbefore, the program will open a new window, called Draw Panel. Same way as text output goes to Output Panel, the Draw Panel is for graphics.

Let's walk through the code of this sample. It is not necessary to assign values to the variables in the beginning of the program, you may do it later, when you need them. But I think it is a good style, sometimes.
First thing, we are creating some variables. Some of them - most of them, actually, are for convenience only. For example, I know (from the SLANG reference guide, perhaps) that to draw a bar chart I have to pass 2 as a parameter to some function. I have a choice - either to pass 2, or to pass a variable with a meaningfull name and with value 2. The second approach will produce a much more readable code.

Pay attantion to the comments in the code below:

sample_07.tsc, part 1

void main()
{
    OUT_CLEANUP();
    
    PRINT("%s\r\n", 
        "Performing initialization of useful variables");

    double marker_non = 0;    
    double marker_rect = 1; 
    double marker_circle = 2; 
    double marker_triangle = 3; 
    double marker_s_5 = 4; 
    double marker_s_6 = 5; 
    double marker_cross = 6; 
    double marker_cross_x = 7; 
    double marker_diamond = 8; 
    double marker_triangle2 = 9;
    double marker_bar2 = 10; 
    double marker_circle2 = 11; 
    double marker_arrow_up = 12; 
    double marker_arrow_dn = 13; 
    double marker_bar = 14;
    double marker_bar3d = 15; 
    double marker_s_bar = 16; 
    double marker_s_bar3d = 17;

    double con_none = 0; 
    double con_line = 1; 
    double con_bar = 2;     
    double con_stack = 3;

    double horiz_axe_1 = 1; 
    double vert_axe_1 = 2; 
    double horiz_axe_2 = 4; 
    double vert_axe_2 = 8;

    double horiz_axe = 0; 
    double vert_axe = 1;

    double horiz_text = 0; 
    double vert_text = 1;

    double graf_no_just = 0; 
    double graf_bar_just = 1;

    double black = 0;     
    double blue = 1; 
    double green = 2; 
    double cyan = 3; 
    double red = 4; 
    double magenta = 5; 
    double brown = 6; 
    double lightgray = 7; 
    double darkgray = 8; 
    double lightblue = 9; 
    double lightgreen = 10;
    double lightcyan = 11; 
    double lightred = 12;     
    double lightmagenta = 13; 
    double yellow = 14; 
    double white = 15;

    double ps_solid = 0; 
    double ps_dash = 1; 
    double ps_dot = 2; 
    double ps_dashdot = 3; 
    double ps_dashdotdot = 4;

    double true = 1; 
    double false = 0;

    double nNumbersX = 0;
    double nDateX = 1;


Once again, there is only one reason for these variables - our convenience. Compare
DRAW_AXES(2, 4, 2, 2, 1, 4, 0 + 1 + 2 + 3);
and
DRAW_AXES(blue, red, ps_dot, green, 1, 4, horiz_axe_1 + vert_axe_1 + horiz_axe_2 + vert_axe_2);
In which case you have less problems ubderstanding the code?

Next thing we need is some data to plot on our charts. Using what we already know about arrays, lets create few and fill them with numbers.

sample_07.tsc, part 2

PRINT("%s\r\n", "Performing initialization of arrays of demo data");

array arr_x = CREATE_ARRAY(0);
arr_x[0] = 0; arr_x[1] = 1; arr_x[2] = 2; 
    arr_x[3] = 3; arr_x[4] = 4; 
arr_x[5] = 5; arr_x[6] = 6; arr_x[7] = 7; 
    arr_x[8] = 8; arr_x[9] = 9;

array arr_y = CREATE_ARRAY(0);
arr_y[0] = 0; arr_y[1] = 1; arr_y[2] = 2; 
    arr_y[3] = 3; arr_y[4] = 4; 
arr_y[5] = 5; arr_y[6] = 4; arr_y[7] = 3; 
    arr_y[8] = 2; arr_y[9] = 3;

array arr_y1 = CREATE_ARRAY(0);
arr_y1[0] = 0; arr_y1[1] = 2; arr_y1[2] = 4; 
    arr_y1[3] = 8; arr_y1[4] = 16; 
arr_y1[5] = 16; arr_y1[6] = 8; arr_y1[7] = 4; 
    arr_y1[8] = 2; arr_y1[9] = 0;

array arr_shifts = CREATE_ARRAY(0);
for(double i = 0; i < 20; i = i + 1)
{
    arr_shifts[i] = 0;
}
.......

In the code above, the arr_shifts array is very important. When we create bars (one bar is displayed on top of another, or side to side), we need to tell the function, where the previous bar was! So we pass this array to the function every time we plot another dataset on the same chart (only when we do bar charts). For example, let's say, we need to plot 3 datasets on a bar chart. It means, that bars of the first dataset (1,1; 2, 2; 3,1) will look like bars, with X coordinates 1, 2 and 3, and height 1, 2 1nd 1. Then, to draw the second dataset, we need to draw bars (perhaps, of the different colors) side to side to the first bars. How should the program know what shift to use? For the bars of the first dataset, the shift is 0, for the second - it is the width of the bars of the first dataset, for the third - double width (width of the bar of the first dataset plus width of the bar of the second dataset) and so on. These shifts are kept in an extra parameter, arr_shifts, all you need to do is to create an empty array, fill it with zeros, and pass it to the corresponding function. After the first time, it will contain shifts for the bars of the second array, and so on.

Next, we need to decide what colors to use. We can use default colors, but it is not necessarily the best choice.

sample_07.tsc, part 3

PRINT("%s\r\n", "Performing initialization of 
    arrays of demo colors and styles for 
    markers and connectors");

ADD_SERIES_STYLE(marker_rect, con_line);
MARKER_SET_COLOR(red, red, 1);
CONNECTOR_SET_COLOR(red, red, 1);

ADD_SERIES_STYLE(marker_circle, con_line);
MARKER_SET_COLOR(green, green, 1);
CONNECTOR_SET_COLOR(green, red, 1);

ADD_SERIES_STYLE(marker_non, con_line);
MARKER_SET_COLOR(magenta, magenta, 1);
CONNECTOR_SET_COLOR(magenta, magenta, 1);

ADD_SERIES_STYLE(marker_bar, con_bar);
MARKER_SET_COLOR(red, yellow, 1);
CONNECTOR_SET_COLOR(green, cyan, 1);

ADD_SERIES_STYLE(marker_bar, con_bar);
MARKER_SET_COLOR(green, cyan, 1);
CONNECTOR_SET_COLOR(green, cyan, 1);

ADD_SERIES_STYLE(marker_bar3d, con_bar);
MARKER_SET_COLOR(red, yellow, 1);
CONNECTOR_SET_COLOR(red, yellow, 1);

ADD_SERIES_STYLE(marker_bar3d, con_bar);
MARKER_SET_COLOR(lightred, lightblue, 1);
CONNECTOR_SET_COLOR(lightred, lightblue, 1);

ADD_SERIES_STYLE(marker_s_bar, con_bar);
MARKER_SET_COLOR(red, yellow, 1);
CONNECTOR_SET_COLOR(red, yellow, 1);

ADD_SERIES_STYLE(marker_s_bar, con_bar);
MARKER_SET_COLOR(lightred, lightblue, 1);
CONNECTOR_SET_COLOR(lightred, lightblue, 1);

ADD_SERIES_STYLE(marker_s_bar3d, con_bar);
MARKER_SET_COLOR(red, yellow, 1);
CONNECTOR_SET_COLOR(red, yellow, 1);

ADD_SERIES_STYLE(marker_s_bar3d, con_bar);
MARKER_SET_COLOR(lightred, lightblue, 1);
CONNECTOR_SET_COLOR(lightred, lightblue, 1);

The preparation is complete, here goes our first chart

sample_07.tsc, part 4

PRINT("%s\r\n", "Line and Markers chart, automatic 
    labels calculation");

SET_CHART_CLIP(40, 40, 240, 240);

CALC_SCALE(arr_x, horiz_axe, true, graf_no_just);
CALC_SCALE(arr_y, vert_axe, true, graf_no_just);
CALC_SCALE(arr_y1, vert_axe, false, graf_no_just);

SET_AXE(horiz_axe_1, 5, horiz_text, nNumbersX);
SET_AXE(vert_axe_1, 5, horiz_text, nNumbersX);
SET_AXE(horiz_axe_2, 5, horiz_text, nNumbersX);
SET_AXE(vert_axe_2, 5, horiz_text, nNumbersX);

DRAW_AXES(blue, red, ps_dot, green, 1, 4, 
    horiz_axe_1 + vert_axe_1 + horiz_axe_2 
    + vert_axe_2);

DRAW_CROSS();

PLOT(0, arr_x, arr_y, 10);
PLOT(1, arr_x, arr_y1, 10);

First, we have specified the size of a chart, by calling the SET_CHART_CLIP.

Then, we calculated the scale, for one x array and two y arrays (we are going to plot two datasets that share the same X data, just in this example).

Then we specify text layout, style and number of ticks for axes.

Then we draw axes, the cross (two lines intersecting in 0,0) and two datasets.

Note how the poor choice of number of ticks and type of grid made the chart look "bad". Later, we will produce better examples.

The second chart is using the "manual scaling", giving you more control over the axes.

sample_07.tsc, part 5

PRINT("%s\r\n", "Line and Markers chart, manual 
    scaling, automatic labels calculation");

SET_CHART_CLIP(300, 40, 500, 240);

CALC_SCALE(arr_x, horiz_axe, true, graf_no_just);

SET_CHART_SCALE(-1, -2, 12, 17);

SET_AXE(horiz_axe_1, 5, horiz_text, nNumbersX);
SET_AXE(vert_axe_1, 5, horiz_text, nNumbersX);
SET_AXE(horiz_axe_2, 5, horiz_text, nNumbersX);
SET_AXE(vert_axe_2, 5, horiz_text, nNumbersX);

DRAW_AXES(blue, red, ps_dot, green, 1, 4, 
    horiz_axe_1 + vert_axe_1 + horiz_axe_2 + 
    vert_axe_2);

DRAW_CROSS();

PLOT(0, arr_x, arr_y, 10);
PLOT(1, arr_x, arr_y1, 10);

In the next example, we are drawing line and bars on the same chart. We do it using SERIES_STYLE of different types.

sample_07.tsc, part 6

PRINT("%s\r\n", "Combined Bar and Line chart, 
    automatic labels calculation, 2 axes only");

SET_CHART_CLIP(580, 40, 780, 240);

CALC_SCALE(arr_x, horiz_axe, true, graf_no_just);
CALC_SCALE(arr_y, vert_axe, true, graf_bar_just);
CALC_SCALE(arr_y1, vert_axe, false, graf_bar_just);

SET_AXE(horiz_axe_1, 5, horiz_text, nNumbersX);
SET_AXE(vert_axe_1, 5, horiz_text, nNumbersX);
SET_AXE(horiz_axe_2, 5, horiz_text, nNumbersX);
SET_AXE(vert_axe_2, 5, horiz_text, nNumbersX);

DRAW_AXES(blue, red, ps_dot, green, 1, 4, 
    horiz_axe_1 + vert_axe_1);

DRAW_CROSS();

PLOT(3, arr_x, arr_y, 10, arr_shifts); 
PLOT(4, arr_x, arr_y1, 10, arr_shifts);
PLOT(2, arr_x, arr_y, 10);

Let's create bar3d charts, which is, generally, the same as in the example above, just with the 3d shadows.

sample_07.tsc, part 7

PRINT("%s\r\n", "Combined Bar3d and Line chart, 
    automatic labels calculation, 2 axes only");

SET_CHART_CLIP(40, 280, 300, 480);

for(i = 0; i < 20; i = i + 1)
{
    arr_shifts[i] = 0;
}

CALC_SCALE(arr_x, horiz_axe, true, graf_no_just);
CALC_SCALE(arr_y, vert_axe, true, graf_bar_just);
CALC_SCALE(arr_y1, vert_axe, false, graf_bar_just);

SET_AXE(horiz_axe_1, 5, horiz_text, nNumbersX);
SET_AXE(vert_axe_1, 5, horiz_text, nNumbersX);
SET_AXE(horiz_axe_2, 5, horiz_text, nNumbersX);
SET_AXE(vert_axe_2, 5, horiz_text, nNumbersX);

DRAW_AXES(blue, red, ps_dot, green, 1, 4, 
    horiz_axe_1 + vert_axe_1);

DRAW_CROSS();

PLOT(5, arr_x, arr_y, 10, arr_shifts); 
PLOT(6, arr_x, arr_y1, 10, arr_shifts);
PLOT(2, arr_x, arr_y, 10);

Note, that at the moment there is no scrolling in the Draw Panel, you will have wi resize it, to see all charts.

Stacked bar charts are a bit more difficult (note the way we keep passing arr_shifts array):

sample_07.tsc, part 8

PRINT("%s\r\n", "Combined Stacked Bar and Line chart, 
    automatic labels calculation, 2 axes only");

SET_CHART_CLIP(340, 280, 560, 480);

array arr_delta = CREATE_ARRAY(0);
for(i = 0; i < 20; i = i + 1)
{
    arr_shifts[i] = 0;
    arr_delta[i] = 0;
}

GET_STACKED(arr_y, 10, arr_delta);
GET_STACKED(arr_y1, 10, arr_delta);

CALC_SCALE(arr_x, horiz_axe, true, graf_no_just);
CALC_SCALE(arr_delta, vert_axe, true, graf_bar_just);

SET_AXE(horiz_axe_1, 5, horiz_text, nNumbersX);
SET_AXE(vert_axe_1, 5, horiz_text, nNumbersX);
SET_AXE(horiz_axe_2, 5, horiz_text, nNumbersX);
SET_AXE(vert_axe_2, 5, horiz_text, nNumbersX);

DRAW_AXES(blue, red, ps_dot, green, 1, 4, 
    horiz_axe_1 + vert_axe_1);

DRAW_CROSS();

PLOT(7, arr_x, arr_y, 10, arr_shifts); 
PLOT(8, arr_x, arr_y1, 10, arr_shifts);
PLOT(2, arr_x, arr_y, 10);

As before, we can add the 3d effect to our bars:

sample_07.tsc, part 9

PRINT("%s\r\n", "Combined Stacked Bar3d and Line chart, 
    automatic labels calculation, 2 axes only");

SET_CHART_CLIP(600, 280, 840, 480);

for(i = 0; i < 20; i = i + 1)
{
    arr_shifts[i] = 0;
    arr_delta[i] = 0;
}

GET_STACKED(arr_y, 10, arr_delta);
GET_STACKED(arr_y1, 10, arr_delta);

CALC_SCALE(arr_x, horiz_axe, true, graf_no_just);
CALC_SCALE(arr_delta, vert_axe, true, graf_bar_just);

SET_AXE(horiz_axe_1, 5, horiz_text, nNumbersX);
SET_AXE(vert_axe_1, 5, horiz_text, nNumbersX);
SET_AXE(horiz_axe_2, 5, horiz_text, nNumbersX);
SET_AXE(vert_axe_2, 5, horiz_text, nNumbersX);

DRAW_AXES(blue, red, ps_dot, green, 1, 4, 
    horiz_axe_1 + vert_axe_1);

DRAW_CROSS();

PLOT(9, arr_x, arr_y, 10, arr_shifts);
PLOT(10, arr_x, arr_y1, 10, arr_shifts);
PLOT(2, arr_x, arr_y, 10);

Finally, we need to "close" the drawing session, so that the result appears on screen:

sample_07.tsc, part 10

    END_DRAWING();

More charts, XML and IE

There is another way of drawing charts, that also allows you to perform some other useful tasks. To do it, we will use the third type of output window Cortex has (first two were text output and draw panel): the Web Browser Window.

To practice, let's load a file from the disk, containing stock quotes for Genzyme (the file comes with the Cortex archive). Then we will draw a chart of daily closing prices against dates.

sample_08.tsc

void main()
{
    string strDataPath = 
    "C:\\S_Projects\\CortexPro\\data\\samples\\stocks\\genz.txt";
    double bIsPathRelative = 0;

    array arrDate = CREATE_ARRAY(0);
    array arrClose = CREATE_ARRAY(0);

    TABLE_LOADER(strDataPath, bIsPathRelative, 1, "", 1, 
        "<!-- chart1.", 0, arrDate, 6, arrClose);

    Chart();
}
// -------------------------------------------
void Chart()
{
    string strXML = "";
    strXML = strXML + "<stocks>";

    string strPath = 
        "c:\\S_Projects\\CortexPro\\data\\samples\\images\\";
    string strStockName = "genz";

    string strFullPath = strPath + strStockName + ".png";    

    strXML = strXML + "<symbol>";
    strXML = strXML + "<symbol>";
    strXML = strXML + strStockName;
    strXML = strXML + "</symbol>";

    strXML = strXML + SAVE_CHART(400, 200, 1, strFullPath, 
        arrDate, arrClose);

    strXML = strXML + "</symbol>";
    strXML = strXML + "</stocks>";

    SAVE_XML(strPath, "chart_dayend", "chart_dayend", 
        "root", strXML);
    SHOW_XML(strPath + "chart_dayend.xml");
}

The SAVE_CHART function is a bit less flexible then tools you have with direct drawing on Draw Panel, but it can save pictures on disk in form of PNG files (for all practical reasons, PNG is the same as GIF).

In the code, we are building the strXML string, one that we later save using SAVE_XML, to produce the file, that can be displayed using Internet Explorer. The SHOW_XML does just that - brings up a window with IE control.

Note, that XML files use XSL files, ones that you can either take from the samples\images directory, or create yourself. To learn basics of XML, visit some of Free XML tutorials online.

Files

To work with a file, you need to OPEN it. If the file was opened successfully, you get a handler - number, that you can use to represent a file. If the open operation failed, you get -1.

After the file is opened, you can write to it, using either F_PRINT function (same as PRINT, except the first argument need to be a file handler), or by calling F_WRITE function.

You can read from file, using the F_READ function.

sample_09.tsc

void main()
{
    OUT_CLEANUP();

    // Open a file for writing in binary mode (wb)
    double hFile = F_OPEN(
        "c:\\s_projects\\CortexPro\\data\\test.txt", "wb");
    F_PRINT(hFile, "%s", "This is a test\r\n");

    double bResult = F_WRITE(hFile, "This is a write test\r\n");
    
    F_CLOSE(hFile);

    // At this point you can open TEST.TXT in text editor to 
    //    take a look at the result

    // Open file for reading
    hFile = F_OPEN("
        c:\\s_projects\\CortexPro\\data\\test.txt", "rb");
    
    // Move a point from which we will start reading
    bResult = F_SEEK(hFile, 3, 0);

    string str;

    // Read up to 20 characters
    bResult = F_READ(hFile, str, 20);
    
    // Read one line of text
    string str_1 = F_GETS(hFile);
    
    // Get file cursor position
    double nPos = F_GETPOS(hFile);
    PRINT("%s", str, ", %s", str_1, ", %2.0f\r\n", nPos);

    F_CLOSE(hFile);

    // Rename a file
    bResult = F_RENAME(
        "c:\\s_projects\\CortexPro\\data\\test.txt", 
        "c:\\s_projects\\CortexPro\\data\\test_1.txt");

    // Copy a file
    bResult = F_COPY(
        "c:\\s_projects\\CortexPro\\data\\test_1.txt", 
        "c:\\s_projects\\CortexPro\\data\\test_2.txt");

    // Delete a file
    bResult = F_UNLINK(
        "c:\\s_projects\\CortexPro\\data\\test_1.txt");
}

Notice the use of OUT_CLEANUP(); function - this way we always begin with the clean output window.

Internet

The way we work with the Internet is very simple. First, we need to open the Internet session with OPEN_HTTP_SESSION function. Then we can download one or more files, using calls to GET_HTTP function. Finally, when done, we need to close the Internet session by calling CLOSE_HTTP_SESSION.

sample_10.tsc

void main()
{
    OUT_CLEANUP();

    // Open the Internet session
    double bResult = OPEN_HTTP_SESSION();

    if(bResult == 1)
    {
        // Download a file
        string strBuffer = GET_HTTP(
            "http://snowcron.com/index.html", bResult);

        if(bResult == 1)
        {
            // Save the result to local disk
            string strFileName = 
                "c:\\s_projects\\CortexPro\\data\\samples\\test.htm";
            double hFile = F_OPEN(strFileName, "wb");
            if(hFile == -1)
            {
                PRINT("%s", "Unable to create file\r\n");
            }
            else
            {
                F_PRINT(hFile, "%s", strBuffer);
                F_CLOSE(hFile);
            }
            
            // Show the result
            SHOW_XML(strFileName);
        }
        else
        {
            PRINT("%s", "Unable to download the file\r\n");
        }
    }
    else
    {
        PRINT("%s", "Unable to open the Internet session\r\n");
    }
}

In the example above, we have downloaded the file from the Internet, and then opened its copy, one that we created on a local disk.

See Stock Downloader for another example of the use of Cortex's Internet access functions. Stock Downloader is a SLANG script that runs by a timer, as the result, we have up to date stock or Forex quotes, downloaded from the Internet sources.

Also, Internet access functions allow you to download files, containing any data (HTML is a good example) and then, to analyze these files, using SLANG as a data analysis tool. Stock Downloader is just one example.

There is another way to work with Internet resources - through the internet Explorer window. We can use it to navigate to sites, to retrieve site's content and to submit forms.

The following functions are used (see Reference Guide:

RUN_JSCRIPT, HTML_SET_ELEMENT, HTML_GET_ELEMENT, HTML_CLICK, SHOW_XML, SHOW_XML_EX

Neural Networks

Working with Neural Networks from SLANG programs is similar to working with files. As with files, you need to open a Neural Network, in order to receive a handle. This handle can be passed to other NN-related functions.

sample_nn_01.tsc, part 1

void main()
{
    array arrNN = CREATE_ARRAY(0);
    double dMA_1 = 14;
    string strStockPath = 
        "c:\\S_Projects\\CortexPro\\data\\samples\\stocks\\genz.txt";
    double bIsPathRelative = 0;
    array arrLags = CREATE_ARRAY(0);

    array arrDate = CREATE_ARRAY(0);
    array arrClose = CREATE_ARRAY(0);

    TABLE_LOADER(strStockPath, bIsPathRelative, 0, "", 1, 
        "<!-- chart1", 0, arrDate, 6, arrClose);

The code above downloads the data from the file genz.txt, one of the sample files that are included in Cortex archive.

sample_nn_01.tsc, part 2

    double hNN = OPEN_NN(
        "c:\\S_Projects\\CortexPro\\data\\samples\\nn\\genz.nn",
        0);

    for(double i = 0; i < 9; i = i + 1)
    {
        arrLags[i] = i + 1;
    }

    APPLY_NN(hNN, 1000, 1.0, 1, arrClose, arrLags, 1, arrNN);

    CLOSE_NN(hNN);

    ....
}
....

The code above opens the NN (previously created one), creates lags (we use simple lags, so instead of calling built-in function, we do it in cycle), applies NN to the data and closes NN.

There are few functions that allow you to do from script all things you can do by hand from the Cortex GUI. For example, you can create an empty NN, set number of layers, neurons, inputs, outputs and so on.

You can start the NN learning session, and specify at what conditions it should stop...

sample_nn_02.tsc

...
    CREATE_NN(strNnFileName, bIsPathRelative,
        strLagFileName, bIsPathRelative, nSkipBefore, 
        nSkipAfter, strStartLine, strEndLine, bReverseArrays, 
        arrInputColumns, arrInputColumnNames, arrOutputColumns, 
        arrOutputColumnNames, nExtractRecords, dStopError, 
        nStopEpoch, nNeuronsLayer1, nNeuronsLayer2, 
        nNeuronsLayer3, nNeuronsLayer4, nLayers, nActivation, 
        nAdjustRange, arrOutTabInputColumns, 
        arrOutTabInputColumnNames, arrOutTabOutputColumns, 
        arrOutTabOutputColumnNames);

        string strSeparator = ",;";

    CREATE_LAG_FILE(strStockFileName, bIsPathRelative, 
        strLagFileName, bIsPathRelative, nSkipBefore, 
        nSkipAfter, strStartLine, strEndLine, strSeparator, 
        bReverseArrays, arrColumns, arrColumnNames, 
        arrLags, nNumOfInputColumns);

    OPEN_NN_FILE(strNnFileName, 0, 1, 1, 1);
}

Sometimes, we process data as they arrive (a real time trading is a good example). To process all 10,000 records every time is very inconvenient. To handle this case, Cortex's scripting language has a function, that allows record-by record (or, to be more accurate, pattern-by-pattern) processing.

sample_nn_03.tsc

....
    double hNN = OPEN_NN(
        "c:\\S_Projects\\CortexPro\\data\\samples\\nn\\genz.nn",
        0);

    for(i = 9; i < ARRAY_SIZE(arrClose); i = i + 1)
    {
        for(double j = 0; j < 9; j = j + 1)
        {
            arrPattern[j] = arrCloseNorm[i - j - 1];
        }

        PATTERN_NN(hNN, arrPattern, dExtendRange);
        arrNN[i] = arrPattern[9];
    }

    CLOSE_NN(hNN);

....

In the code above, we opened the NN, and then feed it data, pattern by pattern. Pattern (for this particular network) is an array of the last 9 values of Close.


Often, we want to create and teach Neural Networks using Cortex, and then to use the results from some other packages. A good example is stock / Forex trading. Let's say, you have created the NN, that you would like to use from, say, Srade Station, MetaStock or MetaTrader.

First choice is to learn C++ and to use Cortex API, however it is not necessarily the best choice. All, or almost all trading packages have their own scripting languages - can we use our results (NN configuration) there, without fighting our way through C++ and DLLs? After all, when the NN is created and teached, all heavy computations are done, and you could have do the rest even using relatively slow scripting engines of your favorite trading package - if you had access to the weights of a fully trained NN, and knew what to do with them.

That is why the GET_NN_WEIGHTS function was created.

The function itself returns weights for a specified layer:

array arrWeights = GET_NN_WEIGHTS(hNn, nLayer);

Let's say, we are retrieving weights for the 1st layer, and there are 5 neurons in the previous, 0th layer. Then array elements 0 - 4 will contain weights for the 1st neuron, and so on.

Below, we have two examples. The first one is to show, how to retrieve weights of the fully trained NN (sample_nn_05), and the second one (sample_nn_06) is to "emulate" the NN without Cortex-specific calls.

It means, that, having "some other trading package", with "some other scripting language", you will be able to port this code from SLANG to that scripting language, and to have NN functionality in your favorite trading package, without the need to learn this packages API and Cortex's API.

sample_nn_05.tsc

void main()
{
    // Clean the output window
    OUT_CLEANUP();

    // Here we are going to keep images and XML
    string strImagePath = 
        "c:\\S_Projects\\CortexPro\\data\\samples\\images\\";

    // FOREX symbol we use in this example
    string strForexName = "EURUSD_H1";    
    
    // Name of the image file we will produce
    string strImageFileName = strImagePath + 
        strForexName + "_05.png";

    // Name of the NN
    string strNnPath = 
        "c:\\S_Projects\\CortexPro\\data\\samples\\nn\\";
    string strNnFileName = strNnPath + strForexName + "_05.nn";

    // ------------

    // Name of the FOREX quotes file (comes with Cortex)
    string strDataFileName = 
        "c:\\S_Projects\\CortexPro\\data\\samples\\forex\\"
        + strForexName + ".TXT";

    // Name of the lag file we will create
    string strLagFileName = 
        "c:\\S_Projects\\CortexPro\\data\\samples\\nn\\"
        + strForexName + "_05.lgg";
    
    // We are going to use absolute path to file(s)
    double bIsPathRelative = 0;

    // Arrays to store quotes
    array     arrDate = CREATE_ARRAY(0);
    array arrTime = CREATE_ARRAY(0);
    array arrOpen = CREATE_ARRAY(0);
    array arrHigh = CREATE_ARRAY(0);
    array arrLow = CREATE_ARRAY(0);
    array arrClose = CREATE_ARRAY(0);

    // Load the quotes from the quotes file
    TABLE_LOADER(strDataFileName, bIsPathRelative, 
        0, "", 0, "", 0, arrDate, 1, arrTime, 2, arrOpen, 
        3, arrHigh, 4, arrLow, 5, arrClose);

    // Interval for CLV indicator (that's what NN will predict)
    double nInterval = 64;
    // MA to smooth the CLV indicator
    double nMa = 3;
    
    // Create CLV indicator and apply MA to it
    array arrClv = CreateClv(nInterval);
    array arrClvSmooth = EXP_MVAVG(arrClv, nMa);

    // We are going to predict it 2 bars (2 hours) into future
    double nOutLag = 2;
    double nRemoveFirst = 200;
    array arrLags = CREATE_ARRAY(0);
    
    // ATTN: These lags are based on nOutLag. 
    // Example: nWinLag = 2, then each number 
    // should be increased by 2
    // The first number (9) is the total number of elements in the
    // lags array (there are 9 numbers after 9 in this string)
    string strLagBuf = "9,0,1,2,3,4,8,12,16,20";

    // Create the lag file, save it to disk
    CreateLagFile(strLagBuf, nRemoveFirst);

    // We are going to use the first 70% of the records as 
    // "learning set", and the rest as "testing set"
    double nExtractRecords = 0.7 * ARRAY_SIZE(arrClose);

    // Additional parameters of the NN we want to create
    double nNeurons = 5;
    double dStopError = 0;
    double nStopEpoch = 5000;

    // The output (predicted) array
    array arrNn = CREATE_ARRAY(0);

    // Create a new NN
    NewNn(arrLags, dStopError, nStopEpoch, nNeurons);
    
    // Bring up the dialog, teach the NN
    TeachNn();

    // We are ready to use the NN. First, obtain the handler
    double hNn = OPEN_NN(strNnFileName, bIsPathRelative);
    
    // Apply NN. We don't really need it to get weights, but 
    // as, just for this example, we are drawing a chart, 
    // we may want to plot predicted values as well.
    APPLY_NN(hNn, nExtractRecords, 1.0, 1, arrClvSmooth, 
        arrLags, 1, arrNn);

    // Get and print the values of the NN weights

    PRINT("%s\r\n", "Layer 1");

    array arrWeights = GET_NN_WEIGHTS(hNn, 0); 
    
    // "9" is number of lags, same as number of inputs of the 1st layer
    // It is also number of neurons in the 1st layer
    for(double nNeuron = 0; nNeuron < 9; nNeuron = nNeuron + 1)
    {
        PRINT("Neuron %.0f: ", nNeuron);

        for(double nInput = 0; nInput < 9; nInput = nInput + 1)
        {
            PRINT("%f, ", arrWeights[9 * nNeuron + nInput]);
        }

        PRINT("%s", "\r\n");
    }

    PRINT("%s\r\n", "Layer 2");

    array arrWeights = GET_NN_WEIGHTS(hNn, 1); 
    
    // "nNeurons" is number of neurons in the 2nd (hidden) layer
    // "9" is number of inputs (same as number of neurons in the 
    // previous (1st) layer
    for(double nNeuron = 0; nNeuron < nNeurons; nNeuron = nNeuron + 1)
    {
        PRINT("Neuron %.0f: ", nNeuron);

        for(double nInput = 0; nInput < 9; nInput = nInput + 1)
        {
            PRINT("%f, ", arrWeights[nNeurons * nNeuron + nInput]);
        }

        PRINT("%s", "\r\n");
    }

    PRINT("%s\r\n", "Layer 3");

    array arrWeights = GET_NN_WEIGHTS(hNn, 2); 
    
    // 1 neuron
    // "nNeurons" is number of inputs (same as number of neurons in the 
    // previous (2nd) layer

    for(double nInput = 0; nInput < nNeurons; nInput = nInput + 1)
    {
        PRINT("%s", "Neuron 1: ");

        PRINT("%f, ", arrWeights[nInput]);

        PRINT("%s", "\r\n");
    }

    CLOSE_NN(hNn);

    PRINT("%s\r\n", "Done");
}

// ---------------

// This indicator is included with "Indicators pack",
// that comes with Cortex Scripting Language

array CreateClv(double nInterval)
{    
    PRINT("%s\r\n", "Creating CLV indicator");

    array arrClv = CREATE_ARRAY(0);
    array arrPeriodLow = CREATE_ARRAY(0);
    array arrPeriodHigh = CREATE_ARRAY(0);

    double nArraySize = ARRAY_SIZE(arrClose);

    array arrPeriodLow = MVMIN(arrLow, nInterval);
    array arrPeriodHigh = MVMAX(arrHigh, nInterval);

    for(double i = 0; i < nInterval; i = i + 1) 
    {
        arrClv[i] = 0;
    }
    
    double dClose;
    double dLow;
    double dHigh;
    for(i = nInterval; i < nArraySize; i = i + 1)
    {
        dClose = arrClose[i];
        dLow = arrPeriodLow[i];
        dHigh = arrPeriodHigh[i];

        // / 2 + 1 to confine to 0...1 instead of -1...1
        arrClv[i] = (((dClose - dLow) - (dHigh - dClose))
            / (dHigh - dLow)) / 2 + 0.5; 
    }

    return arrClv;
}

// ----------------

// Create file with lags on disk, see Introduction to 
// neural networks (neural_networks.htm) for details 
//    on what lags are and why we need them

void CreateLagFile(string strLags, double nRemoveFirst)
{
    double hFile = F_OPEN(strLagFileName, "wb");

    F_PRINT(hFile, "%s", "No,Clv");

    ARRAY_REMOVE(arrLags, -1);
    string strToken = GET_TOKEN(strLags, ",");
    double nNumOfLags = STR2NUM(strToken);
    
    for(double i = 0; i < nNumOfLags; i = i + 1)
    {
        strToken = GET_TOKEN(strLags, ",");
        arrLags[i] = STR2NUM(strToken) + nOutLag;
    }

    double nFullLag;
    for(i = 0; i < ARRAY_SIZE(arrLags); i = i + 1)
    {
        nFullLag = arrLags[i];
        F_PRINT(hFile, ",ClvMa%.0f", nMa, "-%.0f", nFullLag); 
    }

    F_PRINT(hFile, "%s\r\n", "");

    double nNum;
    for(i = nRemoveFirst; i < ARRAY_SIZE(arrClose); i = i + 1)
    {
        nNum = i - nRemoveFirst + 1;
        F_PRINT(hFile, "%.0f", nNum, ",%f", arrClvSmooth[i]);     

        for(double j = 0; j < ARRAY_SIZE(arrLags); j = j + 1)
        {
            F_PRINT(hFile, ",%f", arrClvSmooth[i - arrLags[j]]);     
        }

        F_PRINT(hFile, "%s\r\n", "");
    }

    F_CLOSE(hFile);
}

// --------------

// Create a new NN on disk. This code have already been
// explained in this tutorial

void NewNn(array arrLags, double dStopError, 
    double nStopEpoch, double nNeuronsLayer2)
{
    double nSkipBefore = 0;
    double nSkipAfter = 0;
    string strStartLine = "";
    string strEndLine = "";
    double bReverseArrays = 0;

    // Inputs
    array arrInputColumns = CREATE_ARRAY(0);
    array_s arrInputColumnNames = CREATE_ARRAY_S(0);
    
    // 0  - Number, 1 - Clv, our input begins at column No 2

    for(double nInputCol = 0; nInputCol < ARRAY_SIZE(arrLags); 
        nInputCol = nInputCol + 1) 
    {
        arrInputColumns[nInputCol] = nInputCol + 2;
        arrInputColumnNames[nInputCol] = 
            "Clv-" + NUM2STR(arrLags[nInputCol], "%.0f"); 
    }

    array arrOutputColumns = CREATE_ARRAY(0);
    arrOutputColumns[0] = 1;    // Clv

    array_s arrOutputColumnNames = CREATE_ARRAY_S(0);
    arrOutputColumnNames[0] = "Clv";

    // To do: Modify if more than one indicator is used
    double nNeuronsLayer1 = ARRAY_SIZE(arrLags);
//    double nNeuronsLayer2 = 7;
    double nNeuronsLayer3 = 1;
    double nNeuronsLayer4 = 0;
    double nLayers = 3; 
    double nActivation = 0;
    double nAdjustRange = 1.0;

    array arrOutTabInputColumns = CREATE_ARRAY(0);
    arrOutTabInputColumns[0] = 0;    // Number

    array_s arrOutTabInputColumnNames = CREATE_ARRAY_S(0);
    arrOutTabInputColumnNames[0] = "No";

    array arrOutTabOutputColumns = CREATE_ARRAY(0);
    // Desired output and NN output will be added to the 
    // same list, right after inputs
    arrOutTabOutputColumns[0] = ARRAY_SIZE(arrLags) + 2;    
    arrOutTabOutputColumns[1] = ARRAY_SIZE(arrLags) + 3;

    array_s arrOutTabOutputColumnNames = CREATE_ARRAY_S(0);
    arrOutTabOutputColumnNames[0] = "Clv"; 
    arrOutTabOutputColumnNames[1] = "NN: Clv" ;

    CREATE_NN(strNnFileName, bIsPathRelative, strLagFileName, 
        bIsPathRelative, nSkipBefore, nSkipAfter, strStartLine, 
        strEndLine, bReverseArrays, arrInputColumns, 
        arrInputColumnNames, arrOutputColumns, 
        arrOutputColumnNames, nExtractRecords, dStopError, 
        nStopEpoch, nNeuronsLayer1, nNeuronsLayer2, 
        nNeuronsLayer3, nNeuronsLayer4, nLayers, 
        nActivation, nAdjustRange, arrOutTabInputColumns, 
        arrOutTabInputColumnNames, arrOutTabOutputColumns, 
        arrOutTabOutputColumnNames);
}

// ----------------

void TeachNn()
{
    PRINT("%s\r\n", "Opening NN dialog, teaching the NN");

    double bStartLearning = 1; 
    double bResumeScript = 1;
    double bReset = 1;

    OPEN_NN_FILE(strNnFileName, bIsPathRelative, 
        bStartLearning, bResumeScript, bReset);
}

// -------------

void Chart(string strForexName)
{
    string strXML = "<forex>\r\n";

    strXML = strXML + 
        "\t<symbol>\r\n\t\t<symbol>\r\n";

    strXML = strXML + "\t\t</symbol>\r\n";

    strXML = strXML + "\t\t" + SAVE_CHART(400, 300, 
        0, strImageFileName, 
        arrDate, arrClvSmooth, arrNn);
    strXML = strXML + "\t</symbol>\r\n";

    strXML = strXML + "</forex>\r\n";
    SAVE_XML(strImagePath, "chart_forex_nn_05", 
        "chart_forex_nn", "root", strXML);
    SHOW_XML(strImagePath + "chart_forex_nn.xml");

}

In this example you will find a working system that is created, step by step, and then moved from Cortex to the trading platform, capable to place real trades with the real brocker.

sample_nn_05.tsc

void main()
{
	OUT_CLEANUP();

	string strImagePath = 
		"c:\\S_Projects\\CortexPro\\data\\samples\\images\\";

	string strForexName = "EURUSD_H1";	
	
	string strImageFileName = strImagePath + 
		strForexName + "_05.png";

	string strNnPath = 
		"c:\\S_Projects\\CortexPro\\data\\samples\\nn\\";
	string strNnFileName = strNnPath + 
		strForexName + "_05.nn";

	// ------------

	string strDataFileName = 
		"c:\\S_Projects\\CortexPro\\data\\samples\\forex\\"
		+ strForexName + ".TXT";
	string strLagFileName = 
		"c:\\S_Projects\\CortexPro\\data\\samples\\nn\\"
		+ strForexName + "_05.lgg";
	
	double bIsPathRelative = 0;

	array 	arrDate = CREATE_ARRAY(0);
	array arrTime = CREATE_ARRAY(0);
	array arrOpen = CREATE_ARRAY(0);
	array arrHigh = CREATE_ARRAY(0);
	array arrLow = CREATE_ARRAY(0);
	array arrClose = CREATE_ARRAY(0);

	TABLE_LOADER(strDataFileName, bIsPathRelative, 
		0, "", 0, "", 0, arrDate, 1, arrTime, 2, 
		arrOpen, 3, arrHigh, 4, arrLow, 5, arrClose);

	double nInterval = 64;
	double nMa = 3;
	
	array arrClv = CreateClv(nInterval);
	array arrClvSmooth = EXP_MVAVG(arrClv, nMa);

	double nOutLag = 2;
	double nRemoveFirst = 200;
	array arrLags = CREATE_ARRAY(0);
	
	// ATTN: These lags are based on nWinLag. 
	// Example: nWinLag = 1, then each number 
	// should be increased by 1
	string strLagBuf = "9,0,1,2,3,4,8,12,16,20";

	CreateLagFile(strLagBuf, nRemoveFirst);

	double nExtractRecords = 0.7 * ARRAY_SIZE(arrClose);

	double nNeurons = 5;
	double dStopError = 0;
	double nStopEpoch = 5000;

	array arrNn = CREATE_ARRAY(0);

	NewNn(arrLags, dStopError, nStopEpoch, nNeurons);
	TeachNn();

	double hNn = OPEN_NN(strNnFileName, bIsPathRelative);
	APPLY_NN(hNn, nExtractRecords, 1.0, 1, arrClvSmooth, 
		arrLags, 1, arrNn);

	PRINT("%s\r\n", "Layer 1");

	array arrWeights = GET_NN_WEIGHTS(hNn, 0); 
	
	// "9" is number of lags, same as number 
	// of inputs of the 1st layer
	// It is also number of neurons in the 1st layer
	for(double nNeuron = 0; nNeuron < 9; 
		nNeuron = nNeuron + 1)
	{
		PRINT("Neuron %.0f: ", nNeuron);

		for(double nInput = 0; nInput < 9; 
			nInput = nInput + 1)
		{
			PRINT("%f, ", 
				arrWeights[9 * nNeuron + nInput]);
		}

		PRINT("%s", "\r\n");
	}

	PRINT("%s\r\n", "Layer 2");

	array arrWeights = GET_NN_WEIGHTS(hNn, 1); 
	
	// "nNeurons" is number of neurons in 
	// the 2nd (hidden) layer
	// "9" is number of inputs (same as number 
	// of neurons in the previous (1st) layer
	for(double nNeuron = 0; nNeuron < nNeurons; 
		nNeuron = nNeuron + 1)
	{
		PRINT("Neuron %.0f: ", nNeuron);

		for(double nInput = 0; nInput < 9; 
			nInput = nInput + 1)
		{
			PRINT("%f, ", arrWeights[nNeurons 
				* nNeuron + nInput]);
		}

		PRINT("%s", "\r\n");
	}

	PRINT("%s\r\n", "Layer 3");

	array arrWeights = GET_NN_WEIGHTS(hNn, 2); 
	
	// 1 neuron
	// "nNeurons" is number of inputs 
	// (same as number of neurons in the 
	// previous (2nd) layer

	for(double nInput = 0; nInput < nNeurons; 
		nInput = nInput + 1)
	{
		PRINT("%s: ", "Neuron 1");

		PRINT("%f, ", arrWeights[nInput]);

		PRINT("%s", "\r\n");
	}

	CLOSE_NN(hNn);

	PRINT("%s\r\n", "Done");
}

// ---------------

array CreateClv(double nInterval)
{	
	PRINT("%s\r\n", "Creating CLV indicator");

	array arrClv = CREATE_ARRAY(0);
	array arrPeriodLow = CREATE_ARRAY(0);
	array arrPeriodHigh = CREATE_ARRAY(0);

	double nArraySize = ARRAY_SIZE(arrClose);

	array arrPeriodLow = MVMIN(arrLow, nInterval);
	array arrPeriodHigh = MVMAX(arrHigh, nInterval);

	for(double i = 0; i < nInterval; i = i + 1) 
	{
		arrClv[i] = 0;
	}
	
	double dClose;
	double dLow;
	double dHigh;
	for(i = nInterval; i < nArraySize; i = i + 1)
	{
		dClose = arrClose[i];
		dLow = arrPeriodLow[i];
		dHigh = arrPeriodHigh[i];

		// / 2 + 1 to confine to 0...1 instead of -1...1
		arrClv[i] = (((dClose - dLow) - (dHigh - dClose))
			/ (dHigh - dLow)) / 2 + 0.5; 
	}

	return arrClv;
}

// ----------------

void CreateLagFile(string strLags, double nRemoveFirst)
{
	double hFile = F_OPEN(strLagFileName, "wb");

	F_PRINT(hFile, "%s", "No,Clv");

	ARRAY_REMOVE(arrLags, -1);
	string strToken = GET_TOKEN(strLags, ",");
	double nNumOfLags = STR2NUM(strToken);
	
	for(double i = 0; i < nNumOfLags; i = i + 1)
	{
		strToken = GET_TOKEN(strLags, ",");
		arrLags[i] = STR2NUM(strToken) + nOutLag;
	}

	double nFullLag;
	for(i = 0; i < ARRAY_SIZE(arrLags); i = i + 1)
	{
		nFullLag = arrLags[i];
		F_PRINT(hFile, ",ClvMa%.0f", nMa, 
			"-%.0f", nFullLag); 
	}

	F_PRINT(hFile, "%s\r\n", "");

	double nNum;
	for(i = nRemoveFirst; i < 
		ARRAY_SIZE(arrClose); i = i + 1)
	{
		nNum = i - nRemoveFirst + 1;
		F_PRINT(hFile, "%.0f", nNum, 
			",%f", arrClvSmooth[i]); 	

		for(double j = 0; j < 
			ARRAY_SIZE(arrLags); j = j + 1)
		{
			F_PRINT(hFile, ",%f", 
				arrClvSmooth[i - arrLags[j]]); 	
		}

		F_PRINT(hFile, "%s\r\n", "");
	}

	F_CLOSE(hFile);
}

// --------------

void NewNn(array arrLags, double dStopError, 
	double nStopEpoch, double nNeuronsLayer2)
{
	double nSkipBefore = 0;
	double nSkipAfter = 0;
	string strStartLine = "";
	string strEndLine = "";
	double bReverseArrays = 0;

	// Inputs
	array arrInputColumns = CREATE_ARRAY(0);
	array_s arrInputColumnNames = CREATE_ARRAY_S(0);
	
	// 0 - Number, 1 - Clv, input begins at column No 2

	for(double nInputCol = 0; nInputCol < 
		ARRAY_SIZE(arrLags); nInputCol = nInputCol + 1) 
	{
		arrInputColumns[nInputCol] = nInputCol + 2;
		arrInputColumnNames[nInputCol] = 
			"Clv-" + NUM2STR(arrLags[nInputCol], "%.0f"); 
	}

	array arrOutputColumns = CREATE_ARRAY(0);
	arrOutputColumns[0] = 1;	// Clv

	array_s arrOutputColumnNames = CREATE_ARRAY_S(0);
	arrOutputColumnNames[0] = "Clv";

	// To do: Modify if more than one indicator is used
	double nNeuronsLayer1 = ARRAY_SIZE(arrLags);
//	double nNeuronsLayer2 = 7;
	double nNeuronsLayer3 = 1;
	double nNeuronsLayer4 = 0;
	double nLayers = 3; 
	double nActivation = 0;
	double nAdjustRange = 1.0;

	array arrOutTabInputColumns = CREATE_ARRAY(0);
	arrOutTabInputColumns[0] = 0;	// Number

	array_s arrOutTabInputColumnNames = CREATE_ARRAY_S(0);
	arrOutTabInputColumnNames[0] = "No";

	array arrOutTabOutputColumns = CREATE_ARRAY(0);
	// Desired output and NN output will be added to the 
	// same list, right after inputs
	arrOutTabOutputColumns[0] = ARRAY_SIZE(arrLags) + 2;	
	arrOutTabOutputColumns[1] = ARRAY_SIZE(arrLags) + 3;

	array_s arrOutTabOutputColumnNames = CREATE_ARRAY_S(0);
	arrOutTabOutputColumnNames[0] = "Clv"; 
	arrOutTabOutputColumnNames[1] = "NN: Clv" ;

	CREATE_NN(strNnFileName, bIsPathRelative, 
		strLagFileName, bIsPathRelative, 
		nSkipBefore, nSkipAfter, strStartLine, 
		strEndLine, bReverseArrays, arrInputColumns, 
		arrInputColumnNames, arrOutputColumns, 
		arrOutputColumnNames, nExtractRecords, 
		dStopError, nStopEpoch, nNeuronsLayer1, 
		nNeuronsLayer2, nNeuronsLayer3, 
		nNeuronsLayer4, nLayers, nActivation, 
		nAdjustRange, arrOutTabInputColumns, 
		arrOutTabInputColumnNames, arrOutTabOutputColumns, 
		arrOutTabOutputColumnNames);
}

// ----------------

void TeachNn()
{
	PRINT("%s\r\n", 
		"Opening NN dialog, teaching the NN");

	double bStartLearning = 1; 
	double bResumeScript = 1;
	double bReset = 1;

	OPEN_NN_FILE(strNnFileName, bIsPathRelative, 
		bStartLearning, bResumeScript, bReset);
}

// -------------

void Chart(string strForexName)
{
	string strXML = "<forex>\r\n";

	strXML = strXML + "\t<symbol>
		\r\n\t\t<symbol>\r\n";

	strXML = strXML + "\t\t</symbol>\r\n";

	strXML = strXML + "\t\t" + 
		SAVE_CHART(400, 300, 0, strImageFileName, 
		arrDate, arrClvSmooth, arrNn);
	strXML = strXML + "\t</symbol>\r\n";

	strXML = strXML + "</forex>\r\n";
	SAVE_XML(strImagePath, "chart_forex_nn_05", 
		"chart_forex_nn", "root", strXML);
	SHOW_XML(strImagePath + "chart_forex_nn.xml");
}

Self Organizing Maps

In Cortex, we have Kohonen type of Self Organizing Maps (SOM) implemented. SOM are used for classification tasks, generally speaking, they can figure out how to break input data into categories with no prior knowlege on the subject.

Let's walk through a simple example, note, that details are available in SLANG reference.

First of all, we need to create a SOM:

double bIsPathRelative = 1;
CREATE_SOM("..\\CortexPro\\data\\samples\\Kohonen\\colors.kh", 
	bIsPathRelative, 
	3,		// Num. of inputs, 
	500,	// Default num. of iterations
	50,		// Cells X
	50,		// Cells Y
	50);	// Update picture every 50 iterations

It will create a file "colors.kh" on a disk, containing network data.


Now we need to teach this network. The following code will bring up a dialog box, where we can adjust some parameters and to perform training.

void main()
{
	double bIsPathRelative = 1;
	double bStartLearning = 0;
	
	array arrRed = CREATE_ARRAY(0);
	array arrGreen = CREATE_ARRAY(0);
	array arrBlue = CREATE_ARRAY(0);

	TABLE_LOADER("..\\CortexPro\\data\\samples\\Kohonen\\colors.txt", 
		bIsPathRelative, 0, "", 1, "", 0, arrRed, 1, arrGreen, 2, arrBlue);
	OPEN_SOM_DLG("..\\CortexPro\\data\\samples\\Kohonen\\colors.kh", 
		bIsPathRelative, bStartLearning, arrRed, arrGreen, arrBlue);

This dialog box takes data in form of arrays, so before calling it, we need to create arrays and to populate them with data. In our example, there are 3 inputs (you can think of them as about R, G and B components of a color, if you want). Data is stored in a file colors.txt, to generate it, we used simple code (note, that data is in 0-1 range):

for(double nSet = 0; nSet < 20; nSet = nSet + 1)
{
	PRINT("%f, ", RAND(1), "%f, ", RAND(1), "%f\r\n", RAND(1)); 		
}


When we press "Seve", the dialog box closes, and writes network info to a file. Now we have a trained SOM, and it can be used to process data.

In Cortex, we access neural networks through their handles. We obtain a handle by opening a SOM and have to close it when we no longer need it:

double hSom = OPEN_SOM(
	"..\\CortexPro\\data\\samples\\Kohonen\\colors.kh", 
	bIsPathRelative);
...
CLOSE_SOM(hSom);


We can provide data to a SOM, one pattern in a time. By pattern we mean an array of data, one element per each network input. For example, if our network is trained to distinguish colors, that are provided as RGB values, it will expect 3 inputs (R, G and B components), so we provide a 3 elements array to it.

array arrPattern = CREATE_ARRAY(0);

// Note, that values are in 0 - 1 range, not in 0 - 255 
// as one would expect for RGB.	
arrPattern[0] = 0;
arrPattern[1] = 0.5;
arrPattern[2] = 0.2;	
	
array arrResult = CREATE_ARRAY(0);

arrResult = PATTERN_SOM(hSom, arrPattern);
	
PRINT("Winner:%.0f\r\n", arrResult[0], "Brightness: %.0f\r\n", arrResult[1], 
	"Cell X: %.0f\r\n", arrResult[2], "Cell Y: %.0f\r\n", arrResult[3]);

Resulting array has 4 elements: number of a winning neuron, the level of its signal, coordinates of a winning cell in a grid (yes, you can calculate neuron number using these coordinates).

then we can use the input array in 0 - 255 range.


If we are planning to use our results with some scripting language, other than SLANG (for example, with a trading platform of your choice), we need to be able to export SOM information (see details in the corresponding article on this site).

double nNumOfNeurons = GET_SOM_INFO(hSom);
	
PRINT("Neurons: %.0f\r\n", nNumOfNeurons);
	
array arrWeights = GET_SOM_WEIGHTS(hSom);
double nNumOfWeights = ARRAY_SIZE(arrWeights) / nNumOfNeurons; 

for(double i = 0; i < nNumOfNeurons; i = i + 1)
{
	PRINT("%.0f\t", i);
	for(double j = 0; j < nNumOfWeights; j = j + 1)
	{
		PRINT("%f\t\t", arrWeights[i * nNumOfWeights + j);
	}
	PRINT("%s", "\r\n");
}


Finally, we can apply our SOM to a set of records (patterns) at once. The output array contains numbers of winner neurons.

	// number of data sets
	double nSets = 20;
	array arrRed = CREATE_ARRAY(0);
	array arrGreen = CREATE_ARRAY(0);
	array arrBlue = CREATE_ARRAY(0);

	array arrRedLags = CREATE_ARRAY(0);
	arrRedLags[0] = 0;
	array arrGreenLags = CREATE_ARRAY(0);
	arrGreenLags[0] = 0;
	array arrBlueLags = CREATE_ARRAY(0);
	arrBlueLags[0] = 0;
		
	array arrOutput = CREATE_ARRAY(0);

  for(double nSet = 0; nSet < nSets; nSet = nSet + 1)
  {
		arrRed[nSet] = RAND(1);
		arrGreen[nSet] = RAND(1);
		arrBlue[nSet] = RAND(1);
  }

	APPLY_SOM(hSom, nSets, 1.0, 1, 3, arrRed, arrRedLags, arrGreen, 
		arrGreenLags, arrBlue, arrBlueLags, arrOutput);
	for(nSet = 0; nSet < nSets; nSet = nSet + 1)
	{
		PRINT("%.0f\r\n", arrOutput[nSet]);
	}

	PRINT("%s", "*****\r\n");
	APPLY_SOM(hSom, nSets, 1.0, 0, 3, 
		arrRed, arrGreen, arrBlue, arrOutput);

	for(nSet = 0; nSet < nSets; nSet = nSet + 1)
	{
		PRINT("%.0f\r\n", arrOutput[nSet]);
	}
			
	// ---	
	CLOSE_SOM(hSom);

Note, that there are two ways of using this function. First, we tell it to use input arrays only:

APPLY_SOM(hSom, nSets, 1.0, 0, 3, arrRed, arrGreen, arrBlue, arrOutput);

Second, we provide pairs array - lags, same way we did with FFBP NNs. For example, we can provide the EURUSD exchange rates as the first array, and an array { 1, 2, 3 } as lags, In this case, for array elememt i, the pattern will be eurusd[i-1], eurusd[i-2], eurusd[i-3].

APPLY_SOM(hSom, nSets, 1.0, 1, 3, arrRed, arrRedLags, 
	arrGreen, arrGreenLags, arrBlue, arrBlueLags, arrOutput);

Databases

Cortex uses SQLite database engine. It is small, fast, it implements reasonably full (see SQLite documentation online) SQL syntax, and it is file-based. The last statement means, that it does not use drivers or registry entries, if you want to move the database from one computer to another, all you need do do is to copy a single file.

To use the database, you need to open it first, after you are done, you need to close it.

hDb = DB_OPEN(string strFileName, double bIsPathRelative);
...
DB_CLOSE(double hDb);

While the database is opened, you can get a list of database field names:

array_s arrFieldNames = 
	DB_GET_FIELD_NAMES(double hDb, string strTableName);

Finally, you can run the SQL statement against the database:

double nNumOfRecords = 
	DB_SQL_RUN(double hDb, string strSql, 
		array (or array_s) arrColumn_1, ...);

Note, that the same approach is used here, as with files and NNs - an open databese is represented with a handle.

The function takes arrays, that will receive corresponding fields of a result, returned by a database query.

For an example, see sample_db_01.tsc script file, included with Cortex archive.

Image processing

Cortex uses cximage freeware image processing library.

A single image processing function, IMAGE_PROCESS, us used to take the input file and to produce a modified output file.

IMAGE_PROCESS(
	"c:\\images\\test.jpg", 
	"png",
	0, 		// bGrayScale
	1.5, 	// nBrightness
	1, 		// nContrast
	1, 		// bKeepAspect 
	640, 	// nWidth 
	480); 	// nHeight);

Forms

Cortex takes XML (or HTML) file as the input, to create a modal dialog, containing a form.

The dialog takes pairs in form "variable - corresponding field", so after the user closes the dialog, we can find out, what values were set to each field.

If the field contains multiple values (list box with multiple selection), you should pass a variable of list type, that will be filled, one value per array element.

bOkCancel = HTML_FORM(nWidth, nHeight, 
	strTemplate, strFieldName1, varFieldValue1, ...); 

This function creates a modal dialog, based on HTML (or XML) template, and waits for the user to take an action.

Return value: 0 if the user clicks CANCEL, 1 if the user clicks OK.

nWidth, hHeight are width and height of the window, in screen pixels.

strTemplate is the name of HTML (or XML) file, that the form uses.

pairs strFieldName, varFieldValue are used to retrieve the data from the form. strFieldName is a name of the field in the form, while varFieldValue is the variable (previously declared, and in case of arrays, initialized by calling ARRAY_CREATE or ARRAY_CREATE_S) that will receive the value of that field.

If the field contains multiple values (list box with multiple selection), you should pass a variable of list type, that will be filled, one value per array element.

To close the dialog, you can use either Cancel or OK:

<input type="submit" name="cancel" value="Cancel">
<input type="submit" name="ok" value="OK">

Note, that the name of OK button must be 'ok'.

To simulate the form submit via the html link, use JavaScript, for example:

<script type="text/javascript">

function LinkSelected(control)
{
	document.forms.form.what.value = 'Link was clicked';
	document.forms.form.ok.click();
}

</script>
...

<a href="#" OnClick=LinkSelected(this)>link</a>

For an example, see sample_forms_01.tsc script file, included in Cortex archive.

Regular expressions

Cortex uses Microsoft Regular Expressions, that come with the Internet Explorer (VB, actually), and is, therefore, available on each Windows machine.

For a description of a RegEx syntax, see Microsoft documentation (search Google for Regular Expressions Microsoft).

PLUGINS

It is possible to create third party plugins in the form of DLLs, and to call their functions from Cortex scripts. In this mini tutorial we will walk through the process of creating a simple plugin (comes with Cortex archive as a sample).

#plugin

This is a preprocessor directive, that allows you to load your own (or third party) functions from DLLs.

The source code for a sample plugin: \CortexPro\Plugins\Sample

The use (see CortexPro\data\samples\script\sample_plugin.tsc)

#plugin "sample.dll"

Here, sample.dll is a third party plugin. The path to plugins is relative to the Bin directory.

Creating third party plugin

Below is an overview of Cortex plugin example, written in C++. In theory, it should work with other languages, too. The source code is available in \CortexPro\Plugins\Sample.

First of all, you need to create a DLL, we assume, you already know how to do it. Then, the following functions should be added (once again, this is a C++ example, but it should be possible to port to other languages):

enum {	TOKEN_VOID = 4,
		TOKEN_NUMBER = 8,			
		TOKEN_STRING = 16,			
		TOKEN_ARRAY = 32,
		TOKEN_ARRAY_S = 64 
};

This are predefined values for types you pass to function (one you call from SLANG script, for example, double x = Plus(2, 3), where Plus is a function from plugin). Same numbers are used to specify a return type (in case of Plus function above, it is "double").

enum { LIST_UNSORTED, LIST_SORTED, LIST_SORTED_NODUPS };

Additional values, specifying, if an array you return (in case your function returns an array) is sorted, and (if sorted), if it allows duplicated values.
LIST_UNSORTED: 1, 5, 2, 4, 2...
LIST_SORTED: 1, 2, 3, 3, 4...
LIST_SORTED_NODUPS: 1, 2, 4, 5...

int GetNumOfFunctions()
{
	return 5;
}

This is a mandatory function, all it does is returning number of functions plugin exports.

int GetFunctionInfo(int nFunctionNumber, char* strFunctionName, int* pnArgumentTypes)
{
	switch(nFunctionNumber)
	{
		case 0:
			strcpy(strFunctionName, "Pow2");
			pnArgumentTypes[0] = 1;
			pnArgumentTypes[1] = TOKEN_NUMBER;
			return TOKEN_NUMBER;
	
		case 1:
			strcpy(strFunctionName, "Hi");
			pnArgumentTypes[0] = 1;
			pnArgumentTypes[1] = TOKEN_STRING;
			return TOKEN_STRING;
	
		case 2:
			strcpy(strFunctionName, "ArrayOfDouble");
			pnArgumentTypes[0] = 1;
			pnArgumentTypes[1] = TOKEN_ARRAY;
			return TOKEN_ARRAY;
	
		case 3:
			strcpy(strFunctionName, "Plus");
			pnArgumentTypes[0] = 2;
			pnArgumentTypes[1] = TOKEN_NUMBER;
			pnArgumentTypes[2] = TOKEN_NUMBER;
			return TOKEN_NUMBER;

		case 4:
			strcpy(strFunctionName, "ArrayPlus");
			pnArgumentTypes[0] = 2;
			pnArgumentTypes[1] = TOKEN_ARRAY;
			pnArgumentTypes[2] = TOKEN_NUMBER;
			return TOKEN_ARRAY;

		default:
			return -1;
	}
}

A second mandatory function, it returns information about a particular function plugin exports.
Functions are arranged by number, which is transparent for the user.
Return value: return type of a function (TOKEN_VOID, TOKEN_NUMBER, TOKEN_STRING, TOKEN_ARRAY, TOKEN_ARRAY_S), these values cover all possible types a SLANG function can return.
strFunctionName - non-decorated name of function, same as in our DLL's .DEF file (see sample code).
pnArgumentTypes[0] - num. of arguments we pass to a function
pnArgumentTypes + 1 - first, second and so on elements of this array hold argument types for corresponding parameters we pass to a function. For example, if function takes double and string (f(double x, string str)), then pnArgumentTypes[1] will hold TOKEN_NUMBER, and pnArgumentTypes[2] will hold TOKEN_STRING.

Maximum number of function parameters: the size of array we use internally in Cortex to hold parameters passed to a function is 50, when you pass an array, it takes two elements, when you pass double or string, it takes one element. Therefore, max. number of arguments 2*nNumOfArrays + nNumOfDoubles + nNumOfStrings should be < 50.

All we need to do now is to add functions described in GetFunctionInfo(). Let's go through the code. For use of these functions, see CortexPro\data\samples\script\sample_plugin.tsc

// y = Pow2(x);
double __stdcall Pow2(int* pParam)
{
	double x = *((double*)pParam[0]);
	
	return x * x;
}

This is a siple function, that takes a double value x, and returns x^2. Note, that pParam[0] contains the pointer to the first parameter we passed to a function, also, note the way we retrieve the value itself from it.


// string strOut = Hi(strIn);
char* __stdcall Hi(int* pParam)
{
	char* str = strdup((char*)(pParam[0]));
	
	return str;
}

A second example returns a string. Note, that space for this string is allocated on heap, using strdup (new, malloc and so on).


// array arrOut = ArrayOfDouble(array arrIn);
double* __stdcall ArrayOfDouble(int* pParam, int& nArraySize, int& nArraySorting)
{
	// For array, first is size, then arry itself
	int nInputElements = pParam[0];
	double* pdInput = (double*)(pParam[1]);

	double* pdList = new double[nInputElements];
	for(int i = 0; i < nInputElements; i++)
		pdList[i] = pdInput[i];

	nArraySize = nInputElements;
	nArraySorting = LIST_UNSORTED;

	return pdList;
}

In this function, we return an array, which is a copy of an array we passed as a parameter. Again, we pass a pointer, so to get an array we used double* pdInput = (double*)(pParam[1]);
Note, that when we pass an array, we use two elements of pParam: the first one contains number of array elements (int nInputElements = pParam[0];) and a second one points to an array of elements itself.
As with strings, we allocate space for a new array on a heap, using "new" operator.

There are two parameters passed by reference, int& nArraySize, int& nArraySorting. We fill them with a size of a resulting (returned) array and the sorting of this array.


// z = Plus(x, y);
double __stdcall Plus(int* pParam)
{
	double x = *((double*)pParam[0]);
	double y = *((double*)pParam[1]);
	
	return x + y;
}

An example of a function, that takes two parameters.


// array arrOut = ArrayPlus(array arrIn, x);
double* __stdcall ArrayPlus(int* pParam, int& nArraySize, int& nArraySorting)
{
	// For array, first is size, then arry itself
	int nInputElements = pParam[0];
	double* pdInput = (double*)(pParam[1]);
	double x = *((double*)pParam[2]);

	double* pdList = new double[nInputElements];
	for(int i = 0; i < nInputElements; i++)
		pdList[i] = pdInput[i] + x;

	nArraySize = nInputElements;
	nArraySorting = LIST_UNSORTED;

	return pdList;
}

An example of a function, that takes two parameters, one of which is an array. As array takes two elements of pParam, therefore, the second parameter is stored in pParam[2].


Conclusion

In the SLANG Scripting language Reference guide you will find a complete list of built-in functions, with return types, parameters and so on.







(C) snowcron.com, all rights reserved

Please read the disclaimer