Language Rules​

One rule per row. The guide section linked from a row shows the rule running; an error text quoted here has its row in Errors and Diagnostics.

Two spellings, one meaning​

JaiScript reads as C++ or as JavaScript. Each row's two columns are the same construct. One pair differs in behaviour: a [] lambda captures only what its list names, while an arrow or a function(...) expression captures its whole scope by reference (as [&]).

ConstructC++ spellingJavaScript spelling
Named functionint f(int a) { ... }function f(a) { ... }, function f(int a) -> int { ... }
Anonymous function[x, &y](int a) { ... }: captures what it lists(a) => expr, (a) => { ... }, a => expr, function(a) { ... }: capture by reference
Dynamic variablenone (auto takes its initializer's static type)var x, let x
Null literalnullptrnull
One shared objectmake_shared<T>(args)new T(args)
Floating-point typedouble, float (one type)none (a literal with a . is a float)
Non-public membersprivate:, protected: (one level: the class and its subclasses)none
Textual inclusion#include "x.jai"include "x.jai"
Is it a T?none (dynamic_cast has no spelling: cast<T&>(x) raises where it would fail)x instanceof T
A constantconst auto x = e;, for (const auto x : xs), const auto [a, b] = e;const x = e;, for (const x : xs), const [a, b] = e;

Words from other languages (virtual, delete, goto, extends, constexpr, const_cast, ===, function*, &x) are not reserved; where one fails to parse, the error names the JaiScript spelling (catalogue, Language Bridges). const is a keyword; C++'s other places for it (a const method, a const return type, array<const int>, shared_ptr<const T>) each raise their own text.

Operators​

Chapter: Operators & Arithmetic.

CategoryOperatorsRule
Arithmetic+ - * / %int op int stays int (7 / 2 → 3); any float operand promotes.
String ++Joins text; a char beside a string joins as its character. A number, bool or null beside a string is an error: write to_string(x) or `${x}`.
Unary+ - ! ~+: identity on a number, a char (promoted to its int) and a math value (a copy). - also negates a math value. ~: an int only.
Step++ --Prefix and postfix, 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; the right side runs before any part of the target.
Assignment targetsEvery C++ lvalue: a call returning T&, a conditional of lvalues, (a = b) = c, ++x = v, cast<T&>(e).
Comparison== != < > <= >=1 == 1.0 is true. A char beside a string compares as its one-character string (s[i] == "x"). NaN follows IEEE (false and unequal).
Three-way<=>Returns -1 / 0 / 1; groups all NaNs last; signed zeros are equivalent.
Class testx instanceof TTrue when x holds an object whose class is T or derives from it at any depth (script and host classes, ns::T). A handle answers for its object; a null or expired handle and a value that is no object are false. A relational operator: !(x instanceof T), a instanceof T && b. T is a class: an unknown name, an enum and a builtin type fail the load.
Logical&& || !Short-circuit. !v on a class instance is !to_bool(v); an instance whose class declares no bool to_bool() is true.
Bitwise& | ^ ~ << >>Integers, on the 64-bit pattern. << discards high bits by contract, >> is arithmetic; 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. An int arm and a float arm make a float.
Arrow function(params) => expr (params) => { ... } x => exprA 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 (cast).
Member, index. []
Null-safe member?.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.
Scope::Statics and namespaces. Class::name is the only static spelling; Class.name is not one.

Integer and char arithmetic​

CaseRule
Integer overflow+ - *, unary - and INT64_MIN / -1 raise a catchable error in the default build (JAISCRIPT_CHECKED_OVERFLOW). Integer arithmetic is never undefined behavior.
Division by zeroInteger / and % by zero raise a catchable error. With a float operand they are IEEE: an infinity or NaN.
Deliberate wraparoundmath::wrap_add / wrap_sub / wrap_mul.
char operandsPromote to int (unsigned, 0..255) under arithmetic, bitwise and comparison operators: 'a' == 97 is true, 'z' - 'a' is 25, s[i] > 127 is an unsigned high-bit test (byte 0xC3 is 195, never -61).
int → charNever implicit: type_of('a' + 1) is "int".
Making a charcast<char>(n) (range-checked 0..255), cast<char>("a"), to_char(n), a \xNN literal. Out of one: cast<int>(c), cast<string>(c).

Operators on script classes​

A class declares an operator as a method (Vec operator+(Vec o)). It runs only when the (left) operand is an instance whose class chain declares it; every other operand keeps the builtin meaning.

OperatorRule
Declarable setC++'s overloadable set less && || , -> new delete and !: binary + - * / % & | ^ << >> < > <= >= == != <=>, unary - + ~ (told apart by the parameter count), =, [] and ().
a op= ba = a op b through operator op; there is no operator op=.
=Into an existing object (a slot declared the class, a var): operator= when an overload takes the value, else the implicit memberwise copy. Initialization copy-constructs.
!=, relational!= without an operator!= negates operator==. A relational operator the class does not declare uses <=>.
Truthinessbool to_bool(): if (v) calls it and !v negates it. 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)). A function slot still takes only functions and null: wrap the instance in a lambda.

