This guide covers both sides of ReoScript usage: writing ReoScript code and embedding the engine into a .NET application. The content is based on the current Source/ReoScript.sln and the test suite in this repository.
ReoScript is an ECMAScript-like scripting engine designed to be embedded into .NET applications. Its syntax is close to JavaScript, but it also includes ReoScript-specific extensions such as direct .NET access, module loading, object merging, and tag-style syntax.
Main characteristics:
- JavaScript-like syntax for application scripting
- Execute script text, files, and expressions through
ScriptRunningMachine - Expose C# objects and functions as global values
- Optionally allow .NET type import, property access, and event binding
- Support for lambda expressions, modules, JSON, timers, and closures
The core library is in Source/ReoScript/ and the CLI runner is in Source/ReoScriptRunner/.
dotnet build Source/ReoScript.slnExamples for the CLI runner:
dotnet run --project Source/ReoScriptRunner -- sample.reo
dotnet run --project Source/ReoScriptRunner -- -e "console.log(1 + 2)"
dotnet run --project Source/ReoScriptRunner -- -consoleMain ReoScriptRunner options:
-e <script>/-exec <script>: execute a script string directly-workpath <path>: set the working directory-debug: enable debug mode-console: enter the interactive console after execution-com: compile mode, not implemented in the current version
Interactive console commands:
?expr: evaluate and print an expression?: print the current global object.path: load a script file/quit,/q: exit
var total = 0;
for (var i = 1; i <= 10; i++) {
total += i;
}
console.log(total);Run returns the value of the last evaluated expression.
var a = 10
var b = 20
a + bThe result of the whole script is 30.
var a = 10;
var b = 20, c = a + b;- Variable declarations use
var - A
vardeclared in global scope becomes a global variable - Missing or unset values are often treated as
null
var n = 3.14;
var s = "hello";
var b = true;
var x = null;Common operators:
- Arithmetic:
+,-,*,/,% - Assignment:
=,+=,-=,*=,/= - Comparison:
==,!=,===,!==,<,<=,>,>= - Logical:
&&,||,! - Increment/decrement:
++,-- - Bitwise:
|,&,<<,>> - Ternary:
cond ? a : b - Type checks:
typeof,instanceof - Property deletion:
delete obj.key
var a = 10;
a += 5;
a++;
var label = a > 10 ? "large" : "small";ReoScript supports automatic semicolon insertion.
var a = 10
var b = 20
a + bHowever, semicolons inside a for (...) header are still required.
for (var i = 0; i < 5; i++) {
console.log(i);
}if (score >= 80) {
grade = "A";
} else {
grade = "B";
}var count = 5;
while (count) {
console.log(count);
count--;
}var sum = 0;
for (var i = 0; i < 10; i++) {
sum += i;
}switch (kind) {
case "error":
console.log("error");
break;
case "warn":
console.log("warn");
break;
default:
console.log("info");
break;
}for ... in behaves differently depending on the target:
- Objects: iterates property names
- Arrays: iterates element values
for (key in obj) {
console.log(key);
}
for (item in arr) {
console.log(item);
}This is different from JavaScript, where array iteration usually gives indices in for...in.
function add(a, b) {
return a + b;
}Function declarations are hoisted and can be used before their definition.
var x = add(10, 20);
function add(a, b) {
return a + b;
}var add = function(a, b) {
return a + b;
};var add = (a, b) => a + b;
var square = x => x * x;
var total = ((a, b) => a + b)(2, 3);Block bodies are also supported.
var classify = n => {
if (n >= 0) return "positive";
return "negative";
};ReoScript uses lexical scoping. Inner functions can capture local variables from outer functions.
function makeCounter() {
var count = 0;
return function() {
count = count + 1;
return count;
};
}
var c1 = makeCounter();
var c2 = makeCounter();c1 and c2 keep independent captured state.
Functions provide call and apply, so you can override the call-time this.
function bracketMe() {
return "[" + this + "]";
}
bracketMe.call("abc");var user = {
name: "alice",
age: 20
};Property access:
user.name
user["name"]user.email = "a@example.com";
delete user.email;function User(name) {
this.name = name;
}
var u = new User("alice");ReoScript also supports an extended initializer form after new.
function User() {
this.role = "guest";
}
var u = new User() {
name: "alice",
active: true
};ReoScript supports object merging through +.
var a = { x: 10 };
var b = { y: 20 };
var c = a + b;var a = 1, b = 2;
var obj = { a, b };
var merged = { ...obj, c: 3 };
var { a, c } = merged;var arr = [1, 2, 3];
arr.push(4);
arr[0] = 10;Besides the usual array behavior, several helper methods are added by the built-in scripts.
pushspliceindexOfjoinconcatmapreducewherefirstlastequalseach
var arr = [1, 2, 3, 4, 5];
var even = arr.where(n => Math.floor(n / 2) == n / 2);
var doubled = arr.map(n => n * 2);
var total = arr.reduce((a, c) => a + c, 0);When console is available in the host, these methods are exposed:
console.read()console.readline()console.write(value)console.log(value)
Common constants and functions:
Math.PIMath.EMath.LN2Math.LN10Math.min(...)Math.max(...)Math.floor(...)Math.sqrt(...)Math.atan2(...)
var now = Date.now();
var d = new Date();
var ms = d.getTime();var obj = JSON.parse("{name:'apple',count:10}");
var str = JSON.stringify(obj);Both JSON.parse and JSON.stringify support converter callbacks.
var obj = JSON.parse(src, (key, value) => value);
var str = JSON.stringify(obj, (key, value) => String(value));var value = eval("1 + 2");eval runs in the current scope.
var n = parseInt("FF", 16);The debug object is available when you attach new ScriptDebugger(srm).
debug.assert(1 + 1 == 2);
var sw = debug.Stopwatch.startNew();ReoScript evaluates conditions using truthy / falsy conversion.
Falsy values:
nullfalse0NaN""
Truthy values:
- non-zero numbers
- non-empty strings
- objects
- arrays
- functions
var name = input || "guest";
var enabled = config && config.flag;&& and || return actual values, not only booleans.
try {
dangerous();
} catch (e) {
console.log(e.message);
} finally {
console.log("cleanup");
}catch can also be written without a variable.
try {
dangerous();
} catch {
console.log("error");
}throw new Error("something wrong");
throw 10;For Error objects, message is available.
import "common.reo";This executes the imported script in global scope.
import "math.reo" as math;
var result = math.add(10, 3);This loads the script into an isolated scope and binds it to the given name.
var math = importModule("math.reo");
var result = math.add(3, 4);Properties:
- module scope is isolated from the global scope
- module-level functions and variables become properties of the returned object
- a module file is cached and normally executes only once
Timer functions:
setTimeout(callback, milliseconds)setInterval(callback, milliseconds)clearInterval(id)
var count = 0;
var id = setInterval(function() {
count++;
if (count >= 5) {
clearInterval(id);
}
}, 100);Exceptions raised inside event handlers or async callbacks can be handled on the host side through the ScriptError event.
using unvell.ReoScript;
var srm = new ScriptRunningMachine();
srm.Run("var x = 10; var y = 20;");
var result = srm.CalcExpression("x + y");using unvell.ReoScript;
var srm = new ScriptRunningMachine();
srm.SetGlobalVariable("appName", "Demo");
srm.SetGlobalVariable("version", 1);
srm.Run("console.log(appName);");The indexer works as well.
srm["answer"] = 42;using unvell.ReoScript;
var srm = new ScriptRunningMachine();
srm["hello"] = new NativeFunctionObject("hello", (ctx, owner, args) =>
{
return "Hello " + (args.Length > 0 ? args[0] : "world");
});using unvell.ReoScript;
var srm = new ScriptRunningMachine();
srm.WorkMode |= MachineWorkMode.AllowDirectAccess;
srm.SetGlobalVariable("user", new User());Script side:
user.Nickname = "alice";
user.Hello();srm.WorkMode |= MachineWorkMode.AllowDirectAccess;
srm.ImportType(typeof(System.Windows.Forms.LinkLabel));srm.WorkMode |= MachineWorkMode.AllowDirectAccess
| MachineWorkMode.AllowImportTypeInScript
| MachineWorkMode.AllowCLREventBind;Script side:
import System.Windows.Forms.*;
import System.Drawing.Point;
var f = new Form() {
text: "Form created in ReoScript"
};With AllowCLREventBind, script functions can be attached to .NET events.
var link = new LinkLabel() {
text: "click me",
click: function() {
f.close();
}
};using unvell.ReoScript;
using unvell.ReoScript.Diagnostics;
var srm = new ScriptRunningMachine();
var debugger = new ScriptDebugger(srm);That enables debug.assert and related helpers.
You can limit loop iterations to guard against infinite loops.
srm.MaxIterationsPerLoop = 10_000_000;- default value:
10_000_000 - set
0to disable the limit - exceeding the limit throws
ScriptExecutionTimeoutException
Relative imports use WorkPath as their base path.
srm.WorkPath = @"C:\scripts";ReoScript includes an HTML-like tag syntax for object construction.
function User() { }
var usr = <User />;The grammar also includes template-style declarations such as:
template<UserCard>(name, age) <User />;This syntax is ReoScript-specific and is not the same thing as JavaScript or JSX. If you plan to use it, confirm the expected behavior in the target host application.
ReoScript is ECMAScript-like, not a drop-in JavaScript runtime. Important differences include:
for ... inover arrays yields elements rather than indicesundefinedoften behaves close tonull- there are tests where
NaN == NaNis true, unlike JavaScript debugis not guaranteed to exist unless the debugger helper is attached- object merge with
a + b,new Type() { ... }, and tag syntax are ReoScript-specific extensions
When porting JavaScript code, validate behavior in ReoScript rather than assuming browser or Node.js semantics.
var config = {
title: "My App",
retryCount: 3,
endpoints: ["a", "b", "c"]
};function canPurchase(user, total) {
return user != null && user.active && total > 0;
}import System.Windows.Forms.*;
var button = new Button() {
text: "Run",
click: function() {
console.log("clicked");
}
};- CLI runner:
Source/ReoScriptRunner/ - embedding samples:
Samples/ - language tests:
Source/TestCase/tests/ - core implementation:
Source/ReoScript/ScriptRunningMachine.cs
If you need more examples, start with Source/TestCase/tests/ and Samples/. They are the most concrete reference for supported behavior.