Reference​

The lookup page. The guide teaches the language and the embedding chapters teach the host API; this page is where you come back to when you already know the shape of a thing and need the exact rule, the exact spelling, or the exact error text. The tables point back at the chapters that show each rule running, and the Documents section at the end indexes the specification files themselves.

Two conventions, used everywhere below. A handle is a shared_ptr<T>; a reference is a T&. And the checker means the static checker that runs before any script executes. It is on, in strict mode, unless the host turns it down.

Operators​

From Operators & Arithmetic:

CategoryOperatorsNotes
Arithmetic+ - * / %int op int stays int (7 / 2 → 3); any float operand promotes
Unary- ! ~ ++ --++/-- in prefix and postfix form, on every lvalue (elements, map entries, members)
Assignment= += -= *= /= %= &= |= ^= <<= >>=typed targets convert C++-style; x op= y is x = x op y with x evaluated once, and the right side runs before any part of the target. Every C++ lvalue is a target: a call returning T&, a conditional of lvalues, (a = b) = c, ++x = v, cast<T&>(e)
Comparison== != < > <= >= <=><=> returns -1 / 0 / 1; 1 == 1.0 is true; ordinary numeric comparisons with NaN follow IEEE (false and unequal); <=> groups all NaNs last and treats signed zeros as equivalent
Logical&& || !short-circuit; !v on a class instance is !to_bool(v) (an instance whose class declares no bool to_bool() is true)
Bitwise& | ^ ~ << >>on integers; shifts operate on the 64-bit pattern (<< discards high bits by contract, >> is arithmetic), and the count must be 0..63
Null?? ??=a ?? b: a unless null, else b (evaluated only then); a ??= b stores only into a null a
Ternary?:right-associative
Function(params) => expr (params) => { ... } x => expran arrow function: a function expression that closes over its scope by reference, like function(...)
Castcast<T>(e) cast<T&>(e) cast<shared_ptr<T>>(e)cast is a keyword. See the typed boundary
Member/index. [] ?. ::?. is null-safe and a null in front of it skips the rest of the chain (a?.b.c is null when a is, and still raises when b is null: write a?.b?.c); :: reaches statics and namespaces (Class::name is the only static spelling; Class.name is not one)

Overflowing + - *, unary -, and INT64_MIN / -1 raise a catchable runtime error in the default build (JAISCRIPT_CHECKED_OVERFLOW). Integer arithmetic is never undefined behavior. Integer and float division by zero raise a catchable error (no IEEE infinity). When you want wraparound on purpose, math::wrap_add / wrap_sub / wrap_mul say so.

char operands promote to int (unsigned, 0..255) under arithmetic, bitwise, and comparison operators: 'a' == 97 is true, 'z' - 'a' is 25, and s[i] > 127 is an unsigned high-bit test (byte 0xC3 is 195, never -61). There is no implicit int→char: type_of('a' + 1) is "int". A char converts only through an explicit cast: cast<char>(n) (range-checked 0..255), cast<int>(c), cast<string>(c), cast<char>("a"), or through the stdlib function form to_char(n), or a \xNN literal.

Overloading on script classes. A class declares C++'s overloadable set less && || , -> new delete and !: binary + - * / % & | ^ << >> < > <= >= == != <=>, unary - + ~ (told apart from the binary form by the parameter count), =, [] and (), each as a method (Vec operator+(Vec o)). An operator runs only when its (left) operand is an instance whose class chain declares it; every other operand keeps the builtin meaning. a op= b is a = a op b through operator op (there is no operator op=); = into an existing object (a slot declared the class, a var) runs operator= when an overload takes the value, else the implicit memberwise copy, and initialization copy-constructs; != without an operator!= negates operator==; relational operators use <=> when the class does not declare the requested operator. Truthiness is bool to_bool(): if (v) calls it and !v negates it, and declaring operator! is the parse error operator! cannot be declared: !v is !to_bool(v); declare bool to_bool(). operator() makes an instance callable (v(1, 2)); the checker still holds a function slot to functions and null, so a callable instance goes in wrapped in a lambda. Chapter: Operators & Arithmetic.

Keywords​

The reserved words. None of them names a variable, a parameter, a program-scope function or a class; a qualifier or a member declaration makes one a name (keyword member names):

WordIntroduces
int float bool char string voidthe scalar types and the no-result return type
var let autoa dynamic variable (let is a synonym of var); a variable that takes its initializer's static type (The Type Ladder)
array map shared_ptr weak_ptrthe container and handle types, written with their arguments: array<int>, map<string, int>, shared_ptr<T>
functiona function declaration (function f(a) { ... }), an anonymous function expression (a [&] lambda), and the callable-or-null slot type (function cb = null;)
consta variable, parameter or field set once and then only read (const)
class nocopya class; nocopy class C is one whose instances cannot be copied
public private protectedaccess labels in a class body; protected is a synonym of private, which admits the class and its subclasses
static overridea member of the class rather than of an instance (C::s); a method that replaces a base class's method
this superthe receiver inside a method; the base class (: super(args) in a constructor's initializer list, super::m() in a method)
newnew T(args): one shared object, a shared_ptr<T>; make_shared<T>(args) is the same expression
enum namespacea scoped enumeration (E::v); a named scope (ns::x) (Kitchen Sink)
if else while for break continue returncontrol flow; for is the C-style loop and the range form for (auto x : xs)
switch case default fallthrougha switch; a case breaks by default, and fallthrough; continues into the next one
try catch throwexceptions: catch (e) takes one name and no type (Control Flow)
coroutine yielda coroutine declaration (coroutine prefixes any function spelling); a value handed out by it (Coroutines)
parallel_fora data-parallel loop (Parallel loops)
castcast<T>(e), cast<T&>(e), cast<shared_ptr<T>>(e) (below)
include importtextual inclusion of a file; a module, its own unit (Modules). A leading # is ignored, so #include "x" is include "x"
extern inlinea name defined outside the program; a program-scope variable every declaring unit shares (below)
true false null nullptrthe literals; nullptr is a synonym of null

Words with a meaning only in one position, and names everywhere else: operator (an operator declared in a class, operator+), using (using ns::name; at the start of a statement), do (do { } while (c); when a { follows), double (a synonym of float where a type is read), utf8_string (the text type, where a type is read), uint8 and int8 (an array's element type only), the attributes [[warn_mode]] and [[strict_mode]], and hot_reload_migrate (reserved for the reload hook at a file's top level). Words from other languages (goto, virtual, delete, constexpr, ...) are not reserved; where one would fail to parse, the error names the JaiScript spelling (the table below).

Type conversions​

An example before the table. Every one of these is the same rule applied to a different slot:

jaiscript
class Meters { float v = 0; Meters(float x) { v = x; } }
class Reading { int raw = 0; Reading(int r) { raw = r; } int to_int() { return raw * 10; } }
Meters m = 3;              // one-step converting constructor
int n = Reading(4);        // to_int() on the way into an int slot
float f = 7;               // widens
int t = 3.99;              // truncates toward zero
bool b = 5;                // truthiness
string s = 42;             // anything -> string
print("{} {} {} {} {} {}", m.v, n, f, t, b, s);
var nul = null;
Meters typed_null = nul;   // null is a typed null in any object slot
print("{}", typed_null == null);
output
3.0 40 7.0 3 true 42
true

(A float prints as the shortest text that reads back to the same value, keeping .0 on a whole number, with inf, -inf and nan for the values no digits spell (every NaN prints nan, a format spec included, whatever produced it); template strings and {} placeholders take format specs: ${f:.1f}.)

One table. The same conversion rules govern declarations (int x = e), assignment (x = e), by-value parameters, return values, instance and static field stores, and the element boundary of typed arrays and maps. A conversion is always one step: a converting constructor or a to_X() method runs, but never chains (X.to_int() followed by C(int) is not implicit).

TargetAcceptsNotes
intint; float (truncates toward zero: 3.99 → 3, -3.99 → -3); bool (true → 1); an object with to_int()string, char, null and containers are errors (no implicit parse; use "42".to_int())
floatfloat; int (widens); bool; an object with to_float()
boolanythingtruthiness (5 → true, 0 → false)
stringanythingthe display text to_string gives (Printing values): a container's contents, an object's own to_string() when it defines one, else its class name and fields. Note that a string parameter therefore accepts anything too
charchar; an object with to_char()char c = 65 is an error. Write cast<char>(65)
array<T> / map<K,V>an array / a mapelement and key types are checked on every store: by the checker where it sees both types (cannot push into 'array<int>': expected 'int', got 'string'), at run time otherwise (Cannot store string in array<int>, Cannot push 'string' to array<int>). Int and float convert both ways, bool and int both ways, anything else needs the exact type; map keys never coerce
class Cnull (a typed null); the same class; a derived class (upcast); a shared_ptr handle to C or a derived class; a value that exactly one single-parameter constructor C(src) accepts directly; on plain =, a user operator= of C that accepts the sourcea derived value keeps its class in the slot (no slicing: B x = D(); type_of(x) is "D"). A parent, a sibling, or an unrelated class with the same shape is rejected; so are arrays, maps and functions
shared_ptr<T>a handle to T or a subclass; a T value (construct-and-share); nullthe pointee is enforced: shared_ptr<Bar> p = Foo() is a load error when the checker sees both classes and Cannot initialize shared_ptr<Bar> with Foo: type must match or be a subclass at run time otherwise
T& (a reference declaration or parameter)an lvalue that holds a T (a class: T or a subclass), a var included only while it holds one; a reference parameter its body never writes through also takes a literal or a temporary, and a string& parameter takes one as a writable temporaryno conversion: float& r = v with v holding an int raises Cannot initialize 'r' (declared float&) with int, and a write-through parameter given a literal is the load error 'bump' writes through parameter 'x' (line 1); pass a variable. A store through a reference converts to the reference's declared type (var v = 0; int& r = v; r = 3.9 leaves int 3), and storage that has a declared type of its own keeps its rule (int x; var& w = x; w = 2.7 stores 2)
varanything, foreverthe opt-out: everything computed from a var is unknown to the checker and can only fail at run time
auto with an initializerinferred from the initializer, then enforced as that typea null initializer carries no type, so the variable stays dynamic until its first non-null store
auto without an initializerdynamic until the first assignment, then lockedthe checker cannot see branch order, so it stays silent here

When a mismatch is caught. If both sides are visibly typed (int x = "s", a string returned from a function declared -> int, an unrelated class into a typed field), the checker reports it and the load fails before a single statement runs. If the value came through var (or any other slot the checker treats leniently), the same rule runs at run time and raises a catchable error with the texts in Diagnostics. Both cases are the same table; only the moment differs.

Compound assignment. On a typed (or locked-auto) target, a op= b computes like C++ and converts the result back to the target's type (int i = 7; i /= 2 → 3; i += 0.5 → 7; i += "s" is an error). On a var target the stored value is whatever the operation produces. A compound on a missing map key starts from the value type's default (map<string, int> m; m["x"] += 5 stores 5). The value of an assignment expression is what the destination stored (auto r = (i = 3.9) is int 3).

The typed boundary and the escape hatches​

Three spellings let you say, precisely, where you are stepping outside what the checker can see. None of them loosens anything else.

cast<T>(e), cast<T&>(e), cast<shared_ptr<T>>(e)​

jaiscript
class Base { int b = 1; }
class Derived : Base { int t = 0; }
class Holder { Base p = Derived(); }     // a Derived, held through a Base-typed field

print("{} {} {}", cast<int>('a'), cast<char>(97), cast<string>('z'));
char c = cast<char>(65);                 // `char c = 65;` would be a load error

auto h = Holder();
cast<Derived&>(h.p).t = 5;               // alias the same object: the store lands
print("{} {}", c, cast<Derived&>(h.p).t);

shared_ptr<Derived> sp = new Derived();
shared_ptr<Base> sb = cast<shared_ptr<Base>>(sp);   // share it, don't copy it
sb.b = 42;
print("{}", sp.b);

var v = Base();
try { Derived d = cast<Derived>(v); } catch (e) { print("caught: {}", e); }
output
97 a z
A 5
42
caught: Cannot cast 'Base' to 'Derived'
FormYieldsNotes
cast<T>(e)what T tmp = e; would holda class target is a value copy when e's class is T or derives from it (the copy keeps e's class); primitives take the conversion table above (cast<int>(2.7) is 2). Casting a handle or a reference this way copies the instance. The checker warns and suggests the other two forms
cast<T&>(e)a reference to the same objectmember reads and stores go through. This is the fix under strict checking when a subtype's member is reached through a base-typed receiver: cast<Derived&>(h.p).t = 5. The cast itself is an lvalue whose declared type governs the store: cast<int&>(x) += 2.5 leaves an int in x, even when x is a var
cast<shared_ptr<T>>(e)a handle sharing the instancean upcast of a handle without a copy
cast<int>(c) / cast<float>(c)the code point, 0..255the char↔int door, one direction
cast<string>(c)the one-character string
cast<char>(e)a charfrom an int in 0..255 or a string of exactly one character; anything else raises Cannot cast 'int' to 'char' / Cannot cast 'string' to 'char'

The operand is never checked against T statically. That is the point of a cast. A class mismatch raises the catchable Cannot cast 'X' to 'T' where X is the object's run-time class; null passes the value and handle forms (you get a typed null) and fails the reference form (Cannot cast 'null' to 'T&'). A T that names no class is a load error. Chapter: The Type Ladder.

extern​

extern declares a name that exists outside the program (a host global bound after load, a function the running instance registers, a value another engine injects) so that the checker counts it as declared instead of reporting use of undeclared identifier. At run time it declares nothing: the name resolves when it is used, and a use while nothing defines it raises 'name' is declared extern but never defined (an 'inline' declaration creates it) (extern function: without the parenthesis). inline T name; is the C++17 inline variable: one variable shared by every unit that declares it, created with its initializer or its type's default by the first to run and adopted by the rest (a later unit's initializer does not run again). Inline and plain declarations of one name never mix: a plain declaration of an inline variable raises 'x' is already defined inline; declare it 'inline int x;' to share it, and an inline declaration of another unit's plain variable raises 'x' is already defined (not inline) in <file>; declare every definition 'inline' to share it. When the checker sees both units it reports the plain declaration at load, with the first text.

using ns::name; (a comma list: using game::spawn, game::cost;) makes the rest of the file read name as ns::name, for any namespace. It is a parse-time rewrite: file-local, not transitive through imports or includes, and the name cannot be declared again in that file. There is no using namespace.

SpellingMeaning to the checker
extern function log_to_host;a callable of unknown signature: calls are unchecked
extern int frame; / extern shared_ptr<Game> G;typed: member access, assignments and conversions are checked exactly as for a declared variable of that type
extern config;untyped: behaves like var

An extern name travels with the program: every unit the root imports sees it. It is also the idiom for a shared leaf that reaches into a module only some programs import. In a program that does import the module, the declaration is inert and the real definition wins.

[[warn_mode]] and [[strict_mode]]​

jaiscript
extern function log_to_host;   // the host registers this later: calls are unchecked
extern int frame;              // typed: reads and stores are checked as an int
extern config;                 // untyped: behaves like var

int total = 0;
try {
    [[warn_mode]] total = "oops";        // this one statement is only warned about...
} catch (e) {
    print("run time: {}", e);            // ...so the mistake surfaces here instead
}
print("{}", total);
output
run time: Cannot assign string to 'total' (declared int)
0

The two attributes set the checker's mode for a region of one file. They compose and the innermost wins; everywhere else a file is strict. Where the attribute sits decides what it covers:

