vassilych / cscs

CSCS: Customized Scripting in C#
MIT License
171 stars 49 forks source link

CSCS (Customized Scripting in C#) is a scripting language framework, which is very easy to integrate into any C# project and adjust according to your needs. Basically, the concept of CSCS is not only a language, but also a framework that you can use to create your own language. Since the compiler will be inside of your project, you can do whatever you want with the language: add new features, modify existing ones, etc. How to do that and the CSCS Framework itself, have been described in:

The source code for Mobile App development is here. The usage of CSCS in Mobile App development has been described in:

The usage of CSCS in Unity has been described in:

You can also pre-compile CSCS code to speed-up exection. This has been described in:

You can use Visual Studio Code as an IDE for developing and debugging CSCS code. The Visual Studio Code Extension to debug CSCS code is available here.

Contributions

Special thanks for contributions from Dan Spear, especially using Reflection in order to call C# functionality directly from CSCS. Examples coming up soon.

Description of CSCS


What follows is the description of the CSCS functions. The usage of CSCS has been tested on Windows, Mac, iOS, Android, and Unity. Not all of the functions are supported on all of platforms. First, we see the core fuctions, that are supported everywhere, and then the extended functions, dealing more with OS internals and therefore not supported on all the platforms.

CSCS Control Flow Functions

CSCS Statement Description
include (pathToFile) Includes another scripting file, e.g. include("functions.cscs");
function funcName (param1, param2=value2, param3=value3) { statements; } Declares a custom function with 0 or more parameters. Parameters can optionally have default values. When calling a function, parameters can be specified either implicitly (e.g. sine(10)), or explicitly (e.g. func(param2=value2, param1=value1)).
cfunction funcName (param1, param2=value2, param3=value3) { statements; } Declares a custom precomplied function with 0 or more parameters. Doesn't work on iOS and Android.
return or return variable; Finishes execution of a function and optionally can return a value.
while (condition) { statements; } Execute loop as long as the condition is true.
Curly brackets are mandatory.
for (init; condition; step) { statements; } A canonic for loop, e.g. for (i = 0; i < 10; ++i).
Curly brackets are mandatory.
for (item : listOfValues) { statements; } Executes loop for each elemеnt of listOfValues.
Curly brackets are mandatory.
break Breaks out of a loop.
continue Forces the next iteration of the loop.
if (condition) { statements; }
elif (condition) { statements; }
else { statements; }
If-else control flow statements.
Curly brackets are mandatory.
try { statements; }
catch(exceptionString) { statements; }
Try and catch control flow.
Curly brackets are mandatory.
throw string; Throws an exception, e.g. throw "value must be positive";
true Represents a boolean value of true. Equivalent to number 1.
false Represents a boolean value of false. Equivalent to number 0.


Control Flow Example

include("functions.cscs");
i = 0;
for (i = 0; i < 13; i++) {
  b += (i*4 - 1);
  if ( i == 3) {
    break;
  } else {
    continue;
  }
  print("this is never reached");
}

a = 23; b = 22;
cond = "na";
if (a < b) {
  if (b < 15) {
    cond = "cond1";
  }
  elif  (b < 50) {
    cond = "cond2";
  }
}
elif (a >= 25) {
  cond = "cond3";
}
else {
  cond = "cond4";
}

function myp(par1, par2, par3 = 100) {
  return par1 + par2 + par3;
}


Functions and Try/Catch Example

function myp(par1, par2, par3 = 100) {
  return par1 + par2 + par3;
}

z = myp(par2=20, par1=70); // z = 190

try {
  z = myp(par2=20);
  print("Error. Missing Exception: Function [myp] arguments mismatch: 3 declared, 1 supplied.");
} catch(exc) {
  print("OK. Caught: " + exc);
}
try {
  z = myp(par2=20, par3=70);
  print("Error. Missing Exception: No argument [par1] given for function [myp].");
} catch(exc) {
  print("OK. Caught: " + exc);
}


CSCS Object-Oriented Functions and Named Properties

CSCS Function Description
class className : Class1, Class2, ... { } A definition of a new class. It can optionally inherit from one or more classes. Inside of a class definition you can have constructors, functions, and variable definitions. You access these variables and functions using the dot notation (all of them are public).
new className(param1, param2, ...) Creates and returns an instance (object) of class className. There can be a zero or more parameters passed to the class constructor (depending on the class constructor parameter definitions).
variable.Properties Returns a list of all properties that this variable implements. For each of these properties is legal to call variable.property. Each variable implements at least the following properties: Size, String, Type, and Properties.
variable.Size Returns either a number of elements in an array if variable is of type ARRAY or a number of characters in a string representation of this variable.
variable.Type Returns this variable's type (e.g. NONE, STRING, NUMBER, ARRAY, OBJECT).
variable.EmptyOrWhite Returns true if and only if the underlying string is empty or contains only white characters.
variable.Add(value) Adds passed value to the underlying array or to the internal string representation.
variable.Remove(item) If variable is a map, removes an entry with the passed key name. Otherwise, if variable is an array, removes all entries equal to the passed item. Returns number of entries removed.
variable.RemoveAt(index) If variable is an array, removes the specified entry at this index. Returns number of entries removed.
variable.At(index) If variable is an array, returns array element at this index. Otherwise returns a character at this index.
variable.Contains(value) If variable is a list, returns whether it contains this value. Otherwise converts current variable to a string and returns whether it contains passed value.
variable.StartsWith(value) Whether the variable, converted to a string, starts with this value.
variable.EndsWith(value) Whether the variable, converted to a string, ends with this value.
variable.Replace(oldValue, newValue) Replaces oldValue with the newValue.
variable.ReplaceAndTrim(oldValue1, newValue1, oldValue2, newValue2, ... ) Replaces all oldValueX with the corresponding newValueX. Returns string without leading or trailing white spaces.
variable.IndexOf(value, from=0) Returns index of the value in the variable (-1 if not found).
variable.Join(sep=" ") Converts a list to a string, based on the string separation token.
variable.First Returns the first character of this string or first element of the list.
variable.Last Returns the last character of this string or last element of the list.
variable.Keys If the underlying variable is a dictionary, returns all the dictionary keys.
variable.Substring(value, from, size) Returns a substring of a given string.
variable.Split(sep=" ") Returns a new list based on the string separation token.
variable.Trim() Returns a new variable without leading or trailing white spaces.
variable.Lower() Returns a new variable converted to the lower case.
variable.Upper() Returns a new variable converted to the upper case.
variable.Sort() Sorts the underlying array.
variable.Reverse() Reverses the contents of the underlying array or string.
GetProperty (objectName, propertyName) Returns variable.propertyName.
GetPropertyStrings (objectName) Same as calling variable.properties.
SetProperty (objectName, propertyName, propertyValue) Same as variable.propertyName = propertyValue.


Object-Oriented Example with Multiple Inheritance

class Stuff1 {
  x = 2;
  Stuff1(a) {
    x = a;
  } 
  function addStuff1(n) {
    return n + x;
  }
}

class Stuff2 {
  y = 3;
  Stuff2(b) {
    y = b;
  } 
  function addStuff2(n) {
    return n + y;
  }
}

class CoolStuff : Stuff1, Stuff2 {
  z = 3;
  CoolStuff(a, b, c) {
    x = a;
    y = b;
    z = c;
  } 
  function addCoolStuff() {
    return x + addStuff2(z);
  }
}

addition = 100;
obj1 = new Stuff1(10);
print(obj1.x); // prints 10
print(obj1.addStuff1(addition); // prints 110

obj2 = new Stuff2(20);
print(obj2.y); // prints 20 
print(obj2.addStuff2(addition)); // prints 120

newObj = new CoolStuff(11, 13, 17);
print(newObj.addCoolStuff()); // prints 41
print(newObj.addStuff1(addition)); // prints 111
print(newObj.addStuff2(addition)); // prints 113

Object-Oriented Example with a C# Compiled Object

ct = new CompiledTest();
ct.NaMe="Lala";
print(ct.name); // prints "Lala": properties are case-insensitive

ct.Extra = "New property";
props = ct.properties;
print(props.contains("Extra")); // prints 1 (true)

CSCS Math Functions

CSCS Function Description
Abs (value) Returns absolute value.
Acos (value) Returns arccosine function.
Asin (value) Returns arcsine function.
Ceil (value) Returns the smallest integral value which is greater than or equal to the specified decimal value.
Cos (value) Cosine function.
Exp (value) Returns the constant e (2.718281828...) to the power of the specified value.
Floor (value) Returns the largest integral value less than or equal to the specified decimal value.
GetRandom (limit, numberOfRandoms=1) If numberOfRandoms = 1, returns a pseudorandom variable between 0 and limit. Otherwise returns a list of numberOfRandoms integers, where each element is a pseudorandom number between 0 and limit. If limit >= numberOfRandoms, each number will be present at most once.
Log (base, power) Returns the natural logarithm of a specified number.
Pi Returns the constant pi (3.14159265358979...)
Pow (base, power) Raises the base to the specified power.
Round (number, digits=0) Rounds a number according to the specified number of digits.
Sin (value) Sine function.
Sqrt (number) Returns the squared root of the specified number.


CSCS Variable and Array Functions

CSCS Function Description
Add (variable, value, index = -1) Appends value to the current variable array. If index is greater or equal to zero, inserts it at the index.
AddVariableToHash (variable, value, hashKey) Appends a value to the list of values of a given hash key.
AddAllToHash (variable, values, startFrom, hashKey, sep = "\t") Adds all of the values in values list to the hash map variable. E.g. AddAllToHash("categories", lines, startWords, "all");
Contains (variable, value) Checks if the current variable contains another variable. Makes sense only if curent variable is an array.
DeepCopy (variable) Makes a deep copy of the passed object, assigning new memory to all of its array members.
DefineLocal (variable, value="") Defines a variable in local scope. Makes sense only if a global variable with this name already exists (without this function, a global variable will be used and modified).
FindIndex (variable, value) Looks for the value in the specified variable array and returns its index if found, or -1 otherwise.
GetColumn (variable, column, fromRow=0) Goes over all the rows of the variable array starting from the specified row and returns a specified column.
GetKeys (variable) If the underlying variable is a dictionary, returns all the dictionary keys.
Remove (variable, value) Removes specified value from the variable array. Returns true on success and false otherwise.
RemoveAt (variable, index) Removes a value from the variable array at specified index. Returns true on success and false otherwise.
Size (variable) Returns number of elements in a variable array or the length of the string (same as variable.Size).
Type (variableName) Returns type of the passed variable (same as variable.Type).

Array Example

a[1]=1; a[2]=2;
c=a[1]+a[2];

a[1][2]=22;
a[5][3]=15;
a[1][2]-=100;
a[5][3]+=100;

print(a[5][2]);

a[1][2]++;
print(a[1][2]);
print(a[5][3]++);
print(++a[5][3]);
print(--a[5][3]);
print(a[5][3]--);

b[5][3][5][3]=15;
print(++b[5][3][5][3]);

x["bla"]["blu"]=113;
x["bla"]["blu"]++;
x["blabla"]["blablu"]=126;
--x["blabla"]["blablu"];


CSCS Conversion Functions

CSCS Function Description
Bool (variable) Converts a variable to a Boolean value.
Decimal (variable) Converts a variable to a decimal value.
Double (variable) Converts a variable to a double value.
Int (variable) Converts a variable to an integer value.
String (variable) Converts a variable to a string value.


CSCS String Functions

CSCS Function Description
Size (variableName) Returns the length of the string (for arrays returns the number of elements in an array).
StrBetween (string, from, to) Returns a substring with characters between substrings from and to.
StrBetweenAny (string, from, to) Returns a substring with characters between any of the characters in the from string and any of the characters in the to string.
StrContains (string, argument, case=case) Returns whether a string contains a specified substring. The case parameter can be either "case" (default) or "nocase".
StrEndsWith (string, argument, case=case) Returns whether a string ends with a specified substring.
StrEqual (string, argument, case=case) Returns whether a string is equal to a specified string.
StrIndexOf (string, substring, case=case) Searches for index of a specified substring in a string. Returns -1 if substring is not found.
StrLower (string) Returns string in lower case.
StrReplace (string, src, dst) Replaces all occurunces of src with dst in string, returning a new string.
StrStartsWith (string, argument, case=case) Returns whether a string starts with a specified substring.
StrTrim (string) Removes all leading and trailing white characters (tabs, spaces, etc.), returning a new string.
StrUpper (string) Returns string in upper case.
Substring (string, from=0, length=StringLength) Returns a substring of specified string starting from a specified index and of specified length.
Tokenize (string, separator="\t", option="") Converts string to a list of tokens based on the specified token separator. If option="prev", will convert all empty tokens to their previous token values.
TokenizeLines (newVariableName, variableWithLines, fromLine=0, separator="\t") Converts a list of strings in variableWithLines to the list of tokens based on the specified token separator. Adds the result to the new variable newVariableName.


Measuring Execution Time, Throwing Exceptions, and String Manipulation Examples

cycles = 1000; i = 0;
start = PsTime();
while ( i++ < cycles) {
    str = " la la ";
    str = StrTrim(str);
    str = StrReplace(str, "la", "lu");
    if (str != "lu lu") {    
      throw "Wrong result: [" + str + "] instead of [lu lu]";
    }
}
end = PsTime();
print("Total CPU time of", cycles, "loops:", end-start, "ms.");
// Example output: Total CPU time of 1000 loops: 968.75 ms.



CSCS Debugger

CSCS Function Description
StartDebugger (port=13337) Starts running a debugger server on a specified port (to accept connections from Visual Studio Code).
StopDebugger () Stops running a debugger server.


CSCS Core Miscellaneous Functions

CSCS Function Description
Env (variableName) Returns the value of the specified environment variable.
GetVariableFromJSON (jsonText) Parses a JSON string into the CSCS Array-Dictionary. See examples below.
Lock { statements; } Uses a global lock object to lock the execution of code in curly braces.
Now (format="HH:mm:ss.fff") Returns current date and time according to the specified format.
Print (var1="", var2="", ...) Prints specified parameters, converting them all to strings.
PsTime Returns current process CPU time. Used for measuring the script execution time.
SetEnv (variableName, value) Sets value of the specified environment variable.
Show (funcName) Prints contents of a specified CSCS function.
Singleton (code) Creates a singleton Variable. The code is executed only once. See an example below.
Signal () Signals waiting threads.
Sleep (millisecs) Sleeps specified number of milliseconds.
Thread (functionName) OR { statements; } Starts a new thread. The thread will either execute a specified CSCS function or all the statements between the curly brackets.
ThreadId () Returns current thread Id.
Wait () Waits for a signal.
WebRequest (method, URL, load, trackingId, OnSuccess, OnFailure); Submits a WebRequest to a given URL. The method can be any of GET, PUT, POST, DELETE, TRACE, OPTIONS. Either OnSuccess or OnFailure function is called when completed, with trackingID passed there as the first parameter. See an example below.


Singleton Example

function CreateArray(size, initValue = 0) {
    result = {};
    for (i = 0; i < size; i++) {
        result[i] = initValue;
    }
    return result;
}

pattern = "CreateArray(5, 'Test')";
uniqueArray = Singleton(pattern);
uniqueArray[2] = "Extra";
arr = Singleton(pattern);
print("array=", arr); // {Test, Test, Extra, Test, Test}


JSON and WebRequest Examples

jsonString = '{ "eins" : 1, "zwei" : 2, "drei": "dreiString", "vier": 4.9, "mehr" : { "uno": "uno in spanish" },
                "arrayValue" : [ "une", "deux" ] }';
a = GetVariableFromJSON(jsonString);
print(a.size);        // prints 6
print(a["vier"]);     // prints 4.9;
mehr = a["mehr"]; 
print(mehr["uno"]);   // prints "uno in spanish"
arrayValue = a["arrayValue"];
print(arrayValue[0]); // prints  "une";
print(arrayValue[1]); // prints  "deux);

// Testing a WebRequest:
place      = "8001,ch";
units      = "&units=metric";
key        = "5a548a234f9a28212d0e4b18a96e7a51";
weatherURL = "https://api.openweathermap.org/data/2.5/weather?zip=" + place + units + "&APPID=" + key;

function OnSuccess( object, errorCode, text )
{
    // text='{"coord":{"lon":8.54,"lat":47.37},"weather":[{"id":802,"main":"Clouds","description":"scattered clouds","icon":"03n"}],"base":"stations","main":{"temp":9.92,"pressure":1015,"humidity":87,"temp_min":7.78,"temp_max":12.22},"visibility":10000,"wind":{"speed":1.5,"deg":320},"clouds":{"all":40},"dt":1559861701,"sys":{"type":1,"id":6941,"message":0.0094,"country":"CH","sunrise":1559878231,"sunset":1559935141},"timezone":7200,"id":180002468,"name":"Zurich","cod":200}'

    jsonFromText = GetVariableFromJSON( text );
    main = jsonFromText["main"];
    city = jsonFromText["name"];
    wind = jsonFromText["wind"];
    sys  = jsonFromText["sys"];
    country = sys["country"];
    print("Temperature in", city, country, main["temp"], "tempMin:", main["temp_min"], "tempMax:", main["temp_max"], 
          "humidity:", main["humidity"], "pressure:", main["pressure"], "wind:", wind["speed"]);
    Test(city, "Zurich");
    Test(country, "CH");
}
function OnFailure( object, errorCode, text )
{
    print( "Failure " + errorCode + ": " + text );
}
WebRequest( "GET", weatherURL, "", "", "OnSuccess", "OnFailure" );


All of the functions above are supported on all devices. But there are also a few functions that have more access to the OS internals and are supported only for Windows or Mac apps. They are below.

CSCS File and Command-Line Functions (not available in Unity, iOS, Android)

CSCS Function Description
cd pathname Changes current directory to pathname.
cd.. Changes current directory to its parent (one level up).
clr Clear contents of the Console.
copy source destination Copies source to destination. Source can be a file, a directory or a pattern (like *.txt).
delete pathname Deletes specified file or directory.
dir pathname=currentDirectory Lists contents of the specified directory.
exists pathname Returns true if the specified pathname exists and false otherwise.
findfiles pattern1, pattern2="", ... Searches for files with specified patterns.
findstr string, pattern1, pattern2="", ... Searches for a specified string in files with specified patterns.
kill processId Kills a process with specified Id.
mkdir dirName Creates a specified directory.
more filename Prints content of a file to the screen with the possibility to get to the next screen with a space.
move source, destination Moves source to destination. Source can be a file or a directory.
printblack (arg1, arg2="", ...) Prints specified arguments in black color on console.
printgray (arg1, arg2="", ...) Prints specified arguments in black color on console.
printgreen (arg1, arg2="", ...) Prints specified arguments in black color on console.
printred (arg1, arg2="", ...) Prints specified arguments in black color on console.
psinfo pattern Prints process info for all processes having name with the specified pattern.
pwd Prints current directory.
read Reads and returns a string from console.
readfile filename Reads a file and returns an array with its contents.
readnum Reads and returns a number from console.
run program, arg1="", arg2=""... Runs specified process with specified arguments.
tail filename, numberOfLines=20 Prints last numberOfLines of a specified filename.
writeline filename, line Writes specified line to a file.
writelines filename, variable Writes all lines from a variable (which must be an array) to a file.


CSCS Extended Miscellaneous Functions

CSCS Function Description
CallNative (methodName, parameterName, parameterValue) Calls a C# static method, implemented in Statics.cs, from CSCS code, passing a specified parameter name and value. Not available on iOS and Android.
Exit (code = 0) Stops execution and exits with the specified return code.
GetNative (variableName) Gets a value of a specified C# static variable, implemented in Statics.cs, from CSCS code. Not available on iOS and Android.
SetNative (variableName, variableValue) Sets a specified value to a specified C# static variable, implemented in Statics.cs, from CSCS code. Not available on iOS and Android.
StartStopWatch () Starts a stopwatch. There is just one stopwatch in the system.
StopStopWatch () Stops a stopwatch. There is just one stopwatch in the system. A format is either of this form: "hh::mm:ss.fff" or "secs" or "ms".
StopWatchElapsed (format=secs) Returns elapsed time according to the specified format. A format is either of this form: "hh::mm:ss.fff" or "secs" or "ms".
Timestamp (doubleValue, format="yyyy/MM/dd HH:mm:ss.fff") Converts specified number of milliseconds since 01/01/1970 to a date time string according to the passed format.


Extending CSCS with new Functions

To extend CSCS language with a new function, we need to perform two tasks. First, we define a new class, deriving from the ParserFunction class.

Second, we register the newly created class with the parser in an initialization phase as follows: ParserFunction.RegisterFunction(FunctionName, FunctionImplementation);

Let's see an example how to do that with a random number generator function.

A CSCS Function Implementing a Random Number Generator

class GetRandomFunction : ParserFunction
{
    static Random m_random = new Random();

    protected override Variable Evaluate(ParsingScript script)
    {
        // Extract all passed function args:
        List<Variable> args = script.GetFunctionArgs();

        // Check that we should have at least one argument:
        Utils.CheckArgs(args.Count, 1, m_name);

        // Check that the limit is a positive integer:
        Utils.CheckPosInt(args[0]);

        int limit = args[0].AsInt();
        return new Variable(m_random.Next(0, limit));
    }
}


Registering A CSCS Function with the Parser

ParserFunction.RegisterFunction("Random", new GetRandomFunction());

That's it! Now inside of CSCS we can just execute the following statement:

x = Random(100);

and x will get a random value between 0 and 100.