In the July/August 2016 issue of CODE Magazine, I published an article on how to create your own scripting language and implement it in C#. I called this language CSCS: Customized Scripting in C#. But I didn’t mention any practical usage of such a scripting language at the time, even though there were some unexpected applications of it, e.g., in game hacking.

Since then, Xamarin was acquired by Microsoft and at first Xamarin Studio Community Edition, and later Visual Studio 2017 Community Edition which contained Xamarin, were released for Windows and macOS. Now individual developers and even small companies can develop iOS and Android apps in C# using Xamarin for free (in addition to the Windows Phone apps that they were already able to develop in C#).

There’s a choice of using either Xamarin.Forms (in case users don’t require platform-specific functionality and are comfortable with using XAML) or platform-specific Xamarin.iOS and Xamarin.Android to write apps with any features that they can get as if they were using iOS Swift/Objective-C or Android Java development.

"I want to be buried with a mobile phone, just in case I'm not dead." -- Amanda Holden

The first step of shortening time-to-market if you develop cross-platform apps, is to use Xamarin. And this is where I saw the next step and an application for the CSCS scripting language: I can extend the CSCS scripting language for mobile development, so creating and placing different widgets will be just one-liners. The scripting language doesn’t have to be used exclusively but can be combined with the C# code.

The most time-consuming part, at least for me, has always been the layout, which is implemented differently on iOS and Android. For Android you usually use XML, and for iOS, there’s Auto Layout, a constraint-based layout system. Both systems permit having conflicts—definitions conflicting with each other—that are solved at runtime (not always obviously and depending on the screen size).

The advantages of using customized scripting in C# for mobile development are:

  • The same code is used to create and place a widget on both iOS and Android. Windows Phone can be added easily as well.
  • A simpler layout system works exactly the same for Android and iOS and there’s no possibility of conflicts.
  • You can call the native C# code from inside a CSCS script. You can avoid delays due to marshalling by pre-compilation. You’ll see how to do this in this article.
  • The end-result is still a native app.
  • Debugging time is quicker with CSCS than with C#. When making some modifications in the script, there’s no recompilation of the source code. The changes in the XML/Storyboard aren’t necessary anymore for changing the layout (and it does take some time recompiling the layout changes unless you really have a "crème de la crème" development computer).
  • Because of the proximity of CSCS to the C# code, you can easily modify the existing CSCS functionality or add a new function. For example, it’s very easy to add a new widget, and you’ll see some examples in this article.
  • The differences from Xamarin.Forms are that you don’t need to know XAML, and there fewer lines of code. You can also use platform-specific features more easily.
  • People with little or no programming experience can easily create the UI using CSCS scripting.

All of the code in this article is available for free at https://github.com/vassilych/mobile. It’s also associated with this article on the CODE Magazine website.

To use CSCS for mobile development, you need to download any version of Visual Studio 2017 and enable the Xamarin option there. Then you can use my sample project at https://github.com/vassilych/mobile or the CODE Magazine website, which contains the CSCS compiler in the shared C# code section, and play around with the script file script.cscs there.

A "Hello, World!" in CSCS

Let’s start with our "Hello, World!" program for mobile development. Check out the CSCS script in Listing 1.

The result of executing this script is shown in Figure 1.

Figure 1: Running the "Hello, World!" script on iPhone and Android

Figure 2 shows fragments of the screen on iPhone and Android after typing "Hi there" in the text field and clicking on the "Change me" button.

Figure 2: After clicking on the "Change me" button

As you can see, I added a background and three tabs on the fly. In addition, I added the following widgets: a Label (UILabel in iOS and TextView in Android), a Button (UIButton in iOS and Button in Android), and a TextEdit (UITextField in iOS and EditText in Android).

Let’s briefly examine the contents of the "Hello, World!" script in Listing 1.

The AddTab function creates a tab application on the fly and adds the first tab to the app. Its signature is the following:

AddTab(TabName, ActiveIcon, <InactiveIcon>);

The ActiveIcon is used when the tab is selected, and the InactiveIcon is used otherwise. The InactiveIcon is optional: if it’s not provided, the ActiveIcon is used.

