-
-
Notifications
You must be signed in to change notification settings - Fork 5
Language
DScript is syntactically close to JavaScript (ES2020+). This page covers every supported language construct.
var x = 10; // function-scoped, hoisted
let y = 20; // block-scoped, not hoisted
const PI = 3.14; // block-scoped, immutable bindinglet and const obey block scoping — they are not visible outside the {} block they are declared in.
globalThis refers to the global scope object from any context:
var x = 1;
globalThis.x; // 1
globalThis.y = 2; // same as var y = 2 at the top level| Type | Example literals |
|---|---|
| Number (integer) |
0, 42, -7, 0xFF, 0b1010, 0o17, 1_000, 0xFF_FF, 0b1010_0001, 0o7_7
|
| Number (float) |
3.14, -0.5, 1e10, 1_000.5, 1_000e2
|
| String |
"hello", 'world', `template`
|
| Boolean |
true, false (stored as 1 / 0) |
| Null | null |
| Undefined | undefined |
| Object | { key: value } |
| Array | [1, 2, 3] |
| Function |
function f() {}, x => x
|
| Symbol |
Symbol(), Symbol('label'), Symbol.for('key')
|
| BigInt |
0n, 42n, 0xFFn, 0b1010n, 0o77n
|
Underscores (_) may be used as visual separators inside any numeric literal. They are ignored by the parser and do not affect the value.
var million = 1_000_000; // integer
var pi = 3.141_592_653; // float
var scientific = 1_000e2; // exponent form
var hex = 0xFF_FF; // hex
var bits = 0b1010_0001; // binary
var octal = 0o7_7; // octalInvalid positions (throw SyntaxError):
- Immediately after a prefix (
0x_,0b_,0o_) - Immediately after a decimal point (
1._5) - Immediately after an exponent marker (
1e_2) - At the end of a literal (
100_) - Two consecutive separators (
1__000)
| Category | Operators |
|---|---|
| Arithmetic |
+ - * / % ++ -- (prefix and postfix), unary + -
|
| Assignment |
= += -= *= /= %= &= |= ^= <<= >>= >>>= &&= ||= ??=
|
| Comparison |
< > <= >= == != === !==
|
| Logical |
&& || !
|
| Bitwise |
& | ^ ~ << >> >>>
|
| Nullish / optional |
?? ?.
|
| Other |
?: ternary, typeof, instanceof, in, delete, new, ... spread |
var name = "Alice";
var msg = `Hello, ${name}! You are ${20 + 5} years old.`;Any expression may appear inside ${}. Nested template literals are allowed.
A tag function placed immediately before a template literal receives the string segments and interpolated values as separate arguments:
function tag(strings, ...values) {
// strings[i] — cooked segment i (escape sequences processed)
// strings.raw[i] — raw segment i (escape sequences preserved as-is)
var result = "";
for (var i = 0; i < strings.length; i++) {
result += strings[i];
if (i < values.length) result += values[i];
}
return result;
}
var n = 42;
var r = tag`value\tis\t${n}`;
// strings[0] === "value\tis\t" (tab chars)
// strings.raw[0] === "value\\tis\\t"
// values[0] === 42String.raw is a built-in tag that returns the raw string content (escape sequences are not processed):
var path = String.raw`C:\Users\alice\notes.txt`;
// => "C:\\Users\\alice\\notes.txt"
var name = "World";
var s = String.raw`Hello\n${name}!`;
// => "Hello\\nWorld!"// Object literal
var obj = { x: 1, y: 2 };
// Shorthand property (name === value)
var name = "bob";
var record = { name, score: 100 }; // { name: "bob", score: 100 }
// Method shorthand
var calc = {
add(a, b) { return a + b; }
};
// Getter / setter (ES5 accessor syntax)
var counter = {
_n: 0,
get value() { return this._n; },
set value(v) { this._n = v; }
};
counter.value = 10;
counter.value; // 10
// Computed key
var key = "color";
var car = { [key]: "red" }; // { color: "red" }
// Property access
obj.x; // dot notation
obj["y"]; // bracket notation
// Property deletion
delete obj.x;var arr = [10, 20, 30];
arr[0]; // 10
arr.length; // 3
arr.push(40); // append
arr.pop(); // remove last// Named declaration (hoisted)
function add(a, b) { return a + b; }
// Anonymous expression
var mul = function(a, b) { return a * b; };
// Arrow — expression body (implicit return)
var sq = x => x * x;
var sum = (a, b) => a + b;
// Arrow — block body
var clamp = (v, lo, hi) => {
if (v < lo) return lo;
if (v > hi) return hi;
return v;
};
// Default parameters
function greet(name, msg = "Hello") {
return `${msg}, ${name}!`;
}
// Rest parameters
function first(head, ...tail) { return head; }
// Spread in calls
function add3(a, b, c) { return a + b + c; }
add3(...[1, 2, 3]); // 6
// arguments object — available in all non-arrow functions
function sum() {
var total = 0;
for (var i = 0; i < arguments.length; i++) total += arguments[i];
return total;
}
sum(1, 2, 3); // 6
// Arrow functions do NOT have an arguments binding
var f = () => typeof arguments; // "undefined"Every function captures its enclosing lexical scope:
function counter() {
var n = 0;
return () => ++n;
}
var inc = counter();
inc(); // 1
inc(); // 2
inc(); // 3function Animal(name) {
this.name = name;
}
Animal.speak = function() {
return `${this.name} makes a sound`;
};
function Dog(name) {
Animal.call(this, name);
}
Dog.prototype = new Animal();
var d = new Dog("Rex");
d.speak(); // "Rex makes a sound"
d instanceof Dog; // true
d instanceof Animal; // trueclass syntax is also accepted:
class Shape {
constructor(color) {
this.color = color;
}
describe() {
return `A ${this.color} shape`;
}
}
class Circle extends Shape {
constructor(color, r) {
super(color);
this._radius = r;
}
// Getter / setter (ES5 accessor syntax in class bodies)
get radius() { return this._radius; }
set radius(v) { this._radius = v > 0 ? v : 0; }
area() {
return Math.PI * this._radius * this._radius;
}
}Names prefixed with # are private to the class body. They cannot be accessed from outside the class (a compile-time error is raised if you attempt to do so).
class BankAccount {
#balance = 0; // private instance field with default
constructor(initial) {
this["#balance"] = initial;
}
#validate(amount) { // private method
return amount > 0 && amount <= this["#balance"];
}
withdraw(amount) {
if (this["#validate"](amount)) {
this["#balance"] -= amount;
return true;
}
return false;
}
get balance() { return this["#balance"]; }
}
var acct = new BankAccount(100);
acct.withdraw(30); // true
acct.balance; // 70Static private fields and methods are also supported:
class IdGen {
static #next = 1;
static #inc() { return IdGen["#next"]++; }
static create() { return IdGen["#inc"](); }
}
IdGen.create(); // 1
IdGen.create(); // 2Rules:
-
#namedeclarations must appear in the class body before use. - Accessing
obj.#nameoutside the declaring class is a compile-time error. - Private fields from a parent class are not inherited by subclasses; each class has its own private namespace.
- Instance private field default initializers run at the start of the constructor, before the constructor body executes.
static { ... } runs once when the class is defined. this refers to the class constructor, making it easy to set static properties or call static methods:
class Config {
static defaultPort() { return 8080; }
static {
this.port = this.defaultPort();
this.host = "localhost";
}
}
Config.port; // 8080
Config.host; // "localhost"Multiple blocks are allowed and run in source order. The block runs before any instance can be created.
// Array
var [a, b, c] = [1, 2, 3];
var [x, , z] = [10, 20, 30]; // skip middle
var [head, ...rest] = [1, 2, 3, 4]; // rest = [2,3,4]
// Object
var { x, y } = { x: 1, y: 2 };
var { name: alias } = { name: "alice" }; // rename
var { score = 0 } = {}; // default
var { a: { b } } = { a: { b: 99 } }; // nested// Array spread
var a = [1, 2];
var b = [...a, 3, 4]; // [1,2,3,4]
// Object spread
var base = { x: 1 };
var ext = { ...base, y: 2 }; // { x:1, y:2 }var x = null;
var y = x ?? "default"; // "default"
var obj = { a: { b: 42 } };
obj?.a?.b; // 42
obj?.z?.w; // undefined (no throw)
obj?.fn?.(1, 2); // undefined if fn absentShort-circuit compound assignments — the RHS is only evaluated when needed.
a &&= b // assign b to a only if a is truthy (a && (a = b))
a ||= b // assign b to a only if a is falsy (a || (a = b))
a ??= b // assign b to a only if a is null or undefined
// All three also work on property and index targets:
obj.x &&= computeNewValue();
arr[i] ??= defaultValue;// if / else if / else
if (x > 0) { ... } else if (x < 0) { ... } else { ... }
// while
while (cond) { ... }
// do / while
do { ... } while (cond);
// for
for (var i = 0; i < 10; i++) { ... }
// for...in — property names
for (var key in obj) { console.log(key); }
// for...of — iterable values (arrays, generators, …)
for (var val of [1, 2, 3]) { console.log(val); }
// switch — no fallthrough; default may appear anywhere
switch (x) {
case 1: ...; break;
default: ...;
case 2: ...;
}
// break / continue
while (true) {
if (done) break;
if (skip) continue;
}continue inside a switch that is nested inside a loop targets the loop, not the switch.
try {
throw new Error("something went wrong");
} catch (e) {
console.log(e.message);
} finally {
// always runs
}
// Throw any value
throw { code: 404, message: "not found" };function* range(start, end) {
for (var i = start; i < end; i++) yield i;
}
for (var n of range(0, 5)) console.log(n); // 0 1 2 3 4
var gen = range(0, 3);
gen.next(); // { value: 0, done: false }
gen.next(); // { value: 1, done: false }
gen.next(); // { value: 2, done: false }
gen.next(); // { value: undefined, done: true }Generators that contain no try/catch use a stackless state-machine path.
async function* combines generators and async functions. Each .next() call returns a Promise that resolves to {value, done}:
async function* paginate(ids) {
for (var id of ids) {
var page = await loadPage(id);
yield page;
}
}
var gen = paginate([1, 2, 3]);
gen.next().then(result => console.log(result.value, result.done));Async generator objects also implement [Symbol.asyncIterator] (returning this), so they can be consumed with for await...of.
Iterate over an async iterable inside an async function:
async function processAll(ids) {
for await (var page of paginate(ids)) {
console.log(page);
}
}for await...of also accepts plain arrays and synchronous iterables (falls back to Symbol.iterator). It must appear inside an async function or at the top level of a module (where top-level await is detected automatically).
async function fetchUser(id) {
var data = await loadFromDb(id);
return data.name;
}
fetchUser(1).then(name => console.log(name));After engine.Run(program), call engine.DrainMicroTasks() from C# to flush the microtask queue and settle all pending promises.
await can be used at the top level of a script (outside any function). The compiler detects this automatically and runs the script body as an async function, keeping all variable declarations at the global scope:
var data = await Promise.resolve({ ok: true });
var status = data.ok ? "ready" : "failed";Call engine.DrainMicroTasks() after vm.Run() to let the awaited Promises settle before reading results:
var chunk = compiler.CompileProgram(source); // auto-detects top-level await
vm.Run(chunk, env);
ScriptEngine.DrainMicroTasks(); // settle awaited Promises
var status = engine.Root.GetParameter("status").String;Variables declared with var are accessible at the engine root after draining, exactly as in a regular (non-async) program.
var p = new Promise((resolve, reject) => {
resolve(42);
});
p.then(v => console.log("resolved:", v))
.catch(e => console.log("rejected:", e));
// Static constructors
Promise.resolve(1);
Promise.reject("err");
Promise.all([p1, p2, p3]);
Promise.allSettled([p1, p2]);
Promise.race([p1, p2]);
Promise.any([p1, p2]);/pattern/flags // literal
new RegExp("pattern", "flags") // constructor
/hello/i.test("Hello World"); // true
/(\d+)/.exec("abc123"); // ["123", "123", index: 3, input: "abc123", groups: undefined]
var s = "hello world";
s.match(/\w+/g); // ["hello", "world"]
s.replace(/o/g, "0"); // "hell0 w0rld"Supported flags: g (global), i (ignoreCase), m (multiline), s (dotAll — . matches \n); u, d, v, y accepted and stored.
var re = new RegExp("(?<year>\\d{4})-(?<month>\\d{2})");
var m = re.exec("2024-01");
m.groups.year; // "2024"
m.groups.month; // "01"new RegExp("foo(?=bar)").exec("foobar")[0]; // "foo" (positive lookahead)
new RegExp("foo(?!bar)").exec("foobaz")[0]; // "foo" (negative lookahead)
new RegExp("(?<=foo)bar").exec("foobar")[0]; // "bar" (positive lookbehind)
new RegExp("(?<!foo)bar").exec("bazbar")[0]; // "bar" (negative lookbehind)Returns an array of all matches. The RegExp argument must have the g flag when it is a RegExp object.
var re = new RegExp("\\d+", "g");
var matches = "a1b2c3".matchAll(re); // 3 match objects
matches[0].index; // 1
matches[0].input; // "a1b2c3"
matches[0][0]; // "1"
// Named groups per match
var re2 = new RegExp("(?<n>\\d+)", "g");
"a1b2".matchAll(re2)[1].groups.n; // "2"BigInt is an arbitrary-precision integer type. BigInt literals end with the n suffix:
var a = 42n;
var b = 0xFFFFFFFFFFFFFFFFn; // hex BigInt
var c = 0b1010n; // binary BigInt
var d = 0o77n; // octal BigIntArithmetic, comparison, and bitwise operators all work between two BigInt values:
var x = 10n + 20n; // 30n
var y = 100n / 3n; // 33n (truncated, not rounded)
var z = 5n ** 10n; // only if exponentiation operator is supported
var w = 7n % 3n; // 1n
10n < 20n; // true
10n === 10n; // true
~5n; // -6n (BigInt two's-complement)Mixed-type arithmetic is forbidden and throws a TypeError:
1n + 1; // TypeError: Cannot mix BigInt and other typesUse the BigInt() factory (not new BigInt()) to convert other types:
BigInt(42); // 42n
BigInt("12345"); // 12345n
BigInt(42n); // 42n (identity)typeof returns "bigint":
typeof 42n; // "bigint"typeof 42; // "number"
typeof "hi"; // "string"
typeof true; // "boolean"
typeof undefined; // "undefined"
typeof null; // "object"
typeof {}; // "object"
typeof []; // "object"
typeof function(){}; // "function"
typeof Symbol(); // "symbol"
typeof 42n; // "bigint"
[] instanceof Array; // true
// instanceof can be customised via Symbol.hasInstancevar obj = { a: 1, b: 2 };
"a" in obj; // true
delete obj.a;
"a" in obj; // falseProxy wraps any object and intercepts operations like property access, assignment, in, delete, and function calls via a handler object of trap functions:
var proxy = new Proxy({ x: 1 }, {
get: function(target, key) { return key === 'x' ? 99 : target[key]; }
});
proxy.x; // 99Reflect provides the same fundamental operations as plain functions:
Reflect.get(obj, 'key');
Reflect.set(obj, 'key', value);
Reflect.has(obj, 'key'); // like `key in obj`
Reflect.deleteProperty(obj, 'key');
Reflect.apply(fn, thisArg, args);
Reflect.construct(Ctor, args);
Reflect.ownKeys(obj); // array of own property namesFull API documentation: see Standard Library — Proxy and Standard Library — Reflect.