Skip to content
bizzehdee edited this page Jun 26, 2026 · 20 revisions

Language Reference

DScript is syntactically close to JavaScript (ES2020+). This page covers every supported language construct.

Variables

var x = 10;       // function-scoped, hoisted
let y = 20;       // block-scoped, not hoisted
const PI = 3.14;  // block-scoped, immutable binding

let 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

Types

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

Numeric separators (ES2021)

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;          // octal

Invalid 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)

Operators

Category Operators
Arithmetic + - * / % ++ -- (prefix and postfix), unary + -
Assignment = += -= *= /= %= &= |= ^= <<= >>= >>>= &&= ||= ??=
Comparison < > <= >= == != === !==
Logical && || !
Bitwise & | ^ ~ << >> >>>
Nullish / optional ?? ?.
Other ?: ternary, typeof, void, instanceof, in, delete, new, ... spread, , comma

Template literals

var name = "Alice";
var msg  = `Hello, ${name}! You are ${20 + 5} years old.`;

Any expression may appear inside ${}. Nested template literals are allowed.

Tagged template literals

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] === 42

String.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!"

String escape sequences

String literals ("..." and '...') support the following escape sequences:

Escape Meaning
\n Newline (LF)
\r Carriage return
\t Tab
\\ Backslash
\' Single quote
\" Double quote
\0 Null character
\xHH Hex escape — one byte (U+00–U+FF)
\uXXXX Unicode escape — exactly 4 hex digits (BMP, U+0000–U+FFFF)
\u{X…} Unicode code point escape — 1–6 hex digits, any Unicode code point (ES2015+)
var tick   = "✓";       // ✓
var A      = "A";       // A
var emoji  = "\u{1F600}";    // 😀  (astral plane — two UTF-16 code units)
var hello  = "\u{48}\u{65}\u{6C}\u{6C}\u{6F}";  // Hello

The \u{...} form accepts code points above U+FFFF; DScript emits the corresponding UTF-16 surrogate pair automatically.

Objects

// 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;

Arrays

var arr = [10, 20, 30];
arr[0];            // 10
arr.length;        // 3
arr.push(40);      // append
arr.pop();         // remove last

Functions

// 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"

Closures

Every function captures its enclosing lexical scope:

function counter() {
    var n = 0;
    return () => ++n;
}
var inc = counter();
inc(); // 1
inc(); // 2
inc(); // 3

Classes and prototypes

function 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;   // true

class 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;
    }
}

Private fields and methods (ES2022)

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;        // 70

Static 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();  // 2

Rules:

  • #name declarations must appear in the class body before use.
  • Accessing obj.#name outside 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 initialisation blocks (ES2022)

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.

Destructuring

// 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

Spread operator

// 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 }

Nullish coalescing and optional chaining

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 absent

Logical assignment operators (ES2021)

Short-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;

Control flow

// 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); }

// for...of with destructuring
for (const [key, value] of map) { console.log(key, value); }
for (const { name, age } of users) { console.log(name, age); }

// 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.

Exception handling

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" };

Generators

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 generators (ES2018)

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.

for await...of (ES2018)

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 / await

async function fetchUser(id) {
    var data = await loadFromDb(id);
    return data.name;
}

fetchUser(1).then(name => console.log(name));

// Async arrow functions — parenthesised or single-parameter shorthand
const fetchName = async (id) => {
    var data = await loadFromDb(id);
    return data.name;
};

const delay = async ms => new Promise(resolve => setTimeout(resolve, ms));

After engine.Run(program) (or engine.Execute()), call ScriptEngine.DrainMicroTasks() from C# to flush the microtask queue and settle all pending Promises. Without it, .then callbacks never fire. The dscript CLI calls it automatically; see Engine — Async / await and microtasks.

Top-level await (ES2022)

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.

Promises

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]);

Regular expressions

/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.

Named capture groups (ES2018)

var re = new RegExp("(?<year>\\d{4})-(?<month>\\d{2})");
var m = re.exec("2024-01");
m.groups.year;   // "2024"
m.groups.month;  // "01"

Lookahead and lookbehind (ES2018)

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)

String.prototype.matchAll (ES2020)

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 (ES2020)

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 BigInt

Arithmetic, 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 types

Use 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 and instanceof

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.hasInstance

in and delete

var obj = { a: 1, b: 2 };
"a" in obj;    // true
delete obj.a;
"a" in obj;    // false

Proxy and Reflect (ES2015)

Proxy 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;  // 99

Reflect 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 names

Full API documentation: see Standard Library — Proxy and Standard Library — Reflect.

Strict mode ("use strict")

Place the directive at the top of a script or the first statement of a function body to opt into strict mode:

"use strict";  // whole script is strict

function f() {
    "use strict";  // only this function and nested functions are strict
}

Strict mode enforces several additional constraints at both compile time and runtime:

Compile-time errors

Violation Example
Duplicate parameter names function f(a, a) {}
eval or arguments as a binding name var eval = 1;
delete applied to an identifier var x; delete x;
Octal integer literals var n = 0777;

Runtime errors

Violation Error
Assignment to an undeclared variable ReferenceError
Write to a non-writable property TypeError
arguments.callee or arguments.caller access TypeError
this in a plain (non-method) call undefined instead of the global object

Block-scoped function declarations

In strict mode, a function declaration inside a block { } is scoped to that block rather than hoisted to the enclosing function:

"use strict";
if (true) {
    function helper() { return 1; }
    helper();  // works inside the block
}
typeof helper;  // "undefined" — not visible here

Without strict mode the declaration is hoisted and helper is visible after the block.

Clone this wiki locally