PositionCovers
first declaration of a file ([[warn_mode]]; at the top; a leading comment is fine)the whole unit
first statement of a block (a function, method or lambda body, an if/while/for body, a bare { })the whole block, nested bodies included
before any other statementthat one statement's own subtree; a nested function or lambda body inside it keeps the enclosing mode

In a warn region every error is reported as a warning (still collected, still visible through engine->check()) and an undeclared identifier is accepted as unknown; nothing is proven from it. In a strict region errors gate the load, even inside a warn region. The runtime never changes: both backends treat the attribute as nothing, so a warn region's type mistakes raise at run time exactly as they would have without a checker, which is what the example shows. A unit's mode says nothing about the units it imports. warn_mode and strict_mode are the only attribute names; anything else is a parse error.

Language rules in one place​

The contractual one-liners: the things both backends are tested to agree on, byte for byte.

RuleWhere to read more
Checking is always strict. A typed-to-typed mismatch or an undeclared identifier fails the load; a [[warn_mode]] region reports its errors as warnings instead. Only a var-sourced value can still fail at run time, where try/catch sees it.Static Checking
One conversion table for declarations, =, by-value parameters, returns, instance and static field stores and container elements; one step, never chained.above
Program closure. A file whose import/include paths are literals is the root of a program: every unit those paths reach is gathered and checked before anything runs, names and class models cross units, and import order does not matter for names. A dynamic-path import(expr) keeps undeclared identifiers at warning severity in that unit.Modules
Evaluation order is contractual: the callee is resolved before any argument is evaluated, and arguments go left to right, each taking its value when it is evaluated. (pick()(arg("left", 1), arg("right", 2)) records callee left right on both backends.) An assignment t = e or t op= e evaluates e before every part of the target (a member's receiver, a subscript's container and index): C++17.Functions
Captures are exhaustive. [] captures nothing and [x] only x; using an unlisted local of the enclosing function is the parse error Lambda capture list does not capture enclosing local 'n'; name it or use [=] or [&]. [=]/[x] copy at creation, [&]/[&x] share the variable (and outlive its frame), [x = e] initializes the lambda's own x, [this] captures the instance. A program-scope name is never captured by default: the lambda reads it when it runs. An anonymous function(...) expression is the JavaScript closure: it captures as [&].Functions
Methods are called or bound. obj.heal without a call is an error ('heal' is a method of Player; call it with player.heal() or bind it with reflect::bind(player, "heal")), at load when the checker knows the receiver. reflect::bind(obj, "name") is the method as a callable that holds the object. A builtin method read without a call raises the same way ('push' is a method of array; call it with arr.push() or wrap the call in a lambda).Classes
Typed returns are required. Under strict checking a function declared to return a primitive, a string or a reference must return on every path: 'f' declared to return 'int' may reach the end of its body without returning.Strict checking
Declarations without an initializer take their type's default: a class default-constructs, int 0, float 0.0, bool false, string "", containers empty, a math vector zero; var stays null. A class that declares no constructor takes aggregate initialization: Pt p{1, 2}, Pt{1, 2}, Pt(1, 2) fill the instance fields in declaration order (bases first), the rest keep their initializers. A reference has no default: int& r; raises Reference variable must be initialized.Classes
Top-level code runs in order. A statement at a file's top level sees the declarations above it. A function called before its declaration fails the load ('f' is called before its declaration (line 1): top-level code runs in order, so declare it above the call or make the call after it); a global read, a class constructed or a function taken as a value before its declaration raises Undefined variable 'G'. A function or method body may name a declaration that comes later in the file, because it runs only when called; what it names must exist by then.What the checker sees
Access control is two levels. public: for everyone; private: (and its synonym protected:) for the declaring class and every class derived from it. Members start public. Outside that family: Cannot access private member 'x' of class 'C', and for a private constructor (checked on the overload resolution picks) Cannot access private constructor of class 'C'. A load (from_json, from_binary) reconstitutes the saved state and is never blocked: it runs the default constructor whatever its access, or creates a class no constructor builds without arguments without one.Classes
== is memberwise on heavy values. Arrays elementwise, maps entrywise, instances of one class fieldwise (a user operator== first); handles compare by pointer; same_object(x, y) is identity. contains, index_of, has and switch cases use the same ==.Operators
A return conversion is part of the return statement. It fails inside the callee, at the return's line, with the callee as the top frame; a try around the return catches it there, and uncaught it surfaces in the caller.Return-type rules
A reserved word is a member name when qualified. After ., ?., -> or :: every word is a member name (p.catch(f), obj.default, spider::catch(url), Mode::default, super::catch()), and every reserved word but this and super declares a field, a method, a namespace member or an enum value. A host binds one the same way (.method("catch", ...)), and reflection and archives use the word as written. Bare, the word stays the keyword: a statement catch(x); and a program-scope void catch() {} are parse errors, and a class reaches its own keyword member as this.catch(x).Classes
Static fields enforce their declared type and are spelled Class::name (bare name inside the class's own methods). Class.name is not a spelling. Statics are inherited names, as in C++: a subclass sees its bases' statics bare and Derived::name names them.Statics
User accessors win. If a class (or an ancestor) declares _get_x / _set_x, obj.x reads and writes through them instead of the synthesized accessors; a bare x inside the class's methods is the backing field, so an accessor can read its own field without recursing.Name resolution
Coroutine spellings. coroutine prefixes every function form: coroutine int g(), coroutine g() -> int, coroutine function g() -> auto, coroutine function g(), and bare coroutine g(), at top level, in namespaces and as methods (static ones too). There are no coroutine lambdas.Coroutines
C++ property references bind the member. Passing a bound property (f(this.value), f(obj.value)) to a & parameter reads and writes through the host getter and setter, like any field lvalue.Value Semantics
Value semantics; references by declaration. Assignment, parameters and returns copy. & parameters, auto&, for (auto& x : ...) and shared_ptr<T> (new T(...) is sugar for it) are the spelled-out ways to alias.Value Semantics
Integers are checked, shifts wrap. + - * and unary - raise on overflow; << discards high bits by contract; math::wrap_* is explicit wraparound arithmetic.Operators
Shadowing is legal and warned about. A local shadowing a field or an outer local is a warning whenever the checker runs (strict or warn); warnings never gate.Functions
switch breaks by default; write fallthrough; to fall through. do { } while (c); is C++'s: continue reaches the condition.Control Flow
let is var, and words from other languages (virtual, delete, goto, extends, constexpr, const_cast, ===, function*, &x, a typed catch) are parse errors that name the JaiScript spelling. const itself is a keyword; only C++'s other places for it (a const method, a const return type, array<const int>, shared_ptr<const T>) raise, each with its own text (the table below).Language Bridges
Reading a missing map key returns null and does not insert; m[k] = v inserts; get(k, default) is the explicit fallback.Maps
String mutators mutate the receiver (to_upper, trim, …) and return it for chaining; + never mutates.Strings
Budget and memory cap are terminal. The execution budget cannot be caught by script; the memory cap gives one catchable raise, then goes terminal.Safety Rails
A .jaibite carries its format version. The version moves whenever the parser's output changes, and a bite loads only in a build with the same version. The automatic disk cache silently reparses a cache file of another version or of another source text; an explicit jaibite_load of another version fails with jaibite: unsupported format version N (expected M), M being the build's version. Proofs are never serialized and are re-derived on load.jaibite

Diagnostics as you read them​

There are two shapes of error, and knowing which one you are looking at tells you when it happened.

The static listing (before anything runs)​

jaiscript
int x = 5;
x = "hello";                  // typed to typed: the load fails, nothing runs
print("never printed");
output
jaiscript: static check failed:
<script>:2:3: error: cannot assign 'string' to 'x' declared 'int' (declared at 1:5)
    x = "hello";                  // typed to typed: the load fails, nothing runs
      ^

The shape is FILE:LINE:COL: error: MESSAGE, then the source line, then a caret. FILE is the unit's resolved path for a file (the root file, an import, an include) and <script> for source text handed to execute(). Messages are lowercase, except the few that reuse a run-time text and keep its capital (Cannot access private member 'x' of class 'C', Cannot access private constructor of class 'C'); a message about a declared name ends with that declaration's position, (declared at LINE:COL). A failed load hands the host one jai::static_check_error whose what() is the whole listing (sorted by location, deduplicated, capped at 100), and nothing has executed. Warnings (shadowing, a copying cast) use the same shape with warning: and never stop the load. A syntax error reads the same way under parse failed: instead, thrown as jai::parse_error: the parser lists every syntax error it meets in source order (it resumes after each), and no check runs until the source parses. The runner lists them under --warnings, and a host reads them from last_check_diagnostics(). Common messages:

MessageMeaning
cannot assign 'string' to 'x' declared 'int' (declared at 8:3)a typed store the table rejects
cannot initialize 'c' declared 'char' with 'int'the same at a declaration (char c = 65)
advance() cannot store the yielded 'string' into 'int'a coroutine handle's advance(v) whose declared yield type v's type does not take (advance() writes the yielded value through its argument; pass a variable for a literal)
'sleep' cannot be called in a parallel bodya sleep in a parallel_for body or in a lambda handed to parallel_transform or as parallel_reduce's step (its combine is ordinary code); a sleep in a destructor or in hot_reload_migrate is a warning that it blocks there
nesting too deep for the host thread's stackthe source nests deeper than the parser or the checker can walk on the host thread's stack (run-time twin)
redeclaration of 'x' in the same scope (declared at 3:9)a block or a parameter list declares one name twice (a program's top level may declare a name again: the later declaration stands for it, as a re-run's does); redeclaration of member 'x' in class 'C' (declared at 2:15) for a class's fields, or a field and a method
use of undeclared identifier 'G'no declaration anywhere in the program; extern if the host provides it
no member 'zz' in class 'Vec2'the receiver's class (a value's, a handle's or a reference's) is fully known and has no such field or method: cast<T&> if it lives on a subtype
cannot compare 'int' with 'string' using '==': the types have no comparison (it is always false); convert one operanda comparison between two declared types no conversion or comparison operator relates (!= is always true; an ordering raises Invalid operands for < operator); with a class operand the text names the class that declares no comparison operator
cannot compare 'int' with null using '==': 'int' is never null (it is always false); compare with a value of its type, or declare the slot var to hold nullnull against a typed slot that never holds it: int, float, char, bool, string, utf8_string, an array, a map or a math value (a call's container result can be null and is not checked); class values, handles, functions and var compare with null
'fib' expects 1 argument(s), got 2 / argument 1 of 'fib': cannot pass 'string' to 'n' declared 'int'arity and per-argument checks against a callee the checker can see
'g' is already defined (declared at 1:10)a second function with the same signature; overloads must differ in parameter types or count
'G' is used before its declaration (line 2): top-level code runs in order, so declare it above the use or make the use after it · 'f' is called before its declaration (line 2): ...top-level code that reads a variable, a class or a function this file declares only further down (a function or class body may use them anywhere); a name another unit or an earlier run already defined is there
reference variable 'r' must be initialized: a reference names a variable from its declaration ona T& declaration without an initializer; nothing loads
argument 1 of 'bump': cannot bind 'float' to 'x' declared 'int&'a T& parameter binds a variable of exactly T (a class T or a subclass); a const T& parameter also takes what a T parameter takes, bound as a temporary
method 'speak' redefines 'Cat::speak': mark it 'override'a method that redefines a base class method says override (and method 'm' marked as override but no base class method found when there is none)
cast<C>(h) copies the instance - use cast<C&>(h) to alias it or cast<shared_ptr<C>>(h) to share ita warning: the cast is legal, but probably not what you meant
map keys of class 'C' compare by identity: declare operator<=> (or operator<) or hash() to key its values by valuea warning, from the checker at the subscript and once per class from the run time: a value of C keys a map by identity
No bound 'int * Meter': bind operator "*" as (int, Meter) on Meter, or convert the operanda warning: a number on the left of a bound class's arithmetic operator the class binds no reflected form of
'f' declared to return 'int' may reach the end of its body without returninga primitive, string or reference return is required on every path
'heal' is a method of Player; call it with p.heal() or bind it with reflect::bind(p, "heal")a method read without a call
'bump' writes through parameter 'x' (line 1); pass a variablea literal or temporary passed to a T& parameter the body writes through
argument 3 of 'parallel_reduce': step 'one' must take two parameters (acc, x)a declared step or combine of another arity (argument 4 ... combine ... (a, b) alike)
cannot bind a reference to a conditional of 'int' and 'float': its arms must have one typea T& argument, declaration, default, return or cast<T&> over a conditional whose arms name storage of two types (one arm type binds the chosen variable)
cannot bind non-lvalue default to reference parameter 'p'a T& parameter's default that can never name storage (int& p = 5); a const T& one binds a temporary, and a coroutine binds a copy
cannot assign to a call result: 'get' returns by value · cannot assign to x++: it yields the old value · cannot assign to a literalan assignment target that names no storage (the runtime texts below, lowercased)
regex: missing ')' to close the group at position 1a regex(...) whose literal pattern or flags do not compile; the text is the run time's (Regular expressions)
'x' is already defined inline; declare it 'inline int x;' to share ita plain declaration of an inline variable another unit shares
argument 1 of 'run_sql' is not a constant: <the host's refusal>an argument the host declared constant (constant arguments) is not a literal, a const of one, or a ternary, + or array literal of them

Run-time errors (what catch sees)​

jaiscript
var dynamic = "hello";        // var: the checker cannot see what it holds
try {
    int x = dynamic;          // so this fails at run time, where catch sees it
} catch (e) {
    print("caught: {}", e);
}
output
caught: Cannot initialize 'x' (declared int) with string

Run-time texts lead with the action that failed, or with the builtin or member that refused it; names are quoted. catch (e) receives the text alone, and an uncaught error's what() on the host side is the same text alone, with no category or location appended (only an error raised without a text of its own reads as its category's name). Every text is byte-identical on both backends. The catalog, by family:

Null receiver​

TextWhenFix
Cannot access 'x' on null ('v' is null)A member read goes through a variable that holds null.Check the variable for null first, or give it an object.
Cannot call 'push' on null ('v' is null)A method call goes through a variable that holds null.Check the variable for null first, or give it an object.
Cannot call 'x' on null ('f(...)' returned null)A method call goes through a call result that is null.Check the result for null before calling a method on it.
Cannot access 'x' on nullA member read goes through null where the receiver has no name.Check the receiver expression for null first.

No such member​

TextWhenFix
Vec2 has no member 'x'A member access names nothing the class or its bases declare.Correct the name, or declare the member in the class.
int has no member 'foo'A member access on a builtin value names no method of its type; every builtin type reads alike (string has no member 'foo').Call a method the type has (Builtin methods).
map has no member or key 'foo'A dot access on a map names neither a map method nor a key the map holds.Insert the key before reading it, or correct the name.
parent class Base has no member 'x'A super::name call names a member the base class does not declare.Name a member the base class declares.
class C has no static member 's'C::s names no static of the class or its bases.Declare the static in the class, or correct the name.
Cannot call 'helper' while 'A' is under construction: only a class derived from it declares itA constructor or field initializer calls what only a derived class declares.Declare the method in the class under construction, or call it once construction finishes.
Cannot call 'helper' while 'A' is under destruction: only a class derived from it declares itA destructor calls what only a derived class declares.Declare the method in the class being destroyed, or do that work in the derived destructor.
Cannot assign to 'x': Vec2 has no such memberA store names a member the class does not declare.Declare the field in the class, or correct the name.
Cannot assign to member of non-object typeA member store goes through null or a primitive.Store into an object: assign one first, or check for null.
'C' is a class; use C::sA dot follows a class name.Spell a static member C::s.
No value 'v' in enum 'E'E::v names no value the enum declares.Use a value the enum declares.

Not a function​