Keywords​

A reserved word names no variable, parameter, program-scope function or class. After ., ?., -> or ::, or in a member declaration, it is a name (keyword member names).

WordMeaning
int float bool char string voidThe scalar types and the no-result return type.
var letA dynamic variable; let is var.
autoA variable that takes its initializer's static type (The Type Ladder).
array map shared_ptr weak_ptrContainer and handle types, written with arguments: array<int>, map<string, int>, shared_ptr<T>.
functionA function declaration or expression (spellings); 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: instances cannot be copied.
public private protectedAccess labels; protected is private, which admits the class and its subclasses.
staticA member of the class, not of an instance (C::s).
overrideA method that replaces a base class's method.
this superThe receiver in 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>; the same expression as make_shared<T>(args).
enumA scoped enumeration (E::v); its name is a type (Kitchen Sink).
namespaceA named scope (ns::x).
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; fallthrough; continues into the next case (rule).
try catch throwExceptions (rule, typed catch).
coroutine yieldA coroutine declaration (coroutine prefixes any function spelling); a value it hands out (Coroutines).
parallel_forA data-parallel loop (Parallel loops).
castcast<T>(e), cast<T&>(e), cast<shared_ptr<T>>(e) (cast).
include importTextual inclusion of a file; a module, its own unit (include vs import).
##include "x" is include "x" and #import is import. A # begins nothing else but a #! shebang on the first line.
extern inlineA name defined outside the program; a program-scope variable every declaring unit shares (extern).
true false null nullptrThe literals; nullptr is null.

Words with a meaning in one position only, and names everywhere else:

