This article is about using job-oriented programming and pointers in a scripting language. As a scripting language, we'll use CSCS (Customized Scripting in C#). This is an easy and a lightweight open-source language that has been described in previous CODE Magazine articles: https://www.codemag.com/article/1607081 introduced it, https://www.codemag.com/article/1711081 showed how you can use it on top of Xamarin to create cross-platform native mobile apps, and https://www.codemag.com/article/1903081 showed how you can use it for Unity programming. CSCS resembles JavaScript with a few differences, such as the variable and function names being case-insensitive. In this article, we'll be talking about job-oriented programming as implemented in CSCS.

Every office job, like print invoice, create offer, manage customer data, manage product data, print accounting list, etc., has its counterpart in appropriate menu system of business application used. Behind any menu item, there's separate program micro-module. Such micro-modules can interact with each other, calling “child” micro-modules and passing parameters to them. Calling a child micro-module is done by simple Chain command, at any line of code. When the “chained” job is done, program execution proceeds with the next line of the parent job (the one that executed the Chain command). This way, you can easily develop even huge applications using any number of standalone micro-modules.

You can also send any parameters to the chained-to program (the child) and return any data from the child program. This way, you can chain from one program to another, any level deep. Any micro-module can be executed from menu or using Chain command from another micro-module.

This gives developers the freedom to work on a program independently from one another, as a micro-module is self-sufficient regarding the job that it has to do. Passing parameters is just an option.

To facilitate independent development, you need to define parameters that will be used for communication between scripts in case the parameters are needed. This makes any modifications of the whole application very easy, changing only one micro-module.

All this functionality has been made with only two functions: Chain and Param. The syntax is simple:

CHAIN programName WITH arguments;

The chained-to program (the child program) can listen for arguments with:

PARAM parameters;

The arguments can be constants, variables, or expressions, or any combination. Parameters can be a single variable, an array, or a tuple. From a child program, you can access any variable from a previous (parent) program programmatically. You can also return any value from child to parent programmatically.

Let's see how to use CHAIN/PARAM scripting commands in a C# project, taking as an example a WPF (Windows Presentation Foundation) project.

In addition to the CHAIN/PARAM functionality, we'll also look at how to use pointers in a scripting language. It's important to note that usually, the main piece of code is in a compiled language (such as C#), but only a part of the project is done in CSCS scripting. There's an obvious benefit: You don't have to re-compile anything if you change scripting code, because the scripting will be loaded at runtime.

Another benefit of scripting is that you can load scripts, XAML files, and images on the fly from your server. This way, you can get a new version of your client without a formal release! We'll show how to do this in this article.

Let's start by looking at definition of a chain and how chains can be used in scripting.

Setting Up the CSCS Scripting Environment

Probably the simplest way to start using CSCS scripting is to download the source code from GitHub (see https://github.com/vassilych/cscs) and add the source code directly to your C# .NET project. The license lets you modify and use the code without restrictions.

As an example of adding CSCS scripting to a WPF project, take a look at https://github.com/vassilych/cscs_wpf. This is already a preset WPF project with scripting added. There you can also consult the full implementation of the functionality that you'll see in this article.

Take a look at a previous CODE Magazine article: https://www.codemag.com/Article/2008081. It explains how to use WPF with CSCS scripting.

Once you set up the project structure, you can explore the possibilities of the customized scripting that you might not have explored before.

Using Chains in Scripting

In this section, you'll see a cool feature of starting new scripts from a parent script. Also, you'll see how to pass parameters between these scripts.

Running Child Scripts in the Same Process Space

A sample code for a simple CHAIN functionality is the following:

CHAIN "D:/scripts/scriptName.cscs";

This runs the specified script and waits until it finishes.

You can also specify the default scripts folder in the standard C# project appSettings configuration file (.config). Here's an example:

<add key="ScriptsPath" value="D:/scripts/"/>

In this case, you can use the path above with the special tpath() function:

CHAIN tpath()+"scriptName.cscs";

The tpath() is a CSCS function that returns the corresponding “ScriptsPath” path from the config file.

Note that spaces are not allowed inside of an argument (e.g., in tpath()+“scriptName.cscs”) because the spaces are the argument separators, as you will see in examples below.

You can also provide parameters for the chained-to script in a simple way:

CHAIN "scriptName.cscs" WITH arg1 arg2 arg3;

Example:

CHAIN tpath()+"scriptName.cscs" WITH "string1" variable2 userFunction(arg);

Here, string1 is a string constant, variable2 can be variable of any type, even an array variable of any size, and userFunction() can be any user defined function.

Running Child Scripts in a New Process Space

All chained (child) scripts in the previous section run in the same process space. If you want to run the script in another process, there's another syntax of the CHAIN command:

CHAIN tpath()+"scriptName.cscs" NEWRUNTIME WITH "arg1" "arg2" "arg3" "arg4";

In the command above, the NEWRUNTIME keyword tells the interpreter to run the script in another process. Later on, you'll see an example of a script started in a new runtime environment.

Receiving Arguments in the Child Script and Passing Them Back to the Parent Script

Consider a child script, scriptName.cscs, started from the parent's script CHAIN command in the previous section. That example contains four arguments, passed from the parent: strings “arg1”, “arg2”, “arg3”, and “arg4”. In the chained (child) script, the arguments from the parent script are received as follows:

PARAM(param1, param2, param3, param4);

After running the PARAM command above, the chained script sets its local variables as follows:

param1 = "arg1"; param2 = "arg2";
param3 = "arg3"; param4 = "arg4";

These four variables are local to the chained script and won't be known to the parent.

It's also possible to have a two-way exchange of the parameters between the child and the parent script. The syntax is as follows:

ResetField(scriptName, variableName);

For example:

ResetField("scriptName.cscs", "varNameInParent");

After running this command, the chained script has access to the variable defined in the parent script, with the name “varNameInParent”. Not only is it going to have access to the variable, but also all of the modifications done to “varNameInParent” are visible in the parent script.

Using the ResetField() command, the child can do both - have access to all of the variables in the parent script, and also to set any variable in the parent - so that after the child is finished, the parent has had all of its variables modified.

Note that the varNameInParent variable doesn't have to exist - the child can create it in the parent space and set it to whatever value (including a number, a string, a tuple, etc.) it needs.

Hello, World! Example in Chaining

In this section, you'll see an example of chaining scripts using CSCS scripting language in a WPF C# project. The entry point of the first script to be executed is defined in the project .exe.config configuration file using the “CSCS_Init” key. Here's what we use in the example:

<add key="CSCS_Init" value="C:/cscs_wpf/scripts/demoMenu.cscs"/>

The full code of the demoMenu.cscs is shown in Listing 1.

Listing 1: demoMenu.cscs : Main Menu Script

// Create main menu window;
// tpath gets the location of the scripts folder
CreateWindow(tpath()+"demoMenuWindow.xaml");

define mainMenuDC type a;

function demoMenuWindow_OnDisplay(){
    AddMenuItem("mainMenuDC", "ScriptsDC", "Scripts");
    AddMenuItem("ScriptsDC", "demoScript1.cscs", "Run script1", "runScript");
    AddMenuItem("ScriptsDC", "exit", "Exit", "runScript");
}

// function for starting scripts from main menu
function runScript(sender, load){
    if(sender == "exit"){
        // exits the exe program
        exit;
    }
  CHAIN strTrim(tpath())+strTrim(sender);
}

Note that the first thing the script does is create a new WPF window:

CreateWindow(tpath() + "demoMenuWindow.xaml");

The full code of the demoMenuWindow.xaml file is shown in Listing 2.

Listing 2: demoMenuWindow.xaml: Main Menu Window

<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation";
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml";
xmlns:d="http://schemas.microsoft.com/expression/blend/2008";
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006";
xmlns:local="clr-namespace:WpfCSCS"
xmlns:WPF="clr-namespace:Microsoft.Web.WebView2.Wpf;
 assembly=Microsoft.Web.WebView2.Wpf"
     mc:Ignorable="d"
     Title="Main menu" Height="450" Width="800" 
         WindowStartupLocation="CenterScreen">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="auto" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <WPF:WebView2 Grid.Row="1"
                      x:Name="WebView1"
                      Margin="0,0,0,0"
                      Source="https://github.com/vassilych/cscs" />
        <Menu Grid.Row="0"
             DataContext="mainMenuDC"
             Name="mainMenuDC" />
    </Grid>
</Window>

As you can see, this is a typical XAML file used in any WPF project. Basically, it just creates a WebView based on the https://github.com/vassilych/cscs website and a simple menu.

This entry window is shown in Figure 1:

Figure 1: The entry Window after starting a sample project.
Figure 1: The entry Window after starting a sample project.

At the next step, choose “Run script1” menu entry. The demoMenu.cscs initializes the menus as follows:

function demoMenuWindow_OnDisplay() {
    AddMenuItem("mainMenuDC", "ScriptsDC", . "Scripts");
    AddMenuItem("ScriptsDC", "demoScript1.cscs", "Run script1", "runScript");
    AddMenuItem("ScriptsDC", "exit", "Exit", "runScript");
}

AddMenuItem() is an auxiliary CSCS function that adds an entry to a particular menu.

Here's the CSCS definition of the RunScript function that it uses:

function runScript(sender, load) {
    if (sender == "exit") {
        exit;
    }
// execute a script from the sender.
  CHAIN tpath()+strTrim(sender);
}

Once the user clicks “Run Script1” menu option, the demoScript1.cscs is triggered (see Listing 3).

Listing 3: demoScript1.cscs: First Script triggered from the Menu

CreateWindow(tpath()+"demoScript1.xaml");

function demoScript1_onDisplay(){
    PieChart("dougnutChart1", "init");
    PieChart("dougnutChart1", "values", 50, "label 1", 50);
    PieChart("dougnutChart1", "values", 150, "label 2", 50);
    PieChart("dougnutChart1", "values", 250, "label 3", 50);
    PieChart("dougnutChart1", "values", 350, "label 4", 50);
    PieChart("dougnutChart1", "colors", 
        { "#1C4E80", "#EA6A47", "#5acc6b", "#34d2fa" });
}

// button clicked event, starts a new script and
// sends parameters("Hello" - constant 
// and var1 - variable)
function button1@clicked(){
    var1 = " World!";
    CHAIN strTrim(tpath())+"demoScript2.cscs"
    WITH "Hello" var1;
}

// Same as function above but starts a script in
// a new process
function button2@clicked(){
    var1 = " World!(NEWRUNTIME)";
    CHAIN strTrim(tpath())+"demoScript2.cscs" 
    NEWRUNTIME WITH "Hello" var1;
}

That script creates a new window (see Listing 4). That window is shown in Figure 2.

Listing 4: demoScript1.xaml: Window of Script1

<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation";
   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml";
   xmlns:d="http://schemas.microsoft.com/expression/blend/2008";
   xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006";
      xmlns:local="clr-namespace:WpfCSCS"
      xmlns:lvc="clr-namespace:LiveChartsCore.SkiaSharpView.WPF;
      assembly=LiveChartsCore.SkiaSharpView.WPF"
      mc:Ignorable="d"
      Title="Script 1" Height="450" Width="500" 
          WindowStartupLocation="CenterScreen">
   <Grid>
       <lvc:PieChart LegendPosition="Bottom"
               Name="dougnutChart1"
               VerticalAlignment="Top"
               HorizontalAlignment="Left"
               Width="490"
               Height="289"
               FontSize="10"
               Margin="0,0,0,0" />
       <Button Name="button1"
               Content="CHAIN to script 2"
               HorizontalAlignment="Left"
               Margin="90,334,0,0"
               VerticalAlignment="Top"
               Height="60"
               Width="115" />
       <Button x:Name="button2"
               Content="CHAIN to script 2(NEWRUNTIME)"
               HorizontalAlignment="Left"
               Margin="250,334,0,0"
               VerticalAlignment="Top"
               Height="60"
               Width="210" />
   </Grid>
</Window>
Figure 2: The result of clicking Run Script1 menu
Figure 2: The result of clicking Run Script1 menu

Here we have two buttons; both of them trigger the execution of the demosScript2.cscs script (see Listing 5). The left button triggers the execution in the same process and the right one triggers the execution in a new process space.

Listing 5: demoScript2.cscs: script triggered by demoScript1.cscs

CreateWindow(tpath()+"demoScript2.xaml");

DEFINE aPointer type f; // pointer type
DEFINE var1 type i size 10; // integer variable

function demoScript2_onInit() {
    // PARAM will recieve parameters sent from
    // the previous script.
    // They will be named arg1 and arg2 in this script
    PARAM(arg1,arg2);
    // Show arguments
    MessageBox(arg1 + arg2);

    // aPointer points to var1
    aPointer -> var1;

    var1 = 100;
    MessageBox("var1 = " + var1 + ", &aPointer = " + &aPointer);
    &aPointer = 200;
    MessageBox("var1 = " + var1 + ", &aPointer = " + &aPointer);
}

function demoScript2_onDisplay(){
    Chart("LineChart", "init");
    // x labels first, with font size of 12 px
    Chart("LineChart", "labels","x", 12,
       {"1", "2", "3", "4", "5", "6", "7", "8", "9", "10"});
    Chart("LineChart", "SeparatorStep", 1);
    // set of values for the first line chart
    Chart("LineChart", "values", "line", 
       {100, 512, 265, 300, 1000, 456, 365, 111, 900, 800},"Sample data 1");
    // set of values for the second line chart
    Chart("LineChart", "values", "line",
       {30, 500, 345, 456, 567, 678, 789, 890, 500, 200}, "Sample data 2");
    // define colors of the first and the second line
    Chart("LineChart", "Color.Series", { "#0091D5", "red"}); 
}

function exitButton@clicked(){
  // exit from the script and return to the
  // previous(parent) script
  quit;
}

In any case, as you can see from Listing 5, the first thing the script does is to create a new window, demoScript2.xaml (see Listing 6).

Listing 6: demoScript2.xaml: Window of Script2

<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation";
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml";
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008";
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006";
    xmlns:local="clr-namespace:WpfCSCS"
    xmlns:lvc= "clr-namespace:LiveChartsCore.SkiaSharpView.WPF;
                assembly=LiveChartsCore.SkiaSharpView.WPF"
    mc:Ignorable="d"
    Title="Script 2" Height="450" Width="500" 
       WindowStartupLocation="CenterScreen">
    <Grid>
    <lvc:CartesianChart LegendPosition="Top"
                        x:Name="LineChart"
                        Margin="0"
                        Height="294"
                        Width="500"
                        HorizontalAlignment="Left"
                        VerticalAlignment="Top" />
        <Button Name="exitButton"
                Content="Exit"
                HorizontalAlignment="Left"
                Margin="190,324,0,0"
                VerticalAlignment="Top"
                Height="60"
                Width="115" />
    </Grid>
</Window>

The new window, created after the chaining, is shown in Figure 3.

Figure 3: The chained script (the child)
Figure 3: The chained script (the child)

Both windows (in Figure 2 and in Figure 3) show some graphics. These graphics are parts of the LiveCharts (https://v0.lvcharts.com), an MIT license package that can used as a NuGet package. You can check out how you can create cool graphics from this package in Listing 3 (Pie Chart) and Listing 5 (Normal line chart)).

Downloading Resources from the Internet

In this section, you'll see how to get the resources (all text, .cscs, and .xaml files, and any images) from the internet.

The CSCS function to access a file, located somewhere on the internet, is the following:

Download(fullFilename);

Note that the webserver where you store your files for downloading must be configured to treat .xaml and .cscs files as text files. Usually, this is already done by default for the .xaml files.

The webserver where you store files for downloading must be configured to treat .cscs files as text files.

For the .cscs files, change the MIME types on the IIS Manager page, as shown in Figure 4.

Figure 4: Changing MIME types on the server IIS Manager
Figure 4: Changing MIME types on the server IIS Manager

After setting up your webserver for downloading the .cscs fies, you can access them as follows:

newXamlFile = Download( "http://185.32.126.169/shared/winWithButtons.xaml");

You can also do the chaining, explored in the previous section, using scripts from the internet:

CHAIN Download("http://185.32.126.169/shared/script5.cscs");

This statement means that script5.cscs will be downloaded from an internet location and will be immediately started with just one line of code!

Alternatively, you can download the version of your program and then, if it's not up to date, you can download a new version of the CSCS and /or the XAML files.

Here's a simple pseudo-example how to do it in CSCS:

v = Download("http://185.32.126.169/shared/version.txt");
lines = readfile(v);
version = lines[0];
if (myVersion < version) {
    newScriptFile = Download("http://185.32.126.169/shared/script5.cscs");
    copy(newScriptFile, programScriptsDir);
    newXamlFile = Download("http://185.32.126.169/shared/win5.xaml");
    copy(newXamlFile, programXamlDir);
}

In the same way, you can download images or any other files as well.

Introduction to Pointers in Scripting

Another nice feature that we'd like to introduce in CSCS scripting language is pointers.

Pointers work and look similar to the C/C++ pointers (but not the same though: to dereference a pointer, e.g., to access the value of a variable that it points to, use “&” instead of "*"). An important difference is that you don't have to explicitly allocate and delete the memory to which your pointer points.

After a pointer is defined and initialized to point to a variable, as soon as you change the value of the pointer, the value of the variable to which it points will be changed as well. Also, if you change the value of a variable, the pointer value will also be changed.

Here's an example from Listing 5:

// A pointer must be first defined with a DEFINE keyword
DEFINE aPointer type f;

aPointer -> var1;
// aPointer points now to a variable var1

var1 = 100;
// Now the value of &aPointer will be 100
&aPointer = 200;
// Now the value of var1 will be 200

Wrapping Up

In this article, you saw how you can use chains in a scripting language. It's possible to use script chaining to any level, i.e., you can script from the MainMenu script to script1 then from script1 to script2, and so on.

When you quit any script (using the Quit command) the script execution proceeds from the next line after the chain command in the previous (parent) script. Such an implementation can enable even huge applications to be split into many isolated scripts. This is a great advantage for developers who need to independently develop scripts without conflicting with scripts from other developers. As you can see, updating any application may be very easy with simple change of a single script and not of the whole application.

We're looking forward to your feedback, specifically, your experience in scripting with chains and downloading scripts from the internet. Also, we'll be excited to hear what other features in CSCS you would like to see.