TextWhenFix
Cannot call 'x': it is an int, not a functionA call names a variable that holds a value other than a function.Call a function, or rename the variable that hides it.
Cannot call 'f': it is null, not a functionA call names a variable that holds null.Give the variable a function or lambda before calling it.
Cannot call a value of type int: it is not a functionA computed callee (an element, a call result) is not a function.Call only a value that holds a function.
Cannot call null: it is not a functionA computed callee is null.Check the value for null before calling it.

Arity and overloads​

TextWhenFix
'fib' expects 1 argument, got 2A call passes more or fewer arguments than the function takes.Pass the declared number of arguments, or give the parameters defaults.
lambda expects 1 argument, got 0A lambda is called with more or fewer arguments than it declares.Pass as many arguments as the lambda declares.
No overload of 'dot' takes (int, string)No overload of the function accepts the argument types.Pass arguments an overload takes, or declare one that takes these.
No constructor of 'Vec2' takes (string)No constructor of the class accepts the arguments.Pass arguments a constructor takes, or declare one that takes these.
No constructor found for class 'X'new X() where X names no class.Name a defined class, or define or import it first.
Ambiguous call to 'hit' with (int, int): 'hit(int, float)' and 'hit(float, int)' match equallyTwo overloads match the arguments equally well.Convert an argument (cast<float>(x)) so one overload matches best.
'f(var& x)' differs from 'f(var x)' (line 1) only by reference; overloads must differ in parameter types or countTwo overloads differ only by reference; the text names the second definition first, then the first one's line.Give the overloads different parameter types or counts, or keep one.
Too many initializers for 'Bare': it has 1 field, got 2Aggregate initialization passes more values than the class has fields.Pass at most one value per field.
Base 'Cat' has no default constructor; call super(...)A constructor does not call super(...), and the base has no default constructor.Construct the base in the initializer list: : super(args).
Class 'Car' must construct base 'Radio': it has no default constructorAn initializer list leaves unnamed a later base that has no default constructor.Name that base in the initializer list with its arguments.
Class 'Car' has no base 'Wheel' to constructAn initializer list names a class that is not a base.Construct only the class's own bases there.
Class 'Car' constructs base 'Engine' twiceAn initializer list names one base twice.Construct each base once.
No matching parent constructor found for super()super(...) passes arguments to a base that declares no constructor.Drop the arguments, or declare a base constructor that takes them.
push() takes exactly one argumentA builtin method gets the wrong number of arguments; every one reads NAME() takes ....Pass the arguments the method lists (Builtin methods).
size() takes no argumentsA builtin method that takes nothing gets arguments.Call it with no arguments.
remove() argument must be an integerA builtin method's argument has the wrong type; every one reads NAME() argument must be ....Pass the type the method lists.
math::wrap expects 3 argumentsA math function gets the wrong number of arguments; every one reads math::NAME expects ....Pass the arguments the function lists in the API signatures.
math::sqrt expects a numeric argumentA math function gets an argument that is not a number.Pass an int or a float.

The typed boundary​

TextWhenFix
Cannot pass string to parameter 'n' of 'fib' (declared int)An argument does not convert to its typed parameter.Pass the declared type, or convert the value first.
Cannot return string from 'f' (declared -> int)A function returns a value its declared return type does not take.Return the declared type, or change the declaration.
Cannot assign string to 'x' (declared int)A store into a typed variable holds a value of another type (a shared_ptr<T> variable takes a handle to T or a class derived from it, a weak_ptr never; a weak_ptr<T> keeps T through every store).Assign the declared type, convert first (weak.lock() for a shared_ptr), or declare the variable var.
Cannot initialize 'x' (declared int) with stringA typed declaration's initializer has another type.Initialize with the declared type, or convert first.
Cannot assign string to field 'f' of C (declared int)A store into a typed field holds a value of another type.Assign the field's declared type.
Cannot assign string to static 's' of C (declared int)A store into a typed static holds a value of another type.Assign the static's declared type.
Cannot convert B to A (no suitable single-argument constructor)An object of class B reaches a slot typed A, and A has no constructor taking a B.Pass an A, or give A a constructor that takes a B.
Cannot store string in array<int>A store into a typed array holds another element type.Store the array's element type.
Cannot initialize 'r' (declared float&) with intA typed reference binds a value of another type.Bind a variable of the declared type, or declare the reference auto&.
Cannot yield string from 'g' (declared -> int)A coroutine yields a value its declared type does not take.Yield the declared type.
Cannot initialize 'w' (declared weak_ptr<Foo>) with FooA weak_ptr is initialized from a value rather than a handle.Initialize it from a shared_ptr<Foo>.
Cannot pass string to an int parameterA parameter's type check fails where no parameter name is known.Pass the declared type.

Numeric conversion​

TextWhenFix
Cannot convert float to integer: non-finite or out of rangeA NaN, an infinity or a float outside the int64 range reaches an int slot, cast<int>, math::itrunc or math::ifloor; the destination is not written.Check that the float is finite and in range before converting it.
Cannot store 256 in array<uint8> (0..255)A store into an array<uint8> is outside 0..255.Store values in range, or mask with & 255.
Cannot store -129 in array<int8> (-128..127)A store into an array<int8> is outside -128..127.Store values in range.

Containers​

TextWhenFix
Cannot push 'string' to array<int>push adds a value of another type to a typed array.Push the array's element type.
Cannot assign 'string' to element of type 'int'An element store holds a value of another type.Store the element type.
Cannot use 'int' as a key of type 'string'A typed map gets a key of another type.Use the map's declared key type.
hash() of a map key must return an intA class that keys a map through hash() returns something other than an int from it.Return an int from hash().
Array 'auto' requires homogeneous elements - type mismatch at index 3An auto array literal mixes element types.Make the elements one type, or declare the array var or array<var>.
Map 'auto' requires homogeneous valuesAn auto map literal mixes value types.Make the values one type, or declare the map with var values.
Cannot iterate int: not an array, map, string or coroutineA range-for walks a value that is not iterable.Iterate an array, map, string or coroutine.
Cannot bind array to [k, v]: a structured binding takes a map entry or a value with first and secondA structured binding gets a value that is neither a map entry nor has first and second.Bind a map entry, or destructure an array with auto [a, b] = arr.
map() requires a function argumentmap() gets something other than a function.Pass a function or lambda.
Cannot insert 'string' into array<int>insert adds a value of another type to a typed array.Insert the array's element type.
Cannot insert at index 9 of an array of size 5insert gets an index past the end.Insert at an index from 0 to size().
Cannot reduce an empty array without an initial valuereduce runs on an empty array with no initial value.Pass an initial value.
Destructuring requires an array on the right-hand sideauto [a, b] = value gets a value that is not an array.Destructure an array.

Handles and references​