WordPosition
operatorAn operator declared in a class (operator+).
usingusing ns::name; at the start of a statement.
dodo { } while (c); when a { follows.
doublefloat, where a type is read.
utf8_stringThe text type, where a type is read.
uint8 int8An array's element type only.
[[warn_mode]] [[strict_mode]] [[nodiscard]]The attributes (attributes).
instanceofThe class test, after an operand (class test).
hot_reload_migrateReserved for the reload hook at a file's top level.

Type conversions​

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 = cast<int>(3.99);   // a float enters an int only through cast
bool b = 5;                // truthiness
string s = to_string(42);  // text only: stringify explicitly
print("{} {} {} {} {} {}", m.v, n, f, t, b, s);
output
3.0 40 7.0 3 true 42

One table governs declarations (int x = e), assignment (x = e), by-value parameters, return values, instance and static field stores, and the elements of typed arrays and maps. A conversion is one step: a converting constructor or a to_X() method runs, never a chain (X.to_int() then C(int) is not implicit); one that throws shows its own error. Between typed sides a mismatch fails the load; out of a var the same rule raises at run time (Diagnostics).

TargetAcceptsRefuses
intint; bool (true → 1); an enum value; an object with to_int()A float (rule). string, char, null, containers: no implicit parse ("42".to_int()).
floatfloat; int (widens); bool; an object with to_float()A string: s.to_float().
boolbool, int, float, null, an object: truthiness (5 → true, 0 → false)A string, an array or a map into a declared bool: a load error (!s.empty()). Conditions keep truthiness for all of them.
enum Ea value of E; an int out of a var that is one of its valuesAn int or another enum's value: cast<E>(n) converts an int that is one of its values.
string / utf8_stringeither text kind (a utf8_string's bytes, a string's characters); a char (its one-character text); an object with to_string()int, float, bool, null, containers: no implicit stringify. Write to_string(x) or `${x}` (Printing values).
charchar; a string of exactly one character; an object with to_char()A string of another length: Cannot convert 'string' to 'char': a char takes a string of exactly one character (a literal fails the load). char c = 65: write cast<char>(65).
array<T> / map<K,V>an array / a mapAn element or key of the wrong type, on every store: at load where the checker sees both types (cannot push into 'array<int>': expected 'int', got 'string'), else at run time (Cannot store string in array<int>, Cannot push 'string' to array<int>).
an elementits exact type; an int into a float element; bool and int both ways; char and string both waysAnything else: no truthiness and no to_X() at the element boundary.
a map keythe declared key type; a char key and its one-character string are one key (stored as the declared type)Keys never coerce numerically.
math::vec2 vec3 vec4the same type; the ivec of its dimensions (widens, as an int into a float)Another dimension, a color, a number.
math::ivec2 ivec3the same typeThe vec of its dimensions: cast<math::ivec2>(v) truncates each component toward zero (a load error where both types are visible, the slot's error through a var). A constructor converts what it is handed: math::ivec2(v).
class Cthe same class; a derived class (it keeps its class in the slot: B x = D(); type_of(x) is "D"); a shared_ptr to C or a derived class (copies the pointee); a value exactly one single-parameter constructor C(src) accepts directly; on plain =, a user operator= of C that accepts the sourceA parent, a sibling, an unrelated class of the same shape; arrays, maps, functions. null: a class value is never null (rule).
shared_ptr<T>a handle to T or a subclass; a T value (construct-and-share); nullAnother pointee: shared_ptr<Bar> p = Foo() is a load error when the checker sees both classes, else Cannot initialize shared_ptr<Bar> with Foo: type must match or be a subclass. A shared_ptr<Base> into a shared_ptr<C>: cast<shared_ptr<C>>(b).
weak_ptr<T>a handle to T or a subclass; nullAn instance: it has no shared owner.
functiona function, a lambda, a bound method; nullAnything else, an instance with operator() included: wrap it in a lambda.
T& (declaration or parameter)an lvalue that holds a T (a class: T or a subclass); a var only while it holds oneAny conversion: float& r = v with v holding an int raises Cannot initialize 'r' (declared float&) with int.
T& parameter given a literal or temporarywhen its body never writes through it; a string& parameter takes one as a writable temporaryA write-through parameter: the load error 'bump' writes through parameter 'x' (line 1); pass a variable.
a store through T&what the reference's declared type takes (var v = 0; int& r = v; r = 3.9 and r = "s" are load errors)What the storage's own declared type refuses: int x; var& w = x; w = 2.7 raises Cannot assign float to 'w' (declared int&).
varanything, foreverNothing. Everything computed from a var is unknown to the checker and can only fail at run time.
auto with an initializerthe initializer's type, then enforced as that typeA null initializer carries no type: 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 and stays silent.
CaseRule
a op= b, typed or locked-auto targetComputes like C++ and converts back to the target's type: int i = 7; i /= 2 → 3; i += 0.5 and i += "s" are errors.
a op= b, var targetStores whatever the operation produces.
a op= b, missing map keyA compound reads its target first: map<string, int> m; m["x"] += 5 raises Key "x" not found in map.
Value of an assignmentWhat the destination stored: float f; auto r = (f = 3) is float 3.0.
Printing a floatThe shortest text that reads back to the same value, keeping .0 on a whole number; inf, -inf, nan (every NaN prints nan, under a format spec too).
Format specsTemplate strings and {} placeholders take them: ${f:.1f} (format specs).

The typed boundary​

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'
FormYieldsRule
cast<T>(e), class Twhat T tmp = e; would holdA value copy when e's class is T or derives from it; the copy keeps e's class. On a handle or a reference it copies the instance: the checker warns and suggests the two forms below.
cast<T>(e), primitive Tthe conversion table's resultPlus float to int, truncating toward zero (cast<int>(2.7) is 2), and an int to the enum value it equals.
cast<T&>(e)a reference to the same objectMember reads and stores go through: cast<Derived&>(h.p).t = 5 reaches a subtype's member through a base-typed receiver.
cast<T&>(e) as a targetan lvalueIts declared type governs the store: cast<int&>(x) += 2 leaves an int in x, even when x is a var.
cast<E>(n), enum Ethe enum value that equals nCannot cast 3 to 'E': not one of its values otherwise.
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.
cast<string>(e)e's display text, as to_string(e) gives itThe explicit stringify; a char is its 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'.
a class mismatchthe catchable Cannot cast 'X' to 'T'X is the object's run-time class. The operand is never checked against T statically.
nullan empty handle from the handle formFails the value form (Cannot cast 'null' to 'T') and the reference form (Cannot cast 'null' to 'T&').
a T that names no typea load errorunknown type in cast: 'Nope'.

Chapter: The Type Ladder.

extern, inline, using​

SpellingRule
externDeclares a name that exists outside the program (a host global bound after load, a function the running instance registers, a value another engine injects). The checker counts it as declared instead of reporting use of undeclared identifier.
extern at run timeDeclares nothing; the name resolves when used. A use while nothing defines it raises 'name' is declared extern but never defined (an 'inline' declaration creates it) (extern function: without the parenthesis).
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 as for a declared variable of that type.
extern config;Untyped: behaves like var.
extern function game::spawn; / extern int a::b::depth;A namespace member, in any of the three strengths. The namespace declares it (game::spawn is not no member 'spawn' in namespace 'game'), and the path opens the namespace for its other members' checks.
Where it standsAt program scope or in a namespace, where its name is the namespace's: namespace game { extern int tick; } declares game::tick (read as tick by the namespace's own blocks); namespace game { extern ai::depth; } declares game::ai::depth.
A qualified extern nothing definesRaises the same text with the qualified name: 'game::tick' is declared extern but never defined (an 'inline' declaration creates it). The definition is a namespace block of any unit (namespace game { int tick = 0; }) or the host's registration of that namespace.
ReachAn extern name travels with the program: every unit the root imports sees it. Where a program does import the defining module, the declaration is inert and the definition wins.
inline T name;The C++17 inline variable: one variable shared by every unit that declares it. The first unit to run creates it with its initializer or its type's default; the rest adopt it and their initializers do not run.
A plain declaration of an inline variableRaises 'x' is already defined inline; declare it 'inline int x;' to share it; at load, with this text, when the checker sees both units.
An inline declaration of another unit's plain variableRaises 'x' is already defined (not inline) in <file>; declare every definition 'inline' to share it.
using ns::name;The rest of the file reads name as ns::name, for any namespace; a comma list works (using game::spawn, game::cost;). 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.

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

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 statement's own subtree; a nested function or lambda body inside it keeps the enclosing mode.
ModeRule
Warn regionEvery error is reported as a warning (still collected, still visible through engine->check()). An undeclared identifier is accepted as unknown; nothing is proven from it.
Strict regionErrors gate the load, even inside a warn region.
Run timeNever changes: both backends treat the attribute as nothing, so a warn region's type mistakes raise at run time.
ImportsA unit's mode says nothing about the units it imports.
[[nodiscard]]Marks a function or a method whose result a caller must use (Functions). Any other attribute name is a parse error.

Language rules in one place​

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

Checking and program shape​

RuleStatementChapter
Checking is always strictA typed-to-typed mismatch or an undeclared identifier fails the load; a [[warn_mode]] region reports its errors as warnings. Only a var-sourced value can still fail at run time, where try/catch sees it.Static Checking
Program closureA file whose import/include paths are literals is the root of a program: every unit they reach is gathered and checked before anything runs. Names, class models, function signatures and global types cross units; import order does not matter for names.Modules
A qualified name is checked as the bare onens::f(x), ns::limit = v and a read of ns::limit check against the namespace's declarations, at any depth and through using ns::name;.Caught at load
Builtin and host calls check their argumentsA builtin method and a math:: function take the shapes the API reference states (a number, text, an array, a map, a function, a vector); a host function or bound method takes what its C++ signature takes. A name declared extern function f; is unchecked.Caught at load
Dynamic pathsA dynamic-path import(expr) keeps undeclared identifiers at warning severity in that unit.Modules
Top-level code runs in orderA top-level statement sees the declarations above it. A function or method body may name a later declaration: it runs only when called, and what it names must exist by then.What the checker sees
A call before the declarationFails 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 read before the declarationA global read, a class constructed or a function taken as a value before its declaration raises Undefined variable 'G'.
Namespace top levelRuns in order the same way. Until its declaration runs, a bare name the namespace declares further down reaches an enclosing namespace's member or the global; with neither it is the same load error.
One declaration per scopeA block, a parameter list or one file's top level that declares a variable name twice is a load error (redeclaration of 'x' in the same scope).Static listing
Shadowing is legal and warned aboutA local shadowing a field or an outer local is a warning whenever the checker runs; warnings never gate.Functions
An assignment as a condition warnsif (a = b), while (a = b) draw assignment used as a condition: write '==' to compare, or '((a = b))' with parentheses of its own to assign; if ((a = b)) is silent.Control Flow
A lost write fails the loadA store into a by-value copy of a known value type (a range-for variable, a parameter, a local, a field or element of one) that no path reads again before the copy dies, and nothing else can observe, is a load error naming the reference spelling.Lost writes
A dropped [[nodiscard]] result fails the loadAn expression statement whose value is a call to a [[nodiscard]] function, method or host callable, or to a library copy such as string::to_upper or array::sort. The check is static only.[[nodiscard]]
Format text is checked at loadA print or format literal's placeholders must pair with its arguments, a literal alone included (print("{}") has a placeholder and no argument); format(s, ...) takes only a literal (vformat formats a run-time text).print and format

Types and values​

RuleStatementChapter
A float never enters an int slot implicitlyCompound assignment, insert, a range-for variable, a coroutine's yield or return and a store through an int& included: a load error between typed sides. cast<int>(x) and math::itrunc(x) truncate toward zero, math::ifloor(x) rounds down. An int widens to a float everywhere.Type Ladder
A class value is never nullA slot declared as a class by value (a variable, a field, a static, a parameter, a return) refuses null, and a class-returning function returns on every path. shared_ptr<T>, weak_ptr<T>, var and function hold null.Type Ladder
A class holding itself by valueA load error, through fields or a base, in a warn region too: make one link a shared_ptr<T>.Static listing
An enum name is a typeAn E slot holds E's values and starts at the first. A value converts to int; an int enters only through cast<E>(n); var v = E::a holds the int. The bare name is its value table, a map<string,int>.Enums
Builtin method results are typedOn a receiver of declared type a builtin method has its catalog result type at load: int bad = xs.join(","); is a load error. s.to_int() is null for text that does not parse; s.to_int(0) is an int.API
DefaultsA declaration without an initializer takes its type's default: a class default-constructs, int 0, float 0.0, bool false, an enum its first value, string "", containers empty, a math vector zero; var stays null.Classes
A reference has no defaultint& r; raises Reference variable must be initialized.
Aggregate initializationA class that declares no constructor takes Pt p{1, 2}, Pt{1, 2}, Pt(1, 2): instance fields fill in declaration order (bases first); the rest keep their initializers.Classes
Value semantics; references by declarationAssignment, 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
== is memberwise on heavy valuesArrays elementwise, maps entrywise, instances of one class fieldwise (a user operator== first); handles by pointer; same_object(x, y) is identity. contains, index_of, has and switch cases use the same ==.Operators
A missing map key raises on readKey "k" not found in map, and the read does not insert. m[k] = v inserts. get(k, default) is the explicit fallback. A dot store on a map the checker knows is a load error: store the key as ["k"].Maps
In-place methods return nothings.to_upper(), xs.filter(f), …: using the result, or chaining, is an error. The string::, array:: and map:: functions return copies, and + never mutates.In place or a copy

Functions and evaluation​

RuleStatementChapter
Evaluation order is contractualThe callee is resolved before any argument; arguments go left to right, each taking its value when evaluated (pick()(arg("left", 1), arg("right", 2)) records callee left right).Functions
Assignment ordert = e and t op= e evaluate e before every part of the target (a member's receiver, a subscript's container and index), as in C++17.
Captures are exhaustive[] captures nothing and [x] only x. Using an unlisted enclosing local is the parse error Lambda capture list does not capture enclosing local 'n'; name it or use [=] or [&].Lambdas and captures
Capture forms[=]/[x] copy at creation; [&]/[&x] share the variable (and outlive its frame); [x = e] initializes the lambda's own x; [this] captures the instance.
Program-scope namesNever captured by default: the lambda reads them when it runs.
A captured for variable is each iteration's ownA C-style for variable captured by reference is copied forward before the update. A loop no closure shares compiles as it would without the rule.Lambdas and captures
Default arguments cannot name parametersA default that reads a parameter of its own list is a load error.Functions
Typed returns are requiredA function declared to return a primitive, a string, an enum, a class value or a reference must return on every path: 'f' declared to return 'int' may reach the end of its body without returning.Strict checking
A return conversion is part of the returnIt 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
Coroutine spellingscoroutine prefixes every function form, at top level, in namespaces and as methods (static ones too): coroutine int g(), coroutine g() -> int, coroutine function g() -> auto, coroutine function g(), bare coroutine g().Coroutines
Coroutine limitsNo coroutine lambdas. No reference parameter: a coroutine runs on after the call returns, so it takes copies or a shared_ptr.

Classes​

RuleStatementChapter
Fields initialize in declaration orderA field initializer reads the fields above it, its bases' fields, the statics and the enclosing scope, never a constructor's parameters; reading its own field or a later one fails the load. Its value lands as the declaration T f = e; would, so a class value field refuses a null initializer.Classes
Methods are called or boundobj.heal without a call is an error, at load when the checker knows the receiver: 'heal' is a method of Player; call it with player.heal() or bind it with reflect::bind(player, "heal"). reflect::bind(obj, "name") is the method as a callable that holds the object.Classes
A builtin method read without a callRaises the same way: 'push' is a method of array; call it with arr.push() or wrap the call in a lambda.
Access control is two levelspublic: for everyone; private: (and protected:) for the declaring class and every class derived from it. Members start public.Classes
Outside the familyCannot access private member 'x' of class 'C'; for a private constructor (checked on the overload resolution picks) Cannot access private constructor of class 'C'.
Loads ignore accessfrom_json and from_binary reconstitute the saved state: they run the default constructor whatever its access, or create an instance without one when no constructor takes zero arguments.
StaticsEnforce their declared type; spelled Class::name, or bare name inside the class's own methods. Inherited as in C++: a subclass sees its bases' statics bare, and Derived::name names them.Statics
User accessors winIf a class (or an ancestor) declares _get_x / _set_x, obj.x reads and writes through them. A bare x inside the class's methods is the backing field, so an accessor reads its own field without recursing.Name resolution
A reserved word is a member name when qualifiedAfter ., ?., -> or :: every word is a member name (p.catch(f), obj.default, spider::catch(url), Mode::default, super::catch()). Every reserved word but this and super declares a field, a method, a namespace member or an enum value.Keyword names
Keyword members, bare and boundBare, the word stays the keyword: a statement catch(x); and a program-scope void catch() {} are parse errors; a class reaches its own as this.catch(x). A host binds one the same way (.method("catch", ...)); reflection and archives use the word as written.
C++ property references bind the memberA bound property passed to a & parameter (f(this.value), f(obj.value)) reads and writes through the host getter and setter, like any field lvalue.Value Semantics

Control flow and errors​

RuleStatementChapter
switch breaks by defaultWrite fallthrough; to fall through. A case with no statements shares the body of the label below it. break in a case leaves the switch only.Control Flow
do { } while (c);C++'s: continue reaches the condition.Control Flow
The catch variable is the thrown valuecatch (Type e) takes a class (and its subclasses), an enum's value, a builtin kind or error; clauses run in order; catch (e) takes everything and is last; an error no clause takes goes on.Typed catch
error and e.kindcatch (error e) takes a run-time error; e.kind is one of the error_kind:: names: null_receiver, no_member, name, not_callable, arguments, type, arithmetic, bounds, key_not_found, container, reference, limit, host, other.Error kinds
Budget and memory cap are terminalThe execution budget cannot be caught by script; the memory cap gives one catchable raise, then goes terminal. Budget and second memory-cap errors pass every catch clause.Safety Rails
A .jaibite carries its format versionThe version moves whenever the parser's output changes; a bite loads only in a build with the same version. Proofs are never serialized and are re-derived on load.jaibite
A bite of another versionThe automatic disk cache silently reparses a cache file of another version or of another source text. An explicit jaibite_load fails with jaibite: unsupported format version N (expected M), M being the build's version.

include vs import​

Chapter: Modules.

include "f.jai"import "f.jai"
RunsEvery time it is evaluated.When first reached, then again when its file changed (checked in passes: import_recheck_interval, default 0.2 s).
ScopeExecutes in the current scope.Module declaration.
Expression?Yes: var cfg = include "config.jai"; evaluates to the file's last expression.The 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 pathsinclude(expr)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: that unit's undeclared identifiers drop to warnings.
IdiomForcing a re-run: hot reload of rules or content mid-game.Declaring the module graph at the top of each file.
Persistent stateGoes in a module that is only imported and never included, so a reload cannot wipe it.

The jaibite disk cache​

AspectRule
WhereEvery file-based load keeps one in a .cache folder beside the source (foo.jai → .cache/foo.jai.jaibite), or below the host's cache directory.
HitA 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.
EffectParse-level and side-effect-free: it never changes whether a file runs. Silent on write failure.
Offeng->jaibite_cache(false)

Import modes (engine::import_behavior)​

ModeBehavior
file_timestamp (default)Re-runs a module when its file's (modification time, size) stamp, or that of a file its last run included, changed.
Recheck passesEngine-wide, at most once per import_recheck_interval (0.2 s by default; 0 checks on every reach).
A module whose file disappearsRaises Failed to open import file once, then waits until the file is back.
A stamp that moves during a readNever re-runs a loaded module from that read; 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.
onceImports exactly once; ignores later file changes.
alwaysTreats import as include (runs every time).