SetBackground(image) is used to set the background of the app. Note that before using this function, the image file must be first added to the Resources folder of the Xamarin.Android and Xamarin.iOS projects. See the accompanying source code download for details.

The next section explains how you add different widgets to the app.

Layout

In order to add a widget, you need to define the application layout. In this section, you’re going to see how the layout is organized in CSCS. There’s no familiar drag-and-drop functionality but in exchange, there’s more control about where you want your widget placed.

Layout in CSCS

For the layout definition, I used a mixture of the iOS and Android approaches. From iOS and the Auto Layout, I applied a rather obvious concept: For the unique widget location, I need to define the relative widget placement horizontally and vertically, and also the widget size. That’s it! I’m not sure why this can be defined multiple times, and inconsistently, in both iOS and Android, leading to conflicts; these conflicts may be resolved with unexpected results at runtime.

For the implementation, I took an approach similar to the concept of the RelativeLayout in Android, but it’s not possible in CSCS to have multiple definitions for a widget (because they may contradict each other). For instance, in Android, you can apply the method ApplyRule() an unlimited number of times when placing a widget.

To create a new widget, first you need to create its location. The location has the information about the widget’s horizontal and vertical placement. It won’t have the widget’s size: That’s provided later, when you add the widget itself. The reason is that potentially the same location can be used by various widgets of differing sizes (for example, one of them can be hidden, and another can be shown, depending on some other runtime conditions).

Also, the location will have an optional parameter of the view (or layout, in the case of Android) for where to place the widget. If the location isn’t provided, the widget is placed in the root view (or in the root Android layout, which happens to be a RelativeLayout).

To create a widget location, the syntax is the following:

Location = GetLocation(horizontalReference,
                       horizontalRelation,
                       verticalReference,
                       verticalRelation,
            <additionalHorizontalMargin>,
            <additionalVerticalMargin>,
            <View>);

The horizontal and vertical references are either other widgets (or views) or the root window (in this case, it’s denoted by the "ROOT" string). Most of the horizontal and vertical relation parameters are borrowed from the Android RelativeLayout.LayoutParams class. One of the differences is the Center parameter, which is only used for placement inside of the parent in Android, but you use it depending on the context: If the reference widget isn’t the root, the new widget is centered relative to the reference widget. You’ll see a few examples of placing widgets in different places on the screen and relative to each other later on.

The additional horizontal and vertical margins are, by default, zero. In case they’re provided, they indicate the additional margin for moving the widget horizontally (the direction goes from left to right) and vertically (the direction goes from top to bottom). For example, a horizontal margin of -20 means moving the widget left 20 pixels; a vertical margin of 30 means moving the widget down 30 pixels. This is similar to the TranslationX and TranslationY parameters in Android.

Let’s see, for example:

locLeft = GetLocation("ROOT", "LEFT",
                      "ROOT", "CENTER", 20, 0);

The first argument, "ROOT", refers to the reference widget, which is the main window, and the second argument specifies the horizontal placement; in other words, the widget will be placed horizontally on the left. The third and fourth argument specify the vertical placement at the vertical center of the screen. The fifth parameter, 20, indicates that the widget should be moved 20 pixels to the right.

Let’s see another example:

locAbove = GetLocation(buttonChangeme,
                       "ALIGN_LEFT",
                       buttonChangeme, "TOP");

This horizontally aligns the left corner of buttonChangeme with the left corner of the new widget. Vertically, it places the new widget on top of the buttonChangeme.

After creating a location, you must use it to create a widget:

AddButton(locLeft, "buttonChangeme",
          "Change me", 260, 80);

This creates a button at the location specified before with the specific width and height in pixels. The button has the title string "Change me".

The general syntax of a CSCS command to create a widget is the following:

AddWidget(widgetType, location, widgetName,
          initializationString, width, height);

There are shortcuts for some of the widget types, such as a View, a Button, a Label, etc. The whole list of the functions currently available for the mobile development is shown in Tables 1, 2, and 3. This list is constantly growing, so check the source code at GitHub for up-to-date developments.

The initialization string is context-sensitive. For a label and a button, it sets its text (or its "title" in Android terms). For the TextEdit, it sets a hint (or its "placeholder" in iOS terms). An example of this hint can be seen in Listing 1. The initialization string can also provide the image file name for the ImageView and the initialization parameters for some widgets, like Switch and Slider.

I’ve used pixels in this layout; in a future article, you’re going to see how to use DPS (density-independent pixels). Also, even though I’ve given absolute sizes here, it’s easy to make adjustments and have widget sizes and placement depend on the display size because you can use the DisplayWidth and DisplayHeight functions to find out the display size in pixels and use this information to create a multiplication factor for coordinates and sizes.

That’s it, about the layout in CSCS. Now, using the location defined in this section, you can create widgets anywhere on the screen and position them relative to each other.

Example of the Layout in CSCS

Let’s see how to implement the layout shown in Figure 3 in CSCS.

Figure 3: An example of a layout on iOS and Android

The example in Figure 3 uses many different widgets. Check its implementation in Listing 2.

Implementation of the Layout in C#

Now let’s see the implementation of the layout in the C# code so you can easily modify it to better fit your needs.

First, I’ll do a very quick elevator pitch of how CSCS scripting language works and how you can add new functions to it. For a longer and a much more detailed explanation, take a look at the article that I published in the July-August 2016 issue of CODE Magazine (http://www.codemag.com/article/1607081).

The CSCS language is based on the Split-and-Merge algorithm, and is implemented in C#. At first, you collect a series of tokens and then merge them one by one. As soon as you encounter an expression in parentheses or a function, you apply the whole Split-and-Merge algorithm to that expression or function. At the second stage of the algorithm, merging, you only have simple expressions, like numbers or strings. And that second merging stage takes into account the priorities of the operators. The first step doesn’t take priorities into account.

The CSCS is a functional language where everything turns around functions. Let’s see how to add a new function to CSCS. I’ll start with simple ones: the functions returning the device’s width and height.

The first step is to write a new class deriving from the ParserFunction class, and to override its Evaluate() method.

Here’s an example for iOS:

public class GadgetSizeFunction : ParserFunction
{
        bool m_needWidth;
        public GadgetSizeFunction(
                        bool needWidth = true )
  {
    m_needWidth = needWidth;
  }
        protected override Variable Evaluate(
                           ParsingScript script)
  {
          var nb = UIScreen .MainScreen.NativeBounds;
          return new Variable (m_needWidth ?
                          nb . Width : nb .Height);
  }
}

As you can see, the function just gets the screen bounds containing both the width and the height, and returns either the width, or the height, depending on the initialization parameter.

Here is the same function implementation for Android:

public class GadgetSizeFunction : ParserFunction
{
        bool m_needWidth; public GadgetSizeFunction( bool needWidth= true )
  {
    m_needWidth = needWidth; }
        protected override Variable Evaluate(
                           ParsingScript script)
        {
          DisplayMetrics dm = new DisplayMetrics ();
          MainActivity .TheView.WindowManager .
                            DefaultDisplay.GetMetrics( dm );
          return new Variable (m_needWidth ?
                       dm .W idthPixels : dm .HeightPixels);
        }
}

The second step, after implementing the functions in C#, is to register them with the parser. This registration is now the same for iOS and Android, and it’s the following:

ParserFunction .RegisterFunction( "DisplayWidth" ,
                                 new GadgetSizeFunction ( true )); ParserFunction .RegisterFunction( "DisplayHeight" ,
                                 new GadgetSizeFunction ( false ));

This means that as soon as the parser finds the DisplayWidth token, the Evaluate() method of the GadgetSizeFunction object initialized with m_needWidth = true is called and as soon as the parser finds the DisplayHeight token, the Evaluate() method of the GadgetSizeFunction object initialized with m_needWidth = false is called.

That’s it! In my previous article, there was an additional step to register any possible translations supplied in a configuration file, but I’ll skip it here for brevity. This is how easy it is to add a new functionality to the CSCS scripting language: Implement an Evaluate method in a new class deriving from the ParserFunction class and then register it with the parser!

The Evaluate method returns an object of the Variable class. This is a generic object used in CSCS scripting. For mobile development, you need a more customized object.

The UIVariable class derives from the variable class and is a wrapper over all of the widgets and locations. A fragment of this class is shown in Listing 3. The iOSVariable and DroidVariable are concrete implementations of the UIVariable for the iOS and Android correspondingly. They are shown in Listing 4 and Listing 5.

The first step in a layout operation in CSCS is to get a location for the widget. Listing 6 shows the implementation of getting a location for Android. The implementation for iOS is very similar.

Once you have a location, you can place the widget there. The code for Android is a bit more straightforward (because the placement is done on a RelativeLayout), so I’ll show the code for placing the iOS widgets. The code here is not complete and I encourage you to check out the details in the accompanying source code.

There’s one main function for adding a widget to the screen, and which is used for all types of widgets, AddWidgetFunction. There are a few exceptions to this, such as pop-up dialogs, like an Alert Dialog or a Toast, that are implemented differently. In order to use the same function for different widgets, register functions that add widgets, as follows:

ParserFunction .RegisterFunction( "AddButton" ,
                 new AddWidgetFunction ( "Button" ));
ParserFunction .RegisterFunction( "AddLabel" ,
                 new AddWidgetFunction ( "Label" )); ParserFunction .RegisterFunction( "AddTextEdit" ,
                 new AddWidgetFunction ( "TextEdit" ));

Continue on in that fashion. Listing 7 shows the implementation of the AddWidgetFunction class on iOS.

The function responsible for the translation of the ALIGN_LEFT, BOTTOM, CENTER, etc. parameters to the concepts that iOS and Android understand is called String2Position. This function is shown in Listing 8. Depending on the layout parameters, it returns a coordinate of the point where you place the widget.

Calling the Native C# Functions from the CSCS Code

It may be more convenient to use already-existing C# code from the CSCS code and get the results back into CSCS. Even though any feature of C# can be implemented in CSCS, this may require some time, and the C# code may be already available.

Let’s see an example of how the C# code receives an argument from CSCS, gets the current time, and returns a string back to CSCS. Then this string is shown as a button title. Here’s the CSCS implementation:

clicks = 0;
function click(sender, arg) {
        clicks++;
        title = CallNative("ProcessClick", "arg",
                           clicks); SetText(sender, title);
}
loc1 = GetLocation("ROOT", "LEFT",
                         "ROOT", "BOTTOM");
AddButton(loc1, "but 1", "Left", 220, 80 );
AddAction(but 1, "click");
loc2 = GetLocation("but 1", "RIGHT",
                         "ROOT", "BOTTOM");
AddButton(loc2, "but 2", "Right", 220, 80 );
AddAction(but 2, "click");

The result of executing this script and clicking a few times on each button is shown in Figure 4.

Figure 4: Getting the button title from the C# code

Let’s see how it’s implemented. Listing 9 contains the C# implementation of invoking a method with one string parameter that returns a string. The implementation for methods with a different number of parameters or with different types of arguments, is analogous.

You can see that you cache the compiled function to be executed. The effect of caching is quite noticeable visually: The first click takes a longer time than the consequent ones, especially on Android. You can also pre-cache commonly called functions at the start-up phase.

This InvokeCall() method is called from a Parser function, which is the first point of contact in the C# code with CSCS. The iOS and Android implementation is the same and it’s shown in Listing 10.

To glue everything together, you need to register the InvokeNativeFunction with the parser:

ParserFunction.RegisterFunction("CallNative",
                     new InvokeNativeFunction());

What’s left is the actual implementation of the method being called from the CSCS code. As you saw in Listing 10, the method is implemented in the Statics class that I added in the shared project area. In the Statics class, you can implement all of the methods called from C# and the same code is called from both iOS and Android:

public class Statics
{
        public static string ProcessClick( string arg)
  {
          var now = DateTime .Now.ToString( "T" );
          return "Clicks: " + arg + " \n " + now;
  }
}

Very similarly, you can also implement calling functions with a different number of arguments or with different argument types.

Where the CSCS Script Execution is Triggered in C# Code

One important question is: Where exactly in the flow do you execute the CSCS script?

For iOS, the answer is easier than for Androi.: In iOS, it can be done just at the end of the AppDelegate.FinishedLaunching() method.

For Android, the first attempt to run the CSCS script at the end of the MainActivity.OnCreate() method failed. The reason was that the global layout has not been completely initialized in the OnCreate() method.

The trick is to register a listener that will be triggered as soon as the global layout is initialized:

protected override void OnCreate( Bundle
                              savedInstanceState)
{
        base .OnCreate(savedInstanceState);
        // some other stuff
        ViewTreeObserver ob =
                 relativelayou t.ViewTreeObserver;
        o b .AddOnGlobalLayoutListener(
                 new LayoutListener ()); }

In the listener code, first you unregister the listener (otherwise it will be triggered on every change to the layout) and then run the CSCS script:

public class LayoutListener : Java.Lang. Object ,
                   ViewTreeObserver . IOnGlobalLayoutListener
{
        public void OnGlobalLayout()
  {
          var o b =
                                      MainActivity .TheLayout.ViewTreeObserver;
                o b .RemoveOnGlobalLayoutListener( this );
          MainActivity .RunScript();
  }
}

In the RunScript method, you register all of the Parser functions right before the parser execution is started. Here’s a fragment from the RunScript method:

public static void RunScript()
{
        ParserFunction .RegisterFunction( "_IOS_" , new
                 CheckOSFunction ( CheckOSFunction . OS .IOS));
        // ... Registration of all other functions
  // with the Parser here ...
        string script = "" ;
        AssetManager assets = TheView.Assets;
        using ( StreamReader sr = new StreamReader (
                          assets.Open( "script.cscs" ))) {
    script = sr.ReadToEnd();
  }
        Interpreter .Instance.Process(script);
}

Widgets, Widgets, Widgets

Once you know how to build the layout, another important part of the CSCS scripting language is to be able to use as many widgets as possible. You can see what’s currently implemented in Table 1, 2, and 3, but I’m sure that by the time you read this article, many more widgets will be implemented. Just check the accompanying source-code download at the GitHub link or go to the CODE Magazine website and download it from the article’s link.

As a final example, let’s see how the widgets shown in Figure 5 are implemented.

Figure 5: Various Widgets on iOS and Android

There’s a TypePicker on the top: As soon as you change the value there by moving the picker’s wheel, a different background image is shown.

Also, there is a Button, an ImageView, a Switcher, and a Slider. As soon as you click the Button, or the Switcher, or change the value of the Slider, the other widgets also change.

Listing 11 shows how it’s implemented in CSCS.

Finally, Tables 1, 2, and 3 contain all currently available functions for mobile development in CSCS.

I’m sure by the time you read this, there’ll be many more functions available: Don’t forget to check out my github.com page in the links section.

Wrapping Up

Using the CSCS scripting language, you can write cross-platform applications that run natively on any device.

You can proceed as follows: Download the sample project in the accompanying source code download section and start playing with the script.cscs file there.

The features presented in this article constitute a small fraction of what you can do with CSCS. I plan to expose more advanced topics in the next articles. Some of these future topics are: advanced controls and widgets, in-app purchases, text-to-speech, voice recognition, localization, easy ways of having different layouts in different device orientations, and adding advertising content (like Google AdMob), just to name a few. Everything can be made in CSCS.

But more importantly, it’s relatively straightforward to modify the existing functionality of CSCS, or to add new functions.

To add a new function to CSCS, you need to create a class deriving from the ParserFunction class and override its Evaluate method for all of the platforms where you want your script run. Then, you register the new function with the parser, as you’ve seen in this article.

Another advantage of CSCS is that not all of the code must be written in it, but it can be combined with other code written in C#. For example, you can use CSCS for the pure GUI development. You also saw how you can call a C# function from the CSCS code and virtually eliminate any overhead due to the marshalling by pre-compiling the Reflection functions.

I’m looking forward to getting any feedback you have for programming in CSCS for the mobile development.