TextWhenFix
reset(h) takes a handle (new T(), make_shared<T>() or a shared_ptr), not Cp.reset(h) with a value or a primitive for h (a load error when the checker sees it).Pass a handle: p.reset(new C()), or assign a value into the object with p = C().
Cannot reset shared_ptr<C> to a shared_ptr<U>p.reset(h) where h's class is not p's (or, for a var, the held object's) class or a subclass of it.Reset to a handle of the class or a class derived from it.
reset() takes at most one argumentA shared_ptr's reset called with two or more arguments; a weak_ptr's raises reset() takes no arguments for any.Call reset() or reset(h).
Cannot initialize shared_ptr directly from weak_ptrA shared_ptr is initialized from a weak_ptr.Initialize it from weak.lock().
Cannot create weak_ptr from value-semantic object. Use shared_ptr<T>.weak_ptr<T>(v) of a value.Make the object with new and take the weak_ptr from its handle.
Cannot pass non-lvalue to reference parameterA temporary (a literal, the result of a call that returns by value) reaches a & parameter that needs a variable, where the checker could not reject it at load: one the function writes through, or any reference parameter of a lambda called through a variable. A & parameter the function only reads binds a temporary (Value Semantics). A call returning T&, a script function's or a host's, is an lvalue and binds.Store the value in a variable and pass that.
Cannot bind non-lvalue default to reference parameter 'p'A reference parameter's default names no storage; it raises at the call that uses the default.Pass an argument, or give the parameter a default that names a variable.
Cannot take reference of non-lvalue expression (...)A reference that is not const binds an expression that names no storage; the parenthesis lists what a reference binds.Bind a variable, a field or an element, or declare the reference const T&, which holds a temporary for as long as the reference lives.
Function with reference return type must return an lvalue referenceA function returning T& returns an expression that names no storage: a temporary, a by-value call's result, a map key with no entry (a read never inserts).Return a variable, a field, an element or an existing entry, or return by value.
Cannot take reference of undefined variableA reference names a variable that does not exist.Declare the variable first, or correct the name.
Reference variable must be initializedA reference is declared without an initializer (int& r;).Initialize it with the variable, field or element it names.
Reference to a removed array elementA reference to an array element is used after the array shrank past it (pop, erase, clear).Bind the reference again after shrinking the array, or keep the index instead.
Reference to a removed fieldA reference to an instance's field is used after a hot reload removed the field from the class.Bind the reference again after the reload.
Reference target is nullA reference binds, or is read through, a reference that has no target.Bind the reference to a live variable, field or element.
Reference to a removed map entryA reference is used after its map entry was erased (array element and field alike).Bind the reference again after the removal, or keep the entry while the reference lives.
Cannot iterate a string by reference: its chars are not lvalues (use for (auto c : s))for (auto& c : s) over a string.Iterate by value: for (auto c : s).
A host function returning std::weak_ptr<T> is not supported; return shared_ptr<T>A script calls a host function whose binding returns std::weak_ptr<T>.Return shared_ptr<T> from the host function.
Cannot assign to a reference to 'C': its binding has no operator=An assignment goes through a reference to a host object whose binding does not bind "=".Bind "=" for the class on the host side, or assign its fields one by one.

Const​

TextWhenFix
Cannot assign to 'MAX': it is read-onlyA store targets a const variable.Drop const from the declaration, or store into a copy.
Cannot assign to 'p.x': it is read-only ('p' is const)A field store goes through a const object.Store into a non-const copy, or drop const.
Cannot call 'bump' on 'c': 'c' is const and 'bump' writes its receiver (line 5)A method that writes a field is called on a const object.Call it on a non-const copy, or drop const.
Cannot call 'push' on 'xs': 'xs' is const and 'push' changes itA builtin method that changes its container is called on a const one.Call it on a non-const copy, or drop const.
Cannot bind 'r' (declared int&) to const 'a'; declare it const int& or auto&A non-const reference binds a const variable.Declare the reference const int& or auto&.
Cannot pass const 'a' to parameter 'x' of 'inc': 'inc' writes through it (line 1)A const variable is passed to a reference parameter the function writes.Pass a non-const copy, or stop the function writing the parameter.
Cannot cast const 'a' to int&: cast<int>(a) copies itA reference cast names a const variable.Cast to a value (cast<int>(a)) for a copy.
Cannot share const 'p' as shared_ptr<P>; copy it into a variable firstA const value is shared where a shared_ptr<P> is wanted.Copy it into a non-const variable and share that.
Cannot return const 'a' from 'f' by reference (declared -> int&); return it by valueA function declared to return a reference returns a const variable.Return it by value.

Assignment targets​

TextWhenFix
Cannot assign to a literalThe left side of an assignment is a literal.Assign to a variable, a field or an element.
Cannot assign to an array literalThe left side of an assignment is an array literal (a map literal alike).Assign to a variable, or destructure with auto [a, b] = arr.
Cannot assign to a call result: 'get' returns by valueA store targets the result of a function that returns by value.Return a reference (-> T&) from the function, or store into a variable.
Cannot assign to x++: it yields the old valueA store targets a postfix increment or decrement.Assign to the variable itself.
Cannot assign to the result of '+': it is a temporary valueA store targets an operator's result.Store into a variable.
Cannot assign to the result of cast<float>(x): it is a temporary value (cast<float&>(x) names the variable)A store targets a value cast.Cast to a reference (cast<float&>(x)) to store through it.
Cannot assign to 'x': it is read-onlyA store targets a getter-only host property or a host namespace entry.Store through the host's setter, or keep the value in a script variable.
Cannot assign to a conditional: its arm '5' names no storageA store targets c ? a : b where an arm is not a variable, field or element.Make both arms name storage, or assign in an if.
Cannot assign to 'this'A store targets this.Assign the fields one by one.
Cannot assign to this expression: it names no storageThe left side of an assignment names no variable, field or element.Assign to a variable, a field or an element.
Cannot assign to rvalue expressionA store, compound assignment or ++ targets an element of a temporary.Store the temporary in a variable and change that.
Cannot assign to a member of a temporarymake().x = 1 on a class or math value returned by value, a constructor call or an operator's result.Store the value in a variable and change it there.

Methods as values​

TextWhenFix
'heal' is a method of Player; call it with player.heal() or bind it with reflect::bind(player, "heal")A script method is used as a value without a call.Call it, or bind it with reflect::bind.
'push' is a method of array; call it with arr.push() or wrap the call in a lambdaA builtin method is used as a value without a call.Call it, or wrap the call in a lambda.

Casts​

TextWhenFix
Cannot cast 'X' to 'T'cast<T> gets a value that does not convert to T.Cast only between convertible types.
Cannot cast 'int' to 'char'cast<char> gets an int outside 0..255.Cast an int from 0 to 255.
Cannot cast 'null' to 'T&'A reference cast gets null.Check for null before the cast.

Names​

TextWhenFix
Undefined variable 'x'A name the checker could not rule on (var-typed paths, dynamic imports) names nothing at run time, or top-level code reads a global, constructs a class or takes a function as a value before its declaration (top-level order).Declare the name, or correct its spelling.
'x' is declared extern but never defined (an 'inline' declaration creates it)An extern variable is used and nothing defines it.Define it (or have the host register it), or declare it inline.
'f' is declared extern but never definedextern function f; is used and nothing defines f.Define the function, or have the host register it.
'x' is already defined inline; declare it 'inline int x;' to share itA plain definition meets an inline one of the same name.Declare it inline too.
'x' is already defined (not inline) in <file>; declare every definition 'inline' to share itAn inline definition meets a plain one in another file.Declare every definition inline.
Undefined variable 'x' (not a field of 'this')A name inside a method is neither a local, a field of the class nor a global.Declare the name, or correct its spelling.
Class 'C' not foundA class name used at run time names no defined class.Define or import the class first, or correct the name.
reflect::construct: unknown class 'Nope'reflect::construct gets a name that is not a class.Pass the name of a defined class.
Cannot capture undefined variable 'x'A lambda's capture list names a variable that does not exist.Declare the variable first, or correct the name.
Lambda capture list does not capture enclosing name 'v'; name it, or use [this], [=] or [&]A lambda body uses an enclosing local its capture list leaves out.Name it in the capture list, or use [this], [=] or [&].
Namespace 'jai' is reserved for the engine (C++-registered only)A script declares into the jai namespace.Use another namespace name.
'this' can only be used inside methodsthis appears outside a method.Use this only in methods, constructors and destructors.
Cannot call super() - class has no base classsuper(...) runs in a class that has no base.Remove the call, or give the class a base.

Definitions​

The strict checker reports the class texts below at load, and the method texts with a lowercase first letter.

TextWhenFix
Base class 'X' not foundA class derives from a name that is not a defined class.Define or import the base first, or correct the name.
Class 'D' inherits 'A' through both 'B' and 'C' (diamond inheritance is not supported)A class reaches one base through two paths.Restructure the bases so each is reached through one path.
Method 'm' marked as override but no base class method foundA method marked override overrides nothing.Remove override, or correct the name to a base method's.
Method 'm' redefines 'Base::m': mark it 'override'A method redefines a base method without override.Mark it override, or rename it.
'g(int a)' is already defined (line 1)One run of one program text defines a second function or method of the same signature; another execute or a re-run unit replaces it instead.Rename one, or change its parameters.
Function 'f' already exists in namespace 'ns'. Use 'override' keyword to replace it.A namespace function of an existing signature is defined again without override.Rename one, or mark the replacement override.
'f' is already defined with parameters ()A reopening of a namespace in the same file redefines one of its functions; the strict checker reports it at load.Define the function once, or rename one.

Arithmetic​

TextWhenFix
Integer overflow in '+'An int result leaves the int64 range ('-', '*', '/' and unary '-' alike).Use smaller values or a float, or math::wrap_add for wraparound on purpose.
Integer overflow in '+='A compound assignment's int result leaves the int64 range, whatever its target (a variable, a field, an element, a map entry; '-=', '*=' and '/=' alike).Use smaller values or a float, or math::wrap_add for wraparound on purpose.
Integer overflow in '++'++ steps an int past the int64 range ('--' below it), on any target.Check the bound first, or step a float.
Bitwise & requires integer operands& gets an operand that is not an int (| and ^ alike).Convert the operands to int (cast<int>).
Left shift requires integer operands<< gets an operand that is not an int.Convert the operands to int.
Right shift requires integer operands>> gets an operand that is not an int.Convert the operands to int.
Bitwise NOT requires integer operand~ gets an operand that is not an int.Convert the operand to int.
Unary minus requires numeric operandUnary - gets an operand that is not a number.Negate an int or a float.
Division by zero/ or /= divides by zero, int and float alike.Check the divisor before dividing.
Modulo by zero% or %= takes a zero divisor.Check the divisor first.
Shift amount must be between 0 and 63A shift count is negative or above 63.Keep the shift count from 0 to 63.
Cannot apply '+=' to int and stringA compound assignment gets operand types it has no rule for (every compound and destination).Convert an operand so the types match.
Cannot apply '++' to string++ or -- targets a value that is not a number or a char.Increment an int, a float or a char (which promotes to its int).
Invalid operands for + operator+ gets operand types it has no rule for, and no user operator+ takes them.Convert an operand, or declare the operator in the class.
No bound 'Money + int': bind operator "+" taking int on Money, or convert the operandA bound C++ class declares the operator, but none of its bound overloads takes the other operand.Bind an overload of the operator that takes that type, or convert the operand.
No bound 'int - Money': bind operator "-" as (int, Money) on Money, or convert the operandA number or other non-class value is on the left of a bound class's operator, and the class binds no reflected form that takes it (for a comparison the text asks for "<=>" taking int on Money).Bind the reflected form, .method("-", [](int64_t l, const Money& r) { ... }), or convert the operand.
Invalid operands for < operator< compares types it has no ordering for, and no user operator takes them.Compare values of one comparable type, or declare the operator in the class.

Vectors​

TextWhenFix
math::vec3 expects one vector of 3 componentsA one-argument vector constructor gets a lone scalar, or a vector of another dimension.Pass a vector of the same dimension, or every component.
math::color expects one colormath::color with one argument gets something other than a color.Pass a color, or every component.
math::vec4 expects a narrower vector followed by scalars that complete 4 componentsA vector built from a narrower vector gets the wrong number of scalars after it.Follow the vector with exactly the missing components.
math::vec2 expects numeric componentsA vector constructor gets a component that is not a number.Pass ints or floats.
math::vec3 expects 0 to 3 argumentsA vector constructor gets too many arguments (every vector type alike, named by its own type and count).Pass at most one argument per component.
Integer overflow: the value does not fit an int32 componentAn int bound for an ivec2 or ivec3 component (a constructor, a component store, an arithmetic result, a loaded archive) is outside the int32 range. A build with JAISCRIPT_WRAP_ON_OVERFLOW wraps it instead.Keep the components within int32, or use a float vector.

Indexing​

TextWhenFix
Array index 5 out of bounds for array of size 3An array index is negative or not below size().Index from 0 to size() - 1.
Array index must be an integerAn array subscript is not an int.Index with an int (cast<int> a float).
String index 4 out of bounds for string of size 4A string index is negative or not below size().Index from 0 to size() - 1.
Subscript can only be used on arrays, maps, or types with [] operator[] follows a value that is not a container and declares no operator[].Subscript a container, or declare operator[] in the class.
Cannot pop from empty arraypop runs on an empty array.Check size() before popping.
Cannot get front of empty arrayfront runs on an empty array (back alike).Check size() first.
front() called on empty stringfront() runs on an empty string (back() alike).Check size() first.
String index must be an integerA string subscript is not an int.Index with an int.
Cannot assign int to a string character (it takes a char or a string)A string element store holds a value that is neither a char nor a string.Store a char (cast<char>(n)) or a string.

Strings, bytes, time​

TextWhenFix
code() needs a string of exactly one UTF-8 charactercode() runs on a string that is empty or holds more than one character.Call it on a one-character string.
to_char expects 0..255, got 300to_char gets an int outside 0..255.Pass an int from 0 to 255.
string::repeat count cannot be negativestring::repeat gets a negative count.Pass a count of 0 or more.
Unsupported format spec ':q' in format stringA format() or print placeholder {:spec} / {n:spec} names a spec outside the subset (a template string's ${x:q} is the load-time Unsupported format spec ':q' in template string).Use the subset, [[fill]align][+| ][0][width][,|_][.precision][d|x|X|b|f|e|g]; write {{ for a literal brace.
format spec ':.2f' does not apply to string valueA spec's sign, zero padding, grouping, precision or type meets a value it does not fit (a string under a numeric spec, a float under x).Give the spec a value of the kind it formats, or convert the value first.
to_bytes(4) out of bounds for string of size 3to_bytes gets an offset or count past the end of the string.Keep the offset and count within size().
string::from_bytes expects bytes (0 to 255): element 3 is 256string::from_bytes gets an element outside 0..255.Pass bytes from 0 to 255.
Cannot read int32 at position 10: 4 bytes wanted, 2 remainA binary reader reads past the end of its data.Check the bytes remaining before the read.
Cannot write 256 as uint8 (0 to 255)A binary writer gets a value outside its width's range.Write values in range, or use a wider width.
Cannot read null-terminated text at position 0: no NUL terminator in the remaining 3 bytesA reader's text() finds no NUL before the end of its data, or before the engine's binary scan limit (the text then names the limit).Read a counted field with text(n), or check the data holds a terminator.
Cannot write text: the text holds a NUL at byte 1A writer's text(s) gets a text with a NUL inside. text(s, n) raises Cannot write text: the text is 6 bytes, longer than text(3) for a text longer than its field, and Cannot write text(s, -1): a count cannot be negative for a negative count.Remove the NUL, or give the field room for the text.
Cannot pack field 'pos': the map has no 'pos'A layout packs a map that lacks one of its fields.Give the map every field the layout names.
binary::layout expects name, width pairs, got 3 argumentsbinary::layout gets an odd number of arguments (the reader, writer and layout texts in full: Binary data).Pass a name and a width for every field.
time::date: month must be 1 to 12, got 13time::date gets a month outside 1..12.Pass a month from 1 to 12.
web::url_encode expects a stringA web:: function gets an argument that is not text (every one reads web::NAME expects a string; web::build_query expects a map).Pass the kind the web:: library names.
web::build_query: the value of 'm' is a mapA web::build_query value is neither text, a number, a bool, null nor an array of those (... is an array holding more than text, numbers and bools, ... is not text, a number, a bool or an array of them).Pass text, numbers and bools, or an array of them.
web::parse_url: 'http://example.org:99999/' has a malformed host or portThe URL's port is not a number from 0 to 65535, or an IPv6 host's bracket does not close.Correct the URL, or catch the error for input you do not control.
spider::catch: no HTTP transport installed: the host provides one with engine::http_transport()A spider:: call runs in an engine whose host registered the client and installed no transport (every call reads spider::NAME: ...).Install one with engine::http_transport.
spider::catch: http://127.0.0.1:1/: cannot connect to 127.0.0.1:1The exchange failed: the transport's reason, or timed out after N s, more than N redirects, a redirect from https to http (URL) is refused, redirected to 'L', which is not an http or https URL.Catch it where the network may fail; raise the timeout or redirects option when the server is slow or far.
spider::catch: 'ftp://example.org/' is not an http or https URLThe URL is relative, of another scheme, or has no host.Pass an absolute http or https URL.
spider::catch: unknown option 'retries' (method, headers, timeout, type, redirects)An option is unknown or of the wrong kind (option 'method' takes GET, HEAD, POST, PUT, PATCH, DELETE or OPTIONS, option 'timeout' takes a number of seconds above 0, option 'redirects' takes an int from 0 to 100, 'N' is not a header name, header 'N' holds a line break or NUL in its value, header 'Host' is the transport's to set (Content-Length, Transfer-Encoding and Connection too), type "form" takes a map of fields).Use the options HTTP with spider:: lists.
Response.header: 'X Y' is not a header name, or its value holds a line break or NULspider::Response.header(name, value) was given a name that is not an HTTP token, or a value holding CR, LF or NUL.Use a token name (letters, digits and !#$%&'*+-.^_`|~) and a one-line value.
time::date::add_days expects a whole numberA calendar step (add_days, add_months, add_years) gets a fraction, an infinity or NaN.Pass a whole number.
time::date::add_days is out of rangeA calendar step or a duration in add_* is past ±1015 (days, months, years, or the milliseconds a duration comes to).Step by a smaller amount.
time::date::format: unknown placeholder '${bogus}'A date format string names a placeholder the formatter does not know.Use a placeholder from Time.

Regular expressions​

TextWhenFix
regex: missing ')' to close the group at position 0A pattern does not parse; every such text reads regex: PROBLEM at position N, N counting characters from 0 (the texts in full: The syntax).Correct the pattern at that position.
regex: the backreference '\1' at position 3 is not supported: matching runs in linear time, without backtrackingThe pattern uses a construct that needs backtracking: a backreference, a lookahead or lookbehind, an atomic group, a possessive quantifier or recursion, each named alike.Match more and test the parts in script, or capture and compare the groups.
regex: unknown flag 'g' (the flags are i, m, s and x)The flags hold a letter that names no flag (regex: flag 'i' appears twice for one written twice).Pass only i, m, s and x (find_all and replace cover every match).
regex: the class at position 0 holds 'é', a character of more than one byte, but a string matches by bytes (match a utf8_string, or write its bytes as \xNN)A pattern whose class holds a multi-byte character searches a string.Search a utf8_string, or name the bytes with \xNN.
regex: the pattern is too large to match a string (its program passes 250000 instructions)Counts multiply past the engine's program limit ((a{1000}){1000}).Use smaller counts, or a loop in script.
regex: the replacement names group 2 at position 0, but the pattern has 1 groupA replacement refers to a group the pattern lacks (regex: the replacement names group 'x' at position 0, which the pattern does not have by name; regex: a '$' at position 1 of the replacement starts no group reference (write $$ for a dollar sign) for a stray $).Refer to a group the pattern has; write $$ for a dollar sign.
regex: the replace function must return a string, got intA function replacement returns something other than text.Return a string (to_string(v)).
regex::match has no group 5 (it has 4 groups)A match is asked for a group past its last (regex::match has no group named 'day' for an unknown name).Ask for group 0 to m.size() - 1, or a name the pattern gives.
regex: search position 4 is outside the text (0 to 3)search(text, start) gets a start before 0 or past the end.Start between 0 and the text's size.
regex: search expects a string or utf8_string, got intA regex call gets something other than text to search (regex: the pattern must be a string, got int for the pattern).Pass a string or a utf8_string.

Random numbers and crypto​

TextWhenFix
random::next_int range is inverted (max < min)next_int(lo, hi) gets hi below lo (random::next_float range is inverted (max < min) for floats).Pass the bounds low first.
random::next_float bounds must be finitenext_float(lo, hi) gets an infinite or NaN bound.Pass finite bounds.
random::pick expects a non-empty arraypick gets an empty array or a value that is no array.Check the array's size first.
random::shuffle expects an arrayshuffle gets a value that is no array.Pass an array variable.
random::next_bytes count cannot be negativenext_bytes (or crypto::random_bytes, crypto::random_hex: crypto::random_bytes count cannot be negative) gets a negative count.Pass a count of 0 or more.
random::state expects 64 hex digits, not all zeroA store into state (a load included) is not 64 hex digits, or is all zeros.Store a value read from state.
crypto::random_int range is inverted (max < min)crypto::random_int(lo, hi) gets hi below lo.Pass the bounds low first.
crypto::random_bytes: the operating system's secure random generator failedThe system generator refused to answer (the text names the function: random_hex, random_int, password_hash, or random: ... for random()).Retry; the platform's entropy source is unavailable.
crypto::password_hash cost must be 4 to 31password_hash gets a cost outside 4..31.Pass a cost from 4 to 31 (12 by default).
crypto::password_hash: the password contains a NUL byteThe password holds a 0 byte, which bcrypt cannot read past.Reject or encode such passwords before hashing.

Files​

TextWhenFix
Failed to open include file: helpers.jaiAn include names a file that cannot be opened.Correct the path, or add the file.
Failed to open import file: creature.jaiAn import names a file that cannot be opened.Correct the path, or add the file.
parse failed: <path>:LINE:COL: error: MESSAGEA file a dynamic import(expr) reaches does not parse.Fix the parse error the message names in that file.
Path outside the confined roots: ../secret/creds.txtAn include, import, resolve_path or file_data finds its file only outside the folders the host gave confine_paths.Move the file under a confined root, or the host adds its folder.
include 'part.jai' includes itself or nests too deepA literal include inside a function body reaches a file it is already splicing, or splices more than 32 files deep (a parse error at the include's path).Break the cycle, or import the shared file.
import(...) takes paths: strings or arrays of stringsA computed import(expr) gets a value that is neither a string nor an array of strings.Pass the path as a string, or several in an array.
Insufficient privilege: filesystem.include, import or a file builtin runs on an engine whose host disabled the filesystem privilege.The host enables jai::filesystem, or the script leaves files alone.
Cannot read file 'saves/world.gqw': not foundThe runner's read_file, or crypto::sha256_file, cannot read the path (not a file, permission denied, cannot be opened, read failed).Correct the path, or check the file exists and is readable.

Archives​

TextWhenFix
Invalid shared_ptr ID: 4from_json, from_binary or from_base64 of a handle whose object the save never wrote.Load data a matching save wrote, unedited.

Exceptions​

TextWhenFix
Bare throw with no active exception thrown.throw; runs outside a catch block.Rethrow only inside catch, or throw a value.

Coroutines​

TextWhenFix
Cannot resume a completed coroutineA next() on a finished handle, or a next(n) step past the end.Check done() before calling next().
Coroutine is already runningA coroutine's own body resumes it.Resume a coroutine only from outside it.
next() takes 0 or 1 arguments ([count])next() gets more than one argument.Pass nothing, or a step count.
next() count must be an integernext(n) gets a count that is not an int.Pass an int count.
next() count must be at least 1next(n) gets a count below 1.Pass a count of 1 or more.
advance() takes 1 argument (the variable that receives the yielded value)advance gets no argument, or more than one.Pass the one variable the yielded value goes into: while (h.advance(v)).
Coroutine has not finished: result() is available once done() is trueresult() on a handle whose body has not run to its end.Drive it to the end first (while (h.advance(v)), a range-for), or test done().
result() takes no argumentsresult() gets arguments.Call it with no arguments.
done() takes no argumentsdone() gets arguments.Call it with no arguments.
coroutine_handle has no member 'resume'A coroutine handle is called with a method it lacks, such as resume.Resume it with next().

Sleep​

TextWhenFix
sleep expects a number of secondssleep gets a value that is neither an int nor a float.Pass the seconds as a number.

Parallel bodies​

TextWhenFix
parallel_for: TEXT at LINE:COLThe region refuses a body before the loop runs; parallel_transform: and parallel_reduce: lead those builtins' texts.Change the code at LINE:COL as TEXT says, or run the loop serially.
parallel_for: reference declaration would alias enclosing state 'g' (captured reads are read-only) at 4:12The body binds a reference to a variable from outside the loop, or to a member of one.Bind references to the element or to a local; read enclosing state by value.
parallel_for: assignment target is not allowed in a parallel body at 3:9The body assigns to, or binds a reference to, something that names no storage it can prove its own (a call result, a conditional; reference declaration target alike).Assign to the element, a local or out[i].
parallel_transform: first argument must be an arrayparallel_transform gets a non-array input (parallel_reduce: first argument must be an array alike).Pass an array.
parallel_transform: expected (array, fn) or (array, fn, weight_fn)parallel_transform gets another number of arguments (parallel_reduce: expected (array, init, step, combine) alike, under [[warn_mode]]).Pass the arguments the call takes.
parallel_transform: weight_fn must return a number (element 2)The weight function returns something other than an int or a float for that element (must return a finite non-negative number for a negative, infinite or NaN weight).Return a non-negative number for every element.
parallel_transform: weight_fn failed for element 2: TEXTThe weight function raised a run-time error for that element (its own throw arrives as thrown).Fix the weight function as TEXT says.
parallel_for: nested parallel regions are not supportedA parallel loop starts while another runs, from code the statement could not see (a host callback); parallel_transform: and parallel_reduce: alike.Start the inner loop after the outer one.
parallel_for: time:: calls are not allowed in a parallel body (engine-owned clock) at 3:41The body calls a time:: function.Read the time before the loop and capture the value.
parallel_for: random::seed cannot be called on captured 'loot' in a parallel body (seed before the loop: each element draws its own stream from it) at 5:46The body, or code it reaches, reseeds a random it captures.Seed before the loop. Each element draws its own stream from one draw of the captured generator, the same at every worker count (In parallel loops).
parallel_for: crypto::sha256_file cannot be called in a parallel body (it reads a file) at 3:9The body, or code it reaches, hashes a file.Hash the files before or after the loop; the other crypto:: functions run in a body.
parallel_for: method 'bump' on class 'Pt' is not admitted in a parallel body (only single-overload methods of the element's class or its bases) at 4:33The body calls a method the region cannot admit.Call only single-overload methods defined on the element's own class.
parallel_for: body writes enclosing state 'total' at 11:31The body writes a variable from outside the loop.Write only the element, or total with parallel_reduce.
parallel_for: argument passes enclosing state 'grid' to reference parameter 'g' of 'peak' (captured reads are read-only; 'peak' only reads it: declare it const array<int>& g) at 6:40The body passes a variable from outside the loop to a function's (or method's) non-const reference parameter, which the function never writes.Declare the parameter const T&: a const T& parameter takes enclosing state as a captured read.
parallel_for: argument passes enclosing state 'grid' to reference parameter 'g' of 'clear', which writes it (captured reads are read-only; pass a copy, or take it as const T& where it is only read) at 6:40The same for a parameter the function writes through.Pass a copy, or write the element (or an index write) instead.
parallel_for: argument passes enclosing state 'g' by reference (captured reads are read-only) at 3:9The body passes a variable from outside the loop to a C++ function's non-const T& parameter.Pass a copy; a C++ const T& parameter takes it as a read.
parallel_for: a counted loop must step its variable by a fixed amount toward a bound (write i++, i--, i += k or i -= k)A counted parallel_for steps its variable some other way (i *= 2, a call in the step, a step of another variable).Step by a constant or a name, or iterate an array of the indices.
parallel_for: a counted loop compares its variable with a bound (i < b, i <= b, i > b or i >= b)The condition is missing, tests another variable or uses another comparison.Compare the loop variable with its bound.
parallel_for: a counted loop declares one int variable (int i = a or auto i = a)The counted form declares a non-int, a reference, a const or more than one variable.Declare one int variable.
parallel_for: a counted loop must step toward its bound (i < b and i <= b count up, i > b and i >= b count down)A constant step moves away from the bound, so the serial loop would not end.Flip the step or the comparison.
parallel_for: a counted loop's step must not be zeroThe step is the constant 0.Step by a nonzero amount.
parallel_for: a counted loop's step of -2 never reaches its bound (the serial loop would not end)A step read from a name is zero or moves away from the bound, and the loop would run at least once.Give the step the bound's direction.
parallel_for: a counted loop's bound is float, not intThe start, bound or step is not an int (a load error when its type is declared).Convert it with int(...) before the loop.
parallel_for: the body writes the counted loop's variable 'i' (the loop steps it; copy it into a local to change it) at 3:40The body assigns the counted variable, or hands it to a reference parameter that writes it.Copy it into a local.
parallel_for: method 'scale' of 'Vec3' writes its object, and captured 'sun' is read-only (write a local copy, or the element) at 11:50The body calls a method that writes its object on a captured object (or on an object a captured one holds).Copy it into a local first (Vec3 s = sun;), or keep the state in the elements.
parallel_for: member store reaches captured state 'cam' (captured reads are read-only) at 4:45The body stores into a field of a captured object.Write a local copy or the element.
parallel_for: captured handle 'h' is read through its members and methods only (a worker reads its own view of the object) at 3:40The body uses a captured shared_ptr itself (compares, copies or passes it).Read its members, or capture the members you need.
parallel_for: element 1 reaches the object captured as 'h' (a captured object is read-only, and an element's lane may write it)An element of the range or a zipped array is, or holds, an object the body also reads through a captured name.Read the object through the element, or copy what the body needs before the loop.
parallel_for: captured name 'bad' is not value-semantic (an object holding a handle, a function, a var field or a C++ object)A captured object holds what no worker may copy (an object whose class runs a destructor, a script class over a C++ class, a C++ object inside a script object alike).Capture the fields the body reads.
parallel_for: member access reaches captured state 'm' (captured reads are read-only, and only a captured object has members) at 3:27The body reads a member of a captured value that is no object.Subscript a container: m["k"].
parallel_for: element 0 cannot be proven exclusive (only value-semantic shapes are: primitives, strings, value containers, flat value-class instances and distinct handles to them)The body binds elements in place but the region cannot prove them exclusive; it raises before the loop runs, without a position.Iterate value-semantic elements, or distinct handles to them.
parallel_for: zipped array 2 element 0 cannot be proven exclusiveThe same for an element of a zipped array.Zip value-semantic arrays, or distinct handles.
parallel_reduce: input element 0 cannot be proven exclusiveThe same for an element of a parallel_reduce input that holds objects.Reduce over value-semantic elements, or distinct handles.
parallel_reduce: input elements 0 and 1 hold the same object (an object the step reaches through a handle element belongs to one element)Two handle elements of the input name one object.Give each element its own object.
parallel_for: 'Tune::K' is no longer a constant (a parallel body reads only const statics of a primitive value)A class the body reads a constant of was redefined, and the member is no longer a static const of a primitive value.Keep the member static const, or read it outside the loop.
parallel_reduce: step names '__jai_reduce_xs', the reduction's own inputThe step reads the name the reduction gives its input.Read the element through the step's parameter.
parallel_for: elements 0 and 3 hold the same object (an object a body reaches through a handle element belongs to one element)Two handle elements name one object.Give each element its own object.
parallel_for: elements 1 and 2 share one 'random' in field 'brain' (an element owns the C++ values it holds: give each element its own, or capture one from outside the loop)Two elements hold one bound C++ object (a random, a time::date), in the named field or, without one, as an element of a value container. It raises before the loop runs.Give each element its own object, or draw from one random captured from outside the loop.
parallel_for: elements 2 and 5 write 'out' at the same index 4An index write out[i] = v gets one index from two elements.Give every element a distinct index.
parallel_for: body touches 'out' beyond the slot it writes, out[i] at 4:20The body writes out[i] and also reads out elsewhere.Touch out only at the slot the body writes.
parallel_for: body writes enclosing state 'out' at an index the body changes at 3:9The body writes out[i] and also changes i, directly or through a T& parameter that writes it.Leave the index unchanged in the body.
parallel_for: captured name 'out' is written at an index but is not an arrayThe body writes out[i] where out is not an array.Write by index only into an array.
parallel_reduce: step must take two parameters (acc, x)The step function does not take two parameters (combine must take two parameters (a, b) alike).Declare the step as (acc, x) and the combine as (a, b).
parallel_reduce: step must be a script function or lambdaThe step is not a script function or lambda.Pass a script function or lambda.
parallel_reduce: step takes its element by value (the elements are the input's, never written)The step takes its element by reference.Take the element by value.
parallel_reduce: body writes enclosing state 'total' at 3:40A step the loop rules refuse raises their text under its name.Fold into the accumulator, not into enclosing state.
parallel_for: body writes enclosing state 'next_id' in the constructor of 'Tick' at 2:46A construction in the body runs code that writes enclosing state (in a field initializer of 'T' and in the destructor of 'H' alike).Keep constructors, field initializers and destructors used in the body free of enclosing writes.
parallel_for: C++ class 'Sprite' is marked serial_only_constructor() and cannot be constructed in a parallel body at 3:48The body constructs a host class registered serial-only.Construct it before or after the loop.
parallel_for: class 'Glowing' has a C++ base 'Sprite' marked serial_only_constructor() and cannot be constructed in a parallel body at 4:49The body constructs a script class whose host base is registered serial-only.Construct it before or after the loop.
parallel_for: coroutine constructor of class 'Gen' is not allowed in a parallel body at 3:5The body constructs a class whose constructor is a coroutine.Construct it before or after the loop.
parallel_for: host function 'print' is marked serial_function() and cannot be called in a parallel body at 3:5The body calls a host function registered serial.Call it before or after the loop.
parallel_for: C++ static method 'Sound::play' is marked serial_static_method() and cannot be called in a parallel body at 3:5The body calls a host static method registered serial.Call it before or after the loop.
method 'm' was not admitted in this parallel body (admitted: single-overload public methods of the element's class or its bases touching only element, local, or captured-read state)A body refused while it runs raises the text alone; here a method call the region cannot admit.Call only the methods the text admits.
only direct field access on class instances is allowed in a parallel bodyThe body reads a class instance's member in a way other than a direct field read.Read fields directly.
only direct field stores on class instances are allowed in a parallel bodyThe body stores into a class instance in a way other than a direct field store.Store fields directly.
body writes enclosing state 'calls' in operator '+' of 'V' at 2:68The body applies a class operator whose body breaks the parallel body rules: the text is the rule's, as parallel_for: TEXT at LINE:COL gives it, without the prefix, and names the operator and its class.Make the operator follow the body rules, or run the loop serially.
converting constructor of 'T' was not admitted in this parallel body (the statement admits a conversion into a slot it can type)A conversion into a slot of class T would run its converting constructor where the statement could not tell the slot's type, so the construction was never walked.Construct it explicitly (T(x)) or convert into a slot the body declares.
A host call failed during a parallel regionA C++ function or method a parallel body calls threw: the body runs while the engine records no new text, so the exception's own text is not kept.Run the same call outside the loop to read the exception's text.
custom operator dispatch on class instances is not admitted in a parallel bodyThe body applies a class operator the statement never walked, such as the global [] operator.Apply the operator before or after the loop, or run the loop serially.
an assignment that changes an object's class is not admitted in a parallel bodyThe body stores a subclass instance into a slot that holds another class.Keep each object's class unchanged in the body.
C++ method 'publish' of 'Meter' is marked serial_method() and cannot be called in a parallel bodyThe body calls a host method registered serial.Call it before or after the loop.
C++ method 'add' of 'Meter' changes an object this parallel body did not make (only its const methods may run on it)The body calls a non-const host method on an object the body did not make.Call only const methods on it, or make the object in the body.
C++ property 'level' of 'Meter' is stored on an object this parallel body did not make (only an object it made takes stores)The body stores a host property on an object the body did not make.Store only on objects the body makes.
member 'extra' is stored on a shared object in a parallel body (a body writes its element, out[i] and the objects it made)The body stores into an object a host call returned that the program already holds, or into an object one of its fields holds.Change the object before or after the loop, or write the element, out[i] or an object the body makes.
method 'grow' cannot run on a shared object in a parallel body (an object reached through a host call takes only its C++ class's const methods)The body calls a script method, or a method of an object with no C++ object, on a shared object.Call it before or after the loop.
C++ call 'attach' takes a shared object that holds no C++ object in a parallel bodyThe body passes a shared object with no C++ object to a C++ function or method.Pass it before or after the loop.
member 'on_hit' of a shared object cannot be read in a parallel body (it holds a function or a weak_ptr)The body reads a field of a shared object that holds a function or a weak_ptr.Read it before the loop and capture the result.
a host call in a parallel body returned an object one of its iterations owns (an element, or an object a chunk made)A host call returns an object the region gave to one iteration.Reach the element through the loop variable.
a shared object in a parallel body cannot be copied by value: its C++ object has no copy functionThe body copies a shared object by value whose C++ class registers no copy.Hold it as a shared_ptr, or copy it before the loop.
parallel_transform: type shape was not pre-warmed at the region barrier (internal) - please report this body shapeA thread of a region builds a type the region did not prepare before its threads started (a library defect).Report the body; running the loop serially avoids it.
parallel_transform: name 'tally' was not pre-warmed at the region barrier (internal) - please report this body shapeA thread of a region needs a name the region did not prepare before its threads started (a library defect).Report the body; running the loop serially avoids it.

Script layers​

TextWhenFix
Cannot copy an instance of nocopy class 'C': hold it by reference (auto&/var&) or share it (shared_ptr)A copy of a nocopy class instance.Hold it by reference (auto&, var&) or share it with a shared_ptr.
Script layer 'C' was detached (script_layer_detach, or a move to another engine): this handle no longer reaches its objectA handle is used after its script layer was detached or moved to another engine.Stop using the handle after the detach, or take a new one.
extend: 'C' derives the C++ class 'B', which is not the object's C++ class 'O' or one of its basesextend(obj, C) names a class whose host base does not match the object.Extend with a class derived from the object's own host class or one of its bases.
extend: 'C' needs an object to extendextend(obj, C) or the host's eng->extend(obj, "C") gets null or a value that is not an object.Pass the object to extend.

Access​

TextWhenFix
Cannot access private member 'x' of class 'C'Code outside the class and its subclasses reads or writes a private member.Make the member public, or reach it through a public method.
Cannot access private constructor of class 'C'Code outside the class and its subclasses calls a private constructor.Make the constructor public, or construct through a public static function.

Limits (catchable)​

TextWhenFix
Maximum recursion depth exceeded - possible infinite recursionActive calls reach 256 on the interpreter or 250,000 on the VM by default (engine::max_call_depth(backend, depth)).Bound the recursion, or have the host raise the cap.
Out of memoryThe platform refuses an allocation a builtin asks for (crypto::random_bytes(n), s.repeat(n), s.pad_left(n) with an n past the memory there is).Ask for less, or have the host set engine::memory_cap so the request fails with the cap's text first.
Nesting too deep for the host thread's stackCode nests (expressions, statements, lambdas, a long chain of operators) deeper than the backend can walk on the host thread's stack. When the parser or the checker reaches that depth first, the load fails instead, with nesting too deep for the host thread's stack in the static listing.Flatten the nesting (name intermediate values, split a long chain), or run the engine on a thread with a larger stack.
Native stack exhausted before the call-depth cap: the host thread's stack bounds this recursionA recursion that runs on the host thread's own stack nears its end before the call-depth cap, on either backend: interpreter calls, a top-level include that reaches its own file again, script reentered from host callbacks.Bound the recursion, or run the engine on a thread with a larger stack.

The engine stays usable afterwards. On the VM a bare self tail call reuses its frame and never accumulates depth, so a runaway tail loop is stopped by the budget instead. A value nests as deep as memory allows: a deep copy, ==, <=> and the orderings, to_json/from_json and to_binary/from_binary walk it on work lists, never on the host thread's stack.

Limits (terminal: script cannot catch them)​

TextWhenFix
Script execution budget exceeded - raise engine::execution_budget or break up the workAn execute runs past its execution budget.Have the host raise engine::execution_budget, or split the work across executes.
Script execution budget exceeded - raise engine::execution_step_budget or break up the workAn execute takes more budget checks than its step budget allows.Have the host raise engine::execution_step_budget, or split the work across executes.
Run cancelledThe host cancels a suspended run (script_run::cancel, the run's destructor, the engine's teardown): the run's sleep raises it, every frame unwinds and its destructors run.Nothing in script: the host ends the run.
Script memory cap exceeded (N bytes) - raise engine::memory_cap or break up the workThe memory cap's second raise in one execute; the first raise is catchable.Have the host raise engine::memory_cap, or hold less at once.

Errors in script the runtime calls. A sort comparator, a filter or remove_if predicate, the other callback helpers, a parallel_for body, a user operator, to_bool() under a condition, to_string() under string +, and an accessor or post_load an archive runs all raise their own errors: the text leaves the builtin or operator unchanged, catchable like any run-time error, and an uncaught one carries the callback's frames in its trace. The builtin makes no further calls after the first failure, and sort leaves the array as it was. An archive load tolerates only a store the member itself refuses (a read-only host property, a value the field's store rejects), which leaves that member at its constructed value.

A caught error's e.stack and the host's format_stack_trace() list the innermost 64 frames, then one line ... N more frames. A host that keeps an error result past 256 newer errors whose text embeds a value reads (the error text is no longer available) in place of that text.

Texts thrown to the host as jai::runtime_error, never seen by a script:

Call-depth configuration​

TextWhenFix
max_call_depth requires a built-in backend typemax_call_depth names a backend that is not one of the built-in ones.Pass the VM or the interpreter backend.
max_call_depth must be between 1 and INT_MAXmax_call_depth gets a depth below 1 or above INT_MAX.Pass a depth from 1 to INT_MAX.
Cannot change max_call_depth during executionmax_call_depth is set while a script runs.Set it before or after an execute.

Privileges​

TextWhenFix
Unknown privilege nameA privilege is named that no registration defines.Register the privilege first, or correct the name.
Invalid privilege flagsThe flags hold bits no privilege defines.Pass flags built from defined privileges.
Privilege name must not be emptyA privilege is registered or named with an empty string.Give the privilege a name.
Cannot change privileges during a parallel regionA host function called from a parallel body enables or disables a privilege.Change privileges outside parallel regions.
reflect::is_enabled expects at least 1 argumentA script calls reflect::is_enabled with no argument.Pass the privilege to test.
reflect::is_enabled expects valid privilege flagsA script passes reflect::is_enabled flags no privilege defines.Pass a defined privilege.

Callbacks​

TextWhenFix
Cannot convert a value that is not a function to a std::function callbackconvert_script_function gets a value that is not a function.Pass a script function or lambda.
Cannot call a script function after its engine was destroyedThe host calls a converted callback it kept past its engine.Drop the callback before destroying the engine.

Engine entry points​

TextWhenFix
Variable 'x' not foundget_variable names a variable the scripts never defined.Execute the script that defines it first, or correct the name.
Failed to open script file: <path>execute_file gets a path that cannot be opened.Correct the path, or add the file.
set_backend after execute() is unsupported: defined functions and classes capture the executing backendset_backend runs after the engine executed code.Call set_backend before the first execute.
set_backend(interpreter) is unsupported: this build has no interpreter (link jaiscript_interpreter)set_backend(backend_type::interpreter) in a program built without the interpreter.Link jaiscript_interpreter (CMake option JAISCRIPT_INTERPRETER) in the program that compares the backends.

Suspendable runs​

TextWhenFix
A run resumes only on the thread that started itresume() or cancel() runs on another thread than the run's start.Resume the run on the thread that started it.
The run is running: it resumes only while suspendedCode the run itself is running (a host function under it) resumes or cancels it.Resume the run from outside it.
The script_run holds no runresume(), cancel() or result() on a moved-from or default script_run.Use the script_run the run moved to.
Cannot start a run of a value that is not a functionstart(callable, args) gets a value that is not a function.Pass a function value.

Host records​

TextWhenFix
Record nesting depth exceeds the host thread's stackA save or load of host types whose own serialize/save/load/load_and_construct recurses natively runs out of stack.Nest host records less deeply, or run on a thread with a larger stack.
Maximum record nesting depth (1024) exceededThe same recursion passes 1024 levels where the stack cannot be asked (JAI_MAX_HOST_RECORD_DEPTH); script values nest without a limit.Nest host records less deeply, or raise JAI_MAX_HOST_RECORD_DEPTH.

Conversions​

A host reads a value as a C++ type (as<T>(), as_int(), a bound function's parameter, a bound property's store) and the value holds another type. The text names what the value holds as type_of spells it, and the type the host asked for. A script that caused the read catches the same text.

TextWhenFix
Cannot convert string to intThe value holds a string where the host reads an int; the same text names every other pair (float, bool, char, string, array, map, object, function, weak_ptr, script class instance, and a math type such as math::vec3).Pass a value of the type the host reads, or convert it in the script first.
cannot bind a non-const script reference to a C++-bound variable of a different widthA host T& parameter receives a C++-bound variable whose width or signedness differs from T.Take the parameter by value, or bind a variable of the parameter's exact type.

Values and engines​

TextWhenFix
Value outlived its engine.A held_value is read after its engine was destroyed.Drop held values before their engine, or copy the data out first.
Cannot pass an object from another engine: values cross engines by copy (numbers, strings, math values and arrays and maps of them).A value one engine made is handed to another engine, and it holds something only its own engine can name (an object, a function, a pointer).Pass plain data across engines, or rebuild the object in the receiving engine.

Archives​

Thrown as jai::serialization_error by the C++ archives (from_json, from_binary and the readers under them) when the data does not match the type being loaded.

TextWhenFix
Expected an object in the archive, found an integerThe data holds another kind of value where the loaded type reads a record (an object), an array, a map or an integer. The found side is null, a bool, an integer, a float, a string, an array, an object or nothing in JSON, and the end of the data or marker N in binary.Load the data with the type that wrote it, or correct the data.
Archive value 300 does not fit int8An integer field's value is out of its C++ type's range, or a number with a fraction (1.5, inf) is read into an integer field. The field is named by width: int8 to int64, uint8 to uint64.Widen the field, or correct the data.
Property 'x' not found during deserializationA serialize/load reads a key the record does not have.Check has_property first for a key older files lack (Versions and missing fields).
Dangling shared_ptr reference: {"$id": 4} names an object this archive never defined (T)A handle refers back to an id no earlier part of the data defined: a hand-edited or reordered file.Keep the file in the order the writer wrote it, or save it again.
Unknown polymorphic type: NameA $type names a type no registrar or register_relation registered.Register the type before loading.
Polymorphic type 'Name' is not a registered subtype of requested base 'Base' (...)A $type names a registered type unrelated to the pointer's base.Declare the relation, or correct the data.
Expected '$val' for polymorphic shared_ptrA handle names a $type but carries no $val.Save the data again.

Saved programs​

A malformed .jaibite is refused before anything runs, every text prefixed jaibite: .

TextWhenFix
jaibite: truncated headerThe file ends inside its header.Save the program from source again.
jaibite: not a jaibite file (bad magic)The file does not start with the jaibite magic.Load a file a jaibite save wrote.
jaibite: unsupported format version M (expected N)The file was saved by a build of another format version (M; this build reads N).Save the program from source with this build.
jaibite: corrupt varintAn encoded number in the file is malformed.Save the program from source again.
jaibite: AST nesting too deepThe saved syntax tree nests deeper than the writer or the loader can walk on the thread's stack.Save or load it on a thread with a larger stack, or flatten the nesting.
jaibite: type nesting too deepA saved type nests deeper than the writer or the loader can walk on the thread's stack.Save or load it on a thread with a larger stack, or flatten the nesting.
jaibite: missing required block statement nodeThe saved program lacks a block it requires.Save the program from source again.
jaibite: expected block statement nodeA node that must be a block is something else.Save the program from source again.
jaibite: invalid program: local slot out of rangeA structure check fails (and the other jaibite: invalid program: ... structure checks: a local count the file's syntax does not account for, a break outside a loop or switch (a parallel_for body is not one), a continue outside a loop, a malformed parallel_for).Save the program from source again.
jaibite: corrupt countAny other load text names the part of the file that is damaged or foreign: unexpected end of data, corrupt count, corrupt string index, corrupt type_info, corrupt token type, corrupt literal value, corrupt capture default, corrupt member visibility, corrupt check mode attribute, corrupt node tag N, trailing bytes after declarations, expected expression/statement/declaration node, missing required expression/statement/declaration node and destructuring name/slot count mismatch.Save the program from source again.
jaibite: cannot serialize unresolvable symbol idA save meets a name its engine cannot spell (and jaibite: unserializable AST node type N, a node the format has no encoding for).Save the program from source in the engine that parsed it.

Parse errors that name the JaiScript spelling. Each word is recognized only where it would otherwise fail to parse, so a script that uses it as a name (var goto = 5;) keeps working:

WrittenText
int size() const { }JaiScript has no const methods: a method that writes no field already works on a const object
const int f() { }JaiScript has no const return types: return by value (the caller gets a copy) or by T&
array<const int>const qualifies a declaration, not a type: write const array<int> xs
shared_ptr<const T>JaiScript has no pointer to const: const shared_ptr<T> keeps the handle from rebinding, and the object stays writable
const_cast<T&>(x)JaiScript has no 'const_cast': a const stays const; copy it (auto x = c) for a writable value
constexpr int N = 4;JaiScript has no 'constexpr': a const whose initializer is a constant expression is folded at compile time: const int N = 4;
mutable int cache;JaiScript has no 'mutable' fields: every field of a const object is read-only; keep state that must change behind a shared_ptr field
[n]() mutable { ... }A lambda takes no 'mutable': its copied captures are already its own, writable and kept between calls
x = const 5'const' starts a declaration: const int x = 5;; const int x;: const 'x' needs an initializer: const int x = value;
virtual void f()JaiScript has no 'virtual': every method dispatches dynamically; mark the subclass's method 'override'
delete x;JaiScript has no 'delete': an object is freed when its last owner lets go; set the handle to null to release it
new int[5] · new T;'new' makes one shared object (new T(args)); an array is a value: array<T> a = []; · 'new T' needs its constructor's parentheses: new T() or new T(args)
goto done;JaiScript has no 'goto': use break, continue, return or a flag
super.speak()A base class method is called with super::name(args), not super.name
super(5); in a bodysuper(...) constructs the base only in a constructor's initializer list (: super(args)); call a base method with super::name(args)
&xJaiScript has no address-of '&x': bind a reference with auto& r = x, or pass x to a T& parameter
catch (Error e)catch takes one name and no type: catch (e) { ... }; e.value is what was thrown and type_of(e.value) its type
class B extends AJaiScript has no 'extends': derive with class Name : Base
a === b · a !== bJaiScript has no '===': == never converts between types, so a == b already compares without coercion ('!==' alike)
function* g()JaiScript has no 'function*': a generator is a coroutine, coroutine name(...) { yield v; }
[...a, 3], f(...args)JaiScript has no spread '...': join arrays with array::concat(a, b) (a.concat(b) appends in place), and pass arguments one by one

Other parse errors in the same voice: Enum values take no initializer: 'a' is 0, its position in the enum · 'uint8' is an array element type only (array<uint8>, one byte per element); a single byte is an int · '??=' needs a variable, a member or an element whose index runs no code; store anything else in a local first · A map entry binds two names, the key and the value: for (auto [k, v] : m) · operator! cannot be declared: !v is !to_bool(v); declare bool to_bool() · Nested function 'helper' cannot use enclosing local 'y': a named function captures nothing; use a lambda with a capture ([&], [=] or [y]) or function(...) / => · a class cannot be declared inside a function body (a class is a program-scope type); declare it at program scope or import its file · 'a' names g::a by a using declaration; a variable cannot be declared with that name.

Full API signatures: arguments, defaults, results, effects, and availability for builtins, standard-library functions, and the standalone runner.

Builtin methods​

From Containers & Strings. One registry serves both backends, so the lists below are the same on each.

Arrays (31)​

kindmethods
observerssize empty front back index_of has contains first last length
call back per elementreduce for_each find find_index some every
returns a stringjoin
mutate in placepush pop clear sort reverse remove (erase) remove_if insert
mutate in place and return the receiverslice filter map concat (append)

pop on an empty array raises a catchable error. a.filter(f) keeps the elements f accepts in a itself; remove_if drops the ones its predicate accepts. The copying forms are the array:: functions: array::slice, array::filter, array::map, array::concat, array::append, array::sort and array::reverse return a new array and leave their argument alone. The callback helpers pass the element, or the element and its index to a two-parameter callback; a T& element parameter is the element itself. a.map(f) keeps a's element type (results convert into it); array::map returns an array<T> when its callback declares T; find returns a copy or null, find_index an index or -1; reduce(f) without an initial value raises on an empty array; insert(i, v) takes i from 0 to size(). A typed array (array<int>) checks every store against its element type; array<var>, and a literal with no spelled element type, holds anything.

Maps (14)​

kindmethods
observerssize empty contains has get keys values length to_array
mutate in placeclear erase remove remove_if filter (predicate takes (key, value); keeps the accepted entries and returns the receiver, while map::filter(m, f) returns a new map)

Writing m[key] = v inserts; reading a missing key returns null and does NOT insert. get(key) returns null for a missing key; get(key, default) supplies an explicit fallback. Keys are ordered; any script value can be a key (the total ordering of <=> guarantees it). A declared key type is enforced on stores (map<string,int> m; m[5] = 1 raises); keys never coerce.

Strings (38 core)​

s[i] indexes bytes and returns a char, which promotes to an unsigned int (0..255) in numeric contexts, so s[i] + 0 reads a byte value, s[i] - '0' is the digit-parse shape, and s[i] == 0x1A is a byte test. s.at(i) returns the same char; on a utf8_string, u.at(i) returns character i as text. s[i] = c replaces byte i and s[i] = text replaces it with the whole text (s[i] += text reads, concatenates and writes back); on a utf8_string, u[i] reads and replaces character i. Bytes are minted with cast<char>(n) or to_char(n) (both range-checked 0..255; out += cast<char>(b) is the binary-writer shape) and with \xNN escapes (exactly two hex digits; "\x00" embeds a real NUL). A raw string R"(text)" (or R"delim(text)delim", a delimiter of up to 16 characters, none a space, parenthesis, backslash or quote) holds its text with no escapes, a line ending inside as one \n. find returns -1 when absent; split() with no argument splits on whitespace and drops empty pieces (split(string::whitespace) is the same); split(sep) keeps empties; split("") splits into characters; explode is its synonym.

kindmethods
observers (leave the string alone)length size empty at front back substr find rfind find_first_of find_last_of find_first_not_of find_last_not_of contains starts_with ends_with count to_int to_float split
in-place mutators (modify the receiver, return self for chaining)to_lower to_upper trim trim_left trim_right pad_left pad_right pad_center replace replace_first replace_last insert erase remove_prefix remove_suffix reverse repeat append

The mutator camp is the surprise: auto upper = name.to_upper(); also uppercases name itself. This is deliberate: returning the receiver makes chained pipelines like s.trim().to_lower() allocation-free, and value semantics keeps the mutation local to that one variable. Copy first if you need the original, or build new strings with + (which never mutates), or call the string:: function of the same name (string::to_upper(s) returns a new string).

Every engine also has text helpers beyond the core table: lines partition compare code explode byte_size utf8_length utf8_chars is_valid_utf8 is_digit is_alpha is_alnum is_space is_upper is_lower to_utf8 (a utf8_string), and the binary doors to_bytes([offset], [count]) (an array<uint8>) and reader() (a binary::reader). A utf8_string answers the same method names in characters, except to_utf8.

Smart pointers​

typemethods
shared_ptr (5)reset use_count cpp_ref_count unique same_as
weak_ptr (4)lock expired reset same_as

Library at a glance​

jaiscript
math::vec3 p = math::vec3(1, 2, 3);
p.x = 10;                                     // a component is an lvalue
array<math::vec3> pts = [p, math::vec3(0, 0, 1)];
print(p * 2.0, " ", math::dot(p, p), " ", math::length(math::vec2(3, 4)), " ", pts[1]);

binary::writer w;
w.uint16(513); w.text("hi");
var r = w.to_string().reader();
print(r.uint16(), " ", r.text(), " ", r.at_end());

utf8_string u = "h\xC3\xA9llo";
print(u.size(), " ", u[1], " ", "h\xC3\xA9llo".size());

array<float> xs = [1.0, 2.0, 3.0];
parallel_for (auto& x : xs) { x *= 2.0; }
print(to_json(xs), " ", type_of(time::utc_now()));
print(to_json({"a": [1, 2]}, 2));
output
(20.0, 4.0, 6.0) 113.0 5.0 (0.0, 0.0, 1.0)
513 hi true
5 é 6
[2.0,4.0,6.0] time::date
{
  "a": [
    1,
    2
  ]
}
AreaSurfaceChapter
vectors and colorsmath::vec2 vec3 vec4 ivec2 ivec3 color: value types with component fields (x y z w, r g b a), component-wise operators, a zero divisor component raising Division by zero; math::dot cross length normalize distance and the component-wise math::lerp math::mix math::clamp math::min math::max family; array<math::vec3> is a typed buffer. A default math::color is opaque white; colors mix in OKLabMath
host math typesJAI_BIND_VEC2 / VEC3 / VEC4 / IVEC2 / IVEC3 / COLOR in a C++ type's header make it cross the boundary as the native kindMath Adapters
regular expressionsregex(pattern, [flags]) (flags i m s x), r.search(text, [start]), r.matches(text), r.find_all(text), r.replace(text, with) / r.replace_first ($1 ${name} $0 $$ or a function of the match), r.split(text, [max]), r.pattern / r.flags, regex::escape(text); regex::match: m.text(), m[i] / m["name"], m.start(g), m.end(g), m.size(); linear time, bytes on a string and characters on a utf8_string; a constant pattern compiles at loadContainers & Strings
binary datas.reader() / binary::reader(s), binary::writer w;, the widths int8..int64, uint8..uint32, float32, float64, text(n), bytes(n); binary::layout("field", width, ...) with r.unpack, w.pack and r.read(Class, layout); s.to_bytes() and string::from_bytes(b); array<uint8> / array<int8> are one-byte buffersContainers & Strings
textstring is bytes, utf8_string counts characters (size, u[i], substr, range-for); split() on whitespaceContainers & Strings
random numbersrandom r; (seeded by the system), random r(seed); (the same stream on every platform): next_int(lo, hi) inclusive, next_float() in [0, 1), next_float(lo, hi), next_bool(), pick, shuffle, next_bytes(n), seed(s); a copy replays its draws and state round-trips through every archiveRandom Numbers & Crypto
cryptocrypto::random_bytes(n) random_hex(n) random_int(lo, hi) (the system's secure generator, never seedable); sha256 sha512 sha1 md5 (hex, _bytes raw) and sha256_file(path); hmac_sha256(key, message) hmac_sha512; equals(a, b) in constant time; password_hash(pw[, cost]) and password_verify(pw, hash), bcrypt as PHP writes itRandom Numbers & Crypto
web text (opt-in: register_web)web::html_escape(s) (& < > " '), web::html_unescape(s) (HTML5 references, as a browser reads them), web::url_encode / url_decode (RFC 3986), web::form_encode / form_decode (space as +), web::parse_query, web::parse_query_all, web::build_query (key order), web::parse_url to a web::url whose to_string() joins it backThe web:: Library
HTTP client (opt-in: register_spider, a host transport, the network permission)spider::catch(url[, options]) (also spider::get), spider::sling(content, url[, options]) (also spider::post(url, content[, options])), options method, headers, timeout, type, redirects; each answers a spider::Response (status, headers, cookies, body, url, ok(), json(), header(name))HTTP with spider::
timetime::now() engine_elapsed() execute_elapsed() unix_time() unix_ms() date_now(template) utc_now() local_now() date_from_unix(seconds) utc_date(y, m, d, ...) local_date(y, m, d, ...), the value types time::stopwatch and time::dateTime
waitingsleep(seconds), a builtin of every engine: suspends a run the host started with engine::start until the time has passed, and blocks anywhere elseCoroutines
JSONto_json(x) compact, to_json(x, n) pretty-printed with n spaces per level; from_json(s) rebuilds class instances (through their constructor), chars and non-string map keysContainers & Strings
parallelismparallel_for (auto& x : arr) { ... } , parallel_for (int i = a; i < b; i++) { ... } , parallel_transform(arr, f) and parallel_reduce(arr, init, step, combine): deterministic at any worker count; a body that writes enclosing state raises parallel_for: body writes enclosing state 'total' at 11:31Containers & Strings
reflectionreflect::: type queries, fields and methods by name, reflect::bind(obj, "name")Reflection

What a bare engine has. jai::engine::make() alone runs the whole language with these builtins: a print that concatenates its arguments, template strings with format specs (format_value), the builtin container and string methods and their array:: forms, math:: (functions, vectors, colors, wrap_*), regex, binary:: readers and writers with string::from_bytes, same_object, extend, thread_count, parallel_transform and parallel_reduce, sleep, and the location builtins (executable_dir, file_data, resolve_path and the rest). jai::stdlib::register_all adds the formatted print, format, to_string, type_of, JSON and binary serialization, the string:: functions, to_char, random, time::, crypto:: and reflect::; web:: and spider:: are separate opt-ins.

include vs import​

From Modules:

include "f.jai"import "f.jai"
Runsevery time it is evaluatedwhen first reached, then again when its file changed (checked in passes: import_recheck_interval, default 0.2 s)
Scopeexecutes in the current scopemodule declaration
Expression?yes: var cfg = include "config.jai"; evaluates to the file's last expressionthe statement form never (auto x = import ... is a parse error); the call form import("f.jai") is a bool: true when a module ran
Dynamic pathsyes: include(expr)yes: import(expr)
Checked how?a literal path joins the program closure: its names and classes are known to every unit and checked before anything runs; a dynamic path cannot be gathered, so that unit's undeclared identifiers drop to warnings
Idiomforcing a re-run: hot reload of rules/content mid-gamedeclaring your module graph at the top of each file

#include "x.jai" is accepted (the # is ignored for C++ muscle memory). Persistent state goes in a module you only import and never include, so a reload can't wipe it.

Every file-based load also maintains a jaibite disk cache in a .cache folder beside the source (foo.jai → .cache/foo.jai.jaibite, or below the host's cache directory): a cache recording the exact source text (size + hash) and a matching registration fingerprint loads instead of parsing; any other text reparses and refreshes the cache, whatever its mtime. Parse-level and side-effect-free: it never changes whether a file runs. Silent on write failure; eng->jaibite_cache(false) disables.

Import modes (engine::import_behavior)​

ModeBehavior
file_timestamp (default)Re-runs a module when its file's (modification time, size) stamp or a file its last run included changed, checked in engine-wide passes at most once per import_recheck_interval (0.2 s by default; 0 checks on every reach). A module whose file disappears raises Failed to open import file once, then waits until the file is back. A read the stamp moved during never re-runs a loaded module (the next pass reads it whole). eng->file_stamp_source(fn) supplies the stamps (fn(path) returns a jai::engine::file_stamp or std::nullopt; empty = the file system's). The hot-reload-friendly default
onceImport exactly once; ignore later file changes
alwaysTreat import as include (runs every time)

Engine API quick reference​

The embedding surface used across the embedding chapters; each row links to the chapter with the compiled-and-run example.

Engine lifecycle & execution: Quickstart​

APIWhat it does
jai::engine::make()create an engine (always a shared_ptr<engine>; no global state anywhere)
jai::stdlib::register_all(eng)opt-in stdlib: formatted print, format, to_string, to_json, time::, reflection and other optional helpers. Math, array transforms, text and binary primitives are engine core; a bare engine also has basic print + format_value
eng->execute(src)run a script; returns jai::script_value (last expression's value). Under strict checking a rejected script throws jai::static_check_error before anything runs
r.as<T>() / as_int() / as_float() / as_string() / as_bool()convert results
eng->execute_file(path)run a file; every node is attributed to its canonical path (listings, traces, breakpoints) and a .jaibite in the .cache folder beside it caches the parse
eng->start(src) / start_file(path) / start(callable, args)run a script as a suspendable run: each sleep hands back a jai::script_run whose resume() continues it once wake_at() has passed (Suspendable runs)
eng->get_variable(name) / has_variable(name) / has_function(name)read a global back (a function is a value too)
fn.as_function()(args)call a script function from C++; returns checked_result<script_value> (eng->format_error(r) for its text); an uncaught script throw arrives as jai::script_exception. Wrap in engine::external_call_guard only when a script is running
jai::convert_script_function<Sig>(fn)a typed std::function over a script function; a script error or an uncaught script throw inside it throws jai::runtime_error to the C++ caller (a host function called from script hands it back to the script as that call's error)
v.as<std::vector<T>>() / as<std::map<K, V>>() / as_array() / unchecked_array_node()->for_each(eng, f)containers back into C++: typed arrays and maps copy out, as_array() views an array<var> only, for_each walks any array kind
eng->set_backend(jai::backend_type::interpreter)choose the tree-walking interpreter (the bytecode VM is the default), before the first execute (forbidden after), in a program that links jaiscript_interpreter
eng->set_output_stream(s)redirect print(); nullptr restores std::cout
eng->set_script_error_handler(fn)sink for script errors nothing can catch (a destructor failing at engine teardown)
eng->format_stack_trace() / last_stack_trace()script stack of the most recent failure

Registering the host surface: Quickstart, dynamic_binder​

APIWhat it does
eng->add_function(name, lambda)typed host function; repeated names build an overload set resolved by argument types. A non-const T& parameter writes the callee's final value back into the script lvalue argument (a variable, field, element, map value, static, C++ property or vector component v.x) through that slot's own store rule; a literal or temporary argument is a copy. A std::shared_ptr<T> or std::weak_ptr<T> parameter takes null as an empty handle, and a std::weak_ptr<T> parameter takes a live shared_ptr or weak_ptr handle as one to the same object
eng->add_variadic_function(name, fn)receives the raw std::vector<script_value>. For print-shaped functions and optionals
eng->add_host_direct(name, arity, invoke, target)an entry over the arguments where they are, for the function name holds at this call: a VM call site calls invoke(target, args, arity) for arity non-reference arguments instead of the function, which takes every other call. See A direct entry for a hot host function
eng->add_global(name, value)copy a value in
eng->add_global_ref(name, var)live binding (cpp_bound) to a C++ primitive/string. Script reads and writes the real variable
eng->make_value(v) / make_null() / make_array() / make_map()value factories (every script_value carries its engine); make_value of a script_value hands it back, and as<script_value>() is the value itself
jai::dynamic_binder<T>(*eng, "Name")fluent class binding: .constructor<Args...>() .method(...) .property(...) .static_method(...) .static_property(...) .base_class<B>() .build()
.method("+", &T::operator+)operator overloading by token name (+ - * / % & | ^ << >> == != < <= > >= <=>, unary - + ~, [], (), =); the script rules apply: <=> supplies the relational operators and ==, != falls back to negated ==, and an operator counts for an operand only when one of its overloads takes it
.property("child", &T::child, jai::skip_type_check)opt out of registration-order validation for circular dependencies
extend(object, Class) (script)give a host-owned object a script class over its C++ class or one of its bases, in place; idempotent, re-derives for another class. See Script-Extensible Objects
eng->extend(obj, "Class")the same from C++; add_global, make_object and every other entry hand script the object's layer, extended before or after
eng->script_layer(*obj) / script_layer_detach(obj)the object's live layer / drop it (fields returned as JSON)
obj->script_layer_to_json() / script_layer_from_json(s)script_extensible (every property_owner): the layer's {"$type", "$script"} state, out and back

Safety rails: Safety Rails​

APIWhat it does
eng->execution_budget(seconds)wall-clock cap per execute()/next(); default 5.0 s, 0 disables. Overrun is terminal. Script catch cannot swallow it
eng->execution_step_budget(checks)clock-free twin for deterministic gates: at most checks budget checks (one per 1024 back-edges and call entries) per host entry, then the same terminal error; default 0 (off)
eng->memory_cap(bytes)approximate high-water per top-level execute: first raise is catchable and denies in place, second raise is terminal; 0 = unlimited
eng->max_call_depth(jai::backend_type::vm, n)the recursion cap per backend (interpreter 256, VM 250,000 by default); overrun is the catchable recursion error
eng->throw_on_overflow()reports the build's integer-overflow policy (checked = default)
jai::debug::listen(*eng, port) / conn->listening() / conn->error()the VS Code (DAP) listener; a taken port does not throw, listening() is false and error() says why
eng->add_include_path(dir)search roots for script include/import (first added wins on collisions)
eng->confine_paths({dirs})opt-in: include, import, resolve_path and file_data open only files under these folders, and no .cache is written beside a source outside them; empty (the default) opens any readable path (Safety Rails)
eng->reset_import(path) / reset_imports() / is_imported(path) / get_imported_files()import-cache management
eng->set_import_behavior(mode)file_timestamp (default) / once / always
eng->import_recheck_interval(seconds)how often a reached import asks the filesystem whether its module changed: at most once per window (default 0.2 s); 0 checks on every reach (tests that write a file and import it at once). The script builtin import_recheck_interval(s) sets the same
eng->hot_reload_reporting(true) / hot_reload_reports() / clear_hot_reload_reports()off by default; on, every field operation a class reload performs on live instances, and every saved value a load converts or cannot fit into its field, aggregates into std::vector<jai::field_migration_report> rows (class_name, field_name, old_type, new_type, outcome: preserved / converted / reset / added / removed, count across instances) until cleared
eng->disable(jai::filesystem) / enable(...) / is_enabled(...) / register_privilege("name")the filesystem privilege (on by default) and custom named permissions; a host function registered with a final requirement (add_function(name, fn, {"app.edit"})) raises Insufficient privilege: app.edit. while it is off

Time source: Quickstart, Time​

APIWhat it does
eng->time().source(monotonic, wall, sleep)replace the clock every time:: call reads: monotonic and wall providers return std::chrono::nanoseconds (wall = since the Unix epoch, UTC), sleep takes a duration; an empty provider selects the system one. The engine reads a provider only inside a time:: call, plus the monotonic one at each host entry once a loaded program names time::execute_elapsed
eng->time().monotonic_now() / wall_now() / sleep_for(d)the same providers from C++ (one read each); eng->time() is declared in <jaiscript/core/engine_time.hpp>
eng->engine_elapsed() / execute_elapsed()what time::engine_elapsed() and time::execute_elapsed() answer

Static checking & precompiled scripts: Static Checking, jaibite​

APIWhat it does
eng->check(src)parse (cached) + check without running, returns jai::check_report, the tooling entry point
eng->last_check_diagnostics()report from the last checked execute/bite (warnings included)
eng->type_proofs(bool)default on. The checker's walk also stamps type proofs that let both backends skip conversions it has shown to be identities; every proof sits behind a run-time guard, so type_proofs(false) is observationally identical (same values, output and error text), just slower. Leave it on
eng->parameter_alias(bool) · eng->local_alias(bool)default on. A by-value parameter or local that is provably only read binds the argument or source itself instead of a copy (Choosing a strategy); off copies every time, observationally identical, for a differential check
eng->jaibite(src)parse once → engine-bound handle; bite.execute() skips the parse (VM also caches the compiled chunk). Checked at creation in warn/strict (strict throws there); bite.checked_clean() / bite.check_diagnostics() expose the result
bite.save(path) / save_bytes()ship the parsed AST as .jaibite, stamped with the build's format version (jai::detail::k_jaibite_version); loading another version fails with jaibite: unsupported format version N (expected M) (the automatic disk cache just reparses instead)
eng->jaibite_load(path) / jaibite_load_bytes(b)load into any engine. Symbols re-intern, one file serves both backends. A bite whose registration fingerprint differs from this engine's is re-checked at load against this engine's surface
bite.registration_mismatch()flags a registered-surface difference between saving and loading engines. Advisory, covers every registration form incl. zero-arg and variadic functions; loads never hard-fail on it
eng->jaibite_cache(bool) / jaibite_cache()automatic disk cache for file-based loads (default ON): include/import/execute_file keep .cache/foo.jai.jaibite beside foo.jai, the folder made when missing. A cache loads only for the exact source text it records (size + hash) and a matching registration fingerprint, whatever the files' mtimes; anything else reparses and rewrites it. A cache is written to a temporary file and renamed into place; writes are silent best-effort, tried once per source text (jaibite_cache_write_failures() counts them)
eng->jaibite_cache_directory(dir, root) / jaibite_cache_directory()every cache below dir at the source's path relative to root (outside it, or with no root, the source's own path); a valid .cache beside the source still loads, never rewritten. For read-only bundles; empty restores the default
eng->jaibite_cache_path(source)the cache file the engine writes for source (empty for a .jaibite source)

Serialization & coroutines: Serialization, Coroutine Hosting​

APIWhat it does
to_json(x) / to_json(x, n) / from_json(s) (script)JSON round-trip of any script value; n pretty-prints with n spaces per level
jai::to_json(*eng, v) / jai::to_binary(*eng, v) (C++)host-side convenience serializers for C++ objects the archives know (a property_owner, a type with serialize/save, a handle to one); a jai::script_value is not one of them, so a script value goes through the script builtin to_json
eng->execute("co.next()") / native handle e->next(eng), e->next(eng, n) / e->done()drive script coroutines from C++

Thread pool: Thread Pool for Host Work, Parallel worker count​

API (<jaiscript/thread_pool.hpp>)What it does
jai::thread_pool pool(n) / worker_count() / thread_pool::default_worker_count()start n workers (0 = hardware threads minus one, at least one)
thread_pool::performance_core_count()the performance cores of a hybrid CPU, between 1 and the hardware threads; detected once
jai::thread_pool pool{n, jai::thread_pool::fast_threads}workers that ask the system not to throttle them to save power (off by default)
eng->parallel_thread_count(n) / parallel_thread_count()the threads a parallel region runs on, the calling thread included: default the performance cores, at most the hardware threads minus one; 0 restores it
submit(task) / submit_to(worker, task)the shared queue (first free worker) / one worker's own queue, in order
submit(task, then, where) / submit_batch(tasks, then, where)run then once after the task or the whole batch, on the pool (then_on::pool) or posted (then_on::posted)
post(task) / run_posted()queue work for the host's thread / run it there, returning how many ran
on_exception(handler) / spin_budget_ns(ns) / wait_idle()receive escaped exceptions / spin before parking (0 parks at once) / block until submitted work finishes

Command-line runner​

jaiscript is the reference embedding and the quickest way to run anything on this site. It is built by the jaiscript_runner target (on by default), lands in build/bin/, and always registers the full stdlib, the web:: library, the spider:: HTTP client (WinHTTP on Windows, sockets elsewhere, the network permission granted), file IO (read_file, write_file, file_exists, delete_file, move_file, dir_exists, make_dirs, list_dir, writable_dir, plus Download, zip_entries and zip_extract), a small console-game surface (read_key, key_down, term_cols, term_rows, and sound: load_sound, play_sound, stop_sounds; clocks are the standard library's time::), two socket classes, NetServer and NetChannel, and net_host_name and net_local_addresses. Enough that the example shims and JaiDoom run on it unmodified. Full notes: tools/jaiscript/README.md.

InvocationWhat happens
jaiscript file.jai [-- args...]run the file as a suspendable run (each sleep returns to the runner, which waits on the engine's clock and resumes it) and exit; the exit code is the script's trailing int expression if there is one, 1 on a script error, 2 on a usage error (an unknown flag, a missing or malformed value). Only 0 to 255 survive as an exit code everywhere: an int outside that range exits with its low byte (300 exits 44, -1 exits 255) on every platform. Everything after -- is the global ARGS. Dropping a .jai onto the executable does the same and pauses before closing
jaiscript -e "source" [-e ...] [-- args...]run the source text and exit, as a file run runs a file's text: several -e run in order in one engine (declarations carry over, the first error stops the run), the exit code is the last source's int result, 1 on a script error. Nothing prints unless the source prints (jaiscript -e "print(1 + 2)"); the REPL is the mode that echoes values (echo 1 + 2 | jaiscript). Combines with the flags below; include/import resolve from the working directory; a script file or --watch alongside is a usage error
jaiscripta REPL with a persistent engine: :load, :reset, :backend vm|interp|vm-reference, :time on|off, :help, :quit. Piped input (echo 1 + 2 | jaiscript) exits 1 when any input failed, else 0
jaiscript --watch file.jaire-run the file on every save, in a fresh engine each time (a program that keeps its world reloads with a guarded import in its loop instead: Reloading from a running program)
FlagMeaning
--backend=vm|interp|vm-referenceexecution backend (default vm); both must agree, so the other one is your second opinion. vm-reference is the VM with every optimization off (engine::vm_reference_mode)
--warningslist the checker's warnings on stderr before the script runs (typed mistakes and undeclared names always fail the load)
--workers=Nthe worker count of parallel_for, parallel_transform and parallel_reduce (default: the engine's own)
--budget=Nexecution budget in seconds per execute; default 0 = unlimited (the embedded default of 5.0 s is deliberately lifted in a runner)
--timeprint wall time per run
--debug / --debug-port=Nstand up the DAP debug listener on localhost (VS Code attaches to it) and name the port it bound on stderr; --debug-port=N listens too, trying port N first (default 52477, probing upward in steps of 10). Without either, nothing listens
--pause / --no-pauseforce or suppress the "press Enter to close" hold after a file run
--version / --help
--benchmark-main=Nrun the file once as a warm-up, then call its main() N times (a repeatable timing loop)
--no-param-alias / --no-local-aliasturn off the read-only copy elision for by-value parameters / locals, so every one copies (results never change; a tool for measuring and for bisecting)
--no-proofsrun without type proofs, typed ops or cell blocks: the general form, with the same results
--miss-every=Nthe VM misses at every Nth plain cell instruction, exercising the flush at every position (a build configured with JAISCRIPT_MISS_INJECTION)
--disasm=FILE / --manifest=FILEwrite every chunk the VM compiles to FILE: readable listings, or exact semantic manifests
--coverage=FILEthe VM's execution-coverage census at exit (a build configured with JAISCRIPT_VM_COVERAGE)
--selftestrun the runner's built-in smoke tests and exit

The runner's host API​

The runner registers these on every engine it builds; a host embedding JaiScript has none of them unless it registers its own. The calls that name a file (the file functions, Download.start, load_sound and play_sound) run under the engine's filesystem privilege, so an engine with it disabled raises Insufficient privilege: filesystem. at the call; Download.start reaches the network too and needs the network privilege as well. Signatures and effects are also in the API reference under the standalone runner. Locations (executable_dir(), root_script_dir(), current_script_dir(), resolve_path(name), app_data_dir(name) and the rest) are language builtins on every engine, described in Modules; a script that builds its paths from them runs the same from any working directory.

Files and foldersBehavior
read_file(path) -> stringthe file's bytes exactly (binary mode; "" for an empty file). A file that is missing or unreadable raises Cannot read file '<path>': <reason>
write_file(path, text) -> boolwrites the bytes exactly, creating missing folders; false when it cannot
file_exists(path), dir_exists(path) -> boolwhether the path names something, or a folder
delete_file(path) -> boolremoves the file; false when nothing was removed
move_file(from, to) -> boolreplaces to; false when from is not a file. On one volume the replacement is a single operation, so a reader sees the old file or the new one (the last step of a safe save: write save.tmp, then move it over save.txt); across volumes it copies, then removes the source
make_dirs(path) -> boolcreates the folder and its parents; true when the folder exists afterwards
list_dir(path) -> array<string>the folder's entry names, sorted; empty for a folder that cannot be read
writable_dir(path) -> boolcreates the folder if missing, then writes and removes a probe file: whether the folder can take a save
zip_entries(zip) -> array<string>, zip_extract(zip, entry, dest) -> boola zip archive's entry names, and one entry written to dest with its CRC-32 verified
Download()an asynchronous download the script polls from its own loop: start(url, path) begins it (http or https; false with error() set when it cannot begin), pump() returns true once it is done, success() and error() give the outcome, received(), total() (-1 until known) and fraction() the progress, cancel() stops it. A failed transfer leaves no partial file
Console gamesBehavior
read_key() -> stringthe next key press, without waiting: "" when none is queued, else "up", "down", "left", "right", "enter", "esc", "tab", "space", "backspace", a lowercase letter, a digit or a punctuation character (on Windows also "shift", "ctrl" and "alt")
key_down(name) -> boolwhether the named key is held now: a letter, a digit, "space", an arrow, "enter", "esc", "tab", and on Windows "shift", "ctrl" and "alt". A POSIX terminal sends no key releases, so there a key reads as held for half a second after its last press or auto-repeat, and the modifiers never read as held
term_cols(), term_rows() -> intthe console window's size in characters (80 by 25 when it cannot be read)
SoundBehavior
load_sound(path) -> Soundopens a WAV, MIDI or MP3 file and returns at once while the file preloads in the background, so a long song is ready by the time it plays. The file's first bytes name the format (RIFF/WAVE, MThd, an ID3 tag or an MPEG frame); a header it does not recognize falls back to the extension (.wav, .mid, .midi, .mp3). null for a missing file or another format
s.play(), s.play(true) -> boolstarts the sound from the beginning, looping forever with true, and returns at once; playing a Sound that is already playing restarts it. false once the file proved unplayable (its data did not decode)
s.stop(), s.playing() -> boolstops it; playing() is true from play() until stop(), stop_sounds() or the end of a sound played once
s.volume(level)sets the level, 0.0 to 1.0 (clamped); a Sound starts at 1.0
s.format() -> string"wav", "midi" or "mp3"
play_sound(path), play_sound(path, true) -> Soundload_sound then play, for a sound fired and forgotten: it plays to its end (or forever, looped) even when the result is dropped, and the result stops it. null as load_sound
stop_sounds()stops every Sound

Every Sound is its own voice: music in one, ambience in another and effects in the rest all play at once, mixed. A Sound loaded once and played again is the way to fire an effect many times, since loading a file costs more than playing it. A loaded Sound closes when the last variable holding it lets go (a copy of a Sound is the same sound). No call waits on the audio device: one audio thread opens and drives the devices, so a frame loop never stalls on a sound. Sound plays on Windows; elsewhere load_sound and play_sound return null, so a script that checks for null, as it does for a missing file, runs unchanged, silent.

jaiscript
var music = load_sound("theme.mid");
var hit = load_sound("hit.wav");
if (music != null) { music.volume(0.6); music.play(true); }   // loops until stopped
if (hit != null) { hit.play(); }                               // again and again, each restarting it
play_sound("door.wav");                                        // fire and forget
NetworkingBehavior
NetChannel()one framed TCP connection: listen(port) waits for one peer, connect(host, port) dials one, send(bytes) queues a packet, pump() moves the bytes and fires the signals, close() drops the connection; connected() and status() ("idle", "listening", "connecting", "connected", "closed: <reason>", "error: <reason>") report its state
NetServer()many framed TCP clients on one port: listen(port), send(id, bytes), broadcast(bytes) (the count of clients it reached), pump(), kick(id), client_count(), listening(), close(). Client ids count up from 1 and are never reused by one server
net_host_name() -> string, net_local_addresses() -> array<string>this machine's name and its IPv4 addresses, loopback left out: what a player hosting a game tells the others to type

A packet is one send's bytes, delivered whole and in order: on the wire each packet is a 4-byte big-endian length, then the bytes. Neither class runs a thread. The sockets do not block, and the signals fire only inside pump(), on the script's own call stack, so a game calls pump() once a frame. A NetChannel fires on_connect(), on_packet(bytes) and on_disconnect(reason); a NetServer fires on_client_connect(id), on_client_packet(id, bytes) and on_client_disconnect(id, reason). A disconnect's reason is "closed by peer", "recv error", "send error", "oversized frame" (a length over 64 MiB) or, for a server's client, "kicked". Packets that arrived ahead of a close are delivered before its disconnect.

NetChannel.connect is asynchronous. It returns false only when the attempt fails at once (the host name does not resolve, the socket cannot be made), with the reason in status(). Otherwise it returns true while the connection is still being made, and the outcome arrives later through pump(): on_connect() when the peer accepts, on_disconnect("connect refused") when it does not. Packets sent before then wait in the queue and go out once the connection is made.

Performance rules of thumb: Backends, Performance​

rulewhy
Reuse enginesengine creation dwarfs a small script's execution; a warm engine executes a cached script orders of magnitude faster than it can be created
Prefer jaibite for anything run more than onceparse once, execute many. The handle skips the parse entirely (the VM also caches the compiled chunk)
Repeated execute(string) is already cheapan LRU source cache (64 entries) means identical strings skip the parse
The VM backend is the fast one for hot loopsloops fuse into superinstructions; the interpreter remains the reference for debugging
Pass heavy things by &, declare hot containers with an element typea by-value copy of a big structure deep-copies it; array<int> / array<float> are raw buffers and skip the per-element boxing

Every cost claimed above is quantified. One page, one machine, one protocol: Performance.

Documents​

This site is the specification of the language and its embedding API: where a rule is stated here, that statement is the rule, and a disagreement with the code is a bug worth reporting. The repository holds a few documents beside it, about building and running; the links open them on GitHub.

DocumentWhat it holds
README.mdwhat JaiScript is, and the shortest path to a running engine
BUILD.mdrequirements, CMake options, where the binaries land
CONTRIBUTING.mdhow to build, test and propose a change
tools/jaiscript/README.mdthe runner and REPL
tools/jai_spider/README.mdthe web application host (jai_spider)
examples/gloom · examples/crawler · examples/demoreeleach game's README: flags, keys, the determinism checkpoints