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:
- Customized Scripting in C#, MSDN
- Programming your own language in C#, CODE Magazine
- Implementing a Custom Language Succinctly, Syncfusion E-book
The source code for Mobile App development is here. The usage of CSCS in Mobile App development has been described in:
- Developing Cross-Platform Native Apps with a Functional Language, CODE Magazine
- Writing Native Mobile Apps Using a Customizable Scripting Language, MSDN
- Writing Native Mobile Apps in a Functional Language Succinctly, Syncfusion E-book
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:
- Compiling Scripts to Get Compiled Language Performance, CODE Magazine
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.
Special thanks for contributions from Dan Spear, especially using Reflection in order to call C# functionality directly from CSCS. Examples coming up soon.
- The syntax is a mixture between C#, JavaScript, and Python.
- All statements must end with a semicolon ";".
- Identation and new lines are not used in parsing (unlike Python).
- All CSCS variables have at least 3 properties that can be accessed using the dot notation: properties, type, size, and string.
- Variables and arrays are all defined implicitly, e.g. x=5, b[7]=11
An example of a list initialization: c = {"aa", "bb", "xxx"};
You can also define it explicitly: c[0]="aa"; c[1]="bb";
Definition in index form doesn't have to start from index 0, or even from the first dimension: not defined elements will have a type NONE. E.g.: b[5][3][5][3]=15;
Similarly, when defining dictionaries, e.g.: x["bla"]["blu"]="wichtig"; - Control flow statements if, else, while, for, try, etc., all require statements between the curly braces (even for a single statement).
- "elif" means "else if" (like in Python)
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 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. |
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;
}
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 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. |
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
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 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 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). |
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 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 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. |
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 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 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. |
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}
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 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 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. |
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.
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));
}
}
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.