An engine, always a shared_ptr<engine>. No global state.
jai::stdlib::register_all(eng)
Opt-in stdlib: formatted print, format, to_string, to_json, time::, reflection, other optional helpers.
A bare engine
Engine core (math, array transforms, text and binary primitives), basic print, format_value.
eng->execute(src)
Runs a script; returns the last expression's value, a jai::script_value. A script the checker rejects throws jai::static_check_error before anything runs.
Calls a script function; returns checked_result<script_value> (text: eng->format_error(r)). An uncaught script throw arrives as jai::script_exception.
engine::external_call_guard
Wraps that call only while a script is running.
jai::convert_script_function<Sig>(fn)
Typed std::function over a script function. A script error or uncaught throw throws jai::runtime_error to the C++ caller; a host function called from script returns it as that call's error.
v.as<std::vector<T>>() / as<std::map<K, V>>()
Typed arrays and maps copy out.
as_array()
Views an array<var> only.
unchecked_array_node()->for_each(eng, f)
for_each walks any array kind.
eng->set_backend(jai::backend_type::interpreter)
The tree-walking interpreter; the bytecode VM is the default. Before the first execute only, in a program that links jaiscript_interpreter.
eng->set_output_stream(s)
Redirects print(); nullptr restores std::cout.
eng->set_script_error_handler(fn)
Sink for script errors nothing can catch (a destructor failing at engine teardown).
Typed host function. Repeated names form an overload set resolved by argument types.
Non-const T& parameter
The callee's final value is written back into the script lvalue argument (variable, field, element, map value, static, C++ property, vector component v.x) by that slot's store rule. A literal or temporary is a copy.
std::shared_ptr<T> or std::weak_ptr<T> parameter
Takes null as an empty handle.
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>: print-shaped functions, optionals.
eng->add_host_direct(name, arity, invoke, target)
Direct entry for the function name holds: a VM call site with arity non-reference arguments calls invoke(target, args, arity) over the arguments in place. The function takes every other call.
eng->add_global(name, value)
Copies a value in.
eng->add_global_ref(name, var)
Live binding (cpp_bound) to a C++ primitive or string: script reads and writes the real variable.
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: 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: catchable, denies in place. Second: terminal. 0 = unlimited.
eng->max_call_depth(jai::backend_type::vm, n)
Recursion cap per backend; default interpreter 256, VM 250,000. Overrun is the catchable recursion error (depth limits).
eng->throw_on_overflow()
The build's integer-overflow policy (checked = default).
The VS Code (DAP) listener. A taken port does not throw: listening() is false, error() says why.
eng->add_include_path(dir)
Search root 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; no .cache is written beside a source outside them. Empty (default) opens any readable path (Safety Rails).
A reached import asks the filesystem whether its module changed at most once per window. Default 0.2 s; 0 checks on every reach. Script builtin: import_recheck_interval(s).
Off by default. On: each field operation of a class reload on live instances, and each saved value a load converts or cannot fit, aggregates into std::vector<jai::field_migration_report> until cleared.
Parses (cached) and checks without running; returns jai::check_report.
eng->last_check_diagnostics()
Report of the last checked execute/bite, warnings included.
eng->type_proofs(bool)
Default on: type proofs let both backends skip conversions shown to be identities, each behind a run-time guard. type_proofs(false) is observationally identical (same values, output, error text), slower.
Default on: a by-value parameter or local provably only read binds its argument or source instead of a copy (Choosing a strategy). Off copies every time, observationally identical.
eng->jaibite(src)
Parses once into an engine-bound handle; bite.execute() skips the parse, and the VM caches the compiled chunk. Checked at creation; strict throws there.
bite.checked_clean() / bite.check_diagnostics()
That check's result.
bite.save(path) / save_bytes()
The parsed AST as .jaibite, stamped with the build's format version (jai::detail::k_jaibite_version).
Another format version
The load fails: jaibite: unsupported format version N (expected M). The automatic disk cache reparses instead.
eng->jaibite_load(path) / jaibite_load_bytes(b)
Loads into any engine: symbols re-intern, one file serves both backends. A registration fingerprint that differs from this engine's re-checks the bite against this engine's surface.
bite.registration_mismatch()
Advisory: the registered surface differs between saving and loading engines. Covers every registration form, zero-arg and variadic functions included; a load never fails on it.
eng->jaibite_cache(bool) / jaibite_cache()
Disk cache for file-based loads, default ON: include/import/execute_file keep .cache/foo.jai.jaibite beside foo.jai, making the folder when missing.
Cache validity
Loads only for the exact source text it records (size + hash) and a matching registration fingerprint, whatever the mtimes. Anything else reparses and rewrites it.
Cache writes
A temporary file renamed into place; silent best-effort, once per source text. jaibite_cache_write_failures() counts them.
Caches go below dir at the source's path relative to root (outside it, or with no root, its 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 written for source; empty for a .jaibite source.
The boundary never copies; from there a bound object behaves as a script class instance does (chapter).
Script
Result
A value into a variable, field or element (Turret b = a;, var c = b;), a by-value parameter or return, a container literal element ([a]), a by-value capture ([a], [=])
A separate C++ object made by the copy constructor.
A call's T& result into var c, auto c or Turret c
A copy, as C++ auto drops the reference.
A T& argument the host passes into a var p or Turret p parameter
A copy.
A reference: var& g = gate;, auto& c = owner.child();, C& c, a & parameter
The same object. c = other; runs the class's bound =.
A handle: new Turret(...), a shared_ptr<Turret> variable, a std::shared_ptr<Turret> the host returns, passes to a callback, or gives add_global or make_object
Every copy of the handle shares the one object: var g = gate; names the host's gate.
A class with a deleted copy constructor (std::atomic, std::mutex)
Each copy above raises Cannot copy C++ object of type 'T': the type is not copy-constructible. Hold it by reference (auto&/var&) or share it (shared_ptr<T>).
A typed slot the object converts into: int n = obj; as a declaration, assignment, parameter, return or field
Calls the bound to_int on the object itself, with no copy.
A float, bool, char or string slot
Calls to_float, to_bool, to_char or to_string.
Assigning into a variable, field or element declared C (a = other;)
Runs the bound = on the held object. With none bound, a copy-constructed object replaces the held one.
Assigning into a var variable
Takes a copy of other in place of what it held.
A bound property passed to a & parameter: gate.level, or this.level or a bare level inside a method of a script class deriving Spawner
Binds the property itself; the callee's writes land in the C++ member.
A bound property in a lambda
A [=] lambda copies its value at creation; a [&] lambda reads the live property.
A constructor overload, resolved by arity and C++ parameter types.
.constructor<>()
Prefers a T(engine*) constructor when the type has one.
.method(name, &T::fn)
A member function, const or not. A member data pointer here is a compile error that points to .property().
.method(name, lambda)
A lambda whose first parameter is T& or const T& is a method on that object.
Any other lambda
A method that ignores its receiver (.method("twice", [](int64_t k) { return k * 2; })).
A stateful functor
Keeps one state on every call path.
Overloads
Registering a name again with another C++ signature, same arity included, adds an overload.
.static_method(name, fn)
A free function or lambda, called as Vec2::axes(). Bound-class arguments work.
A static method returning a bound class
Does not compile: return primitives or strings, and bind a constructor overload or an engine-level function as a factory.
.property(name, &T::member)
Field access. A setter is generated only for a copy-assignable member type. For a class with no serialization route of its own the member is a key of its archive record, written and read in place; a const member stays out (binding records).
A std::vector<E> member
Binds as a live view without a copy (size, push, pop, at, []).
A computed property from lambdas or a member function pair. A nullptr setter makes it read-only: a store raises Cannot assign to 'x': it is read-only. In a binding record the getter's value is written, and a load reads over it and hands it to the setter; a read-only one stays out.
The same property, kept out of the class's binding record, as JAI_TRANSIENT_PROPERTY is out of a property_owner's.
A member of a class type not registered yet
Binds; the type may register later, in any order (std::shared_ptr<Node> next inside Node's own chain). The first load, check or host call into script raises Bindings are incomplete: with one line per type still missing: the class and member, the C++ type and the registration to write.
A class-level value, read and written as Class::name. The pointer form binds the live variable.
.base_class<Base>()
An inheritance edge: base methods and properties are callable on the derived with virtual dispatch, and a Base slot may hold this type on a polymorphic load. Diamond inheritance throws. A binding record writes the base's binding first, and a polymorphic one takes its $type from the binding's name.
.auto_bind(mode)
Everything the declarations know: property_owner schema properties, observables and signals; base classes; and to_string(), size(), empty() and ==/!= where the class has them. Modes: all, properties, hierarchy.
static T::jai_auto_bind(dynamic_binder<T>&)
A hook through which a class binds its own private members.
.transparent_wrapper(unwrap)
An operation not found on the wrapper retries on the unwrapped value: gate.threat + 2 reads the observable's int while .on_change stays on the wrapper.
.post_load_hook(fn)
The class's post_load, [](T& self) or [](T& self, int version), run by a script-side load (from_json, from_binary, from_base64) after it writes the fields, and by a C++ archive after it loads a binding record.
.version(n)
A number (default 1) for the script archives only; the C++ archives never read it (versions).
.deserialization_factory(fn)
Constructs a non-default-constructible type on load. The archive-only, context-only and context-plus-archive signatures are detected.
The engine this binder targets, for a configure function that calls bind_signal_type or adds globals.
.build()
Optional: commits the registration there and throws what fails. Without it the binding commits when the binder's lifetime ends, or at the engine's first load while the binder is still alive; a failure then joins the Bindings are incomplete: error of the next load. Chain calls after the commit apply to the registered class. A binder moves but does not copy.
Operators are methods named by token and follow the script rules of the operators chapter.
Binding
Rule
Tokens
+ - * / % & | ^ << >> == != < <= > >= <=>, unary - + ~ (a lambda taking only the object), [], (), =, and to_bool for !v and if (v).
Supplied
<=> supplies < <= > >= and ==; == supplies !=; 5 == m asks m's == taking an int.
++, --
C++'s two forms, which a script class cannot declare: a lambda taking only the object answers ++v; one taking the object and an ignored int64_t answers v++.
A missing ++ or -- form
Cannot apply '++' to T
Compound ("+=" ...)
Runs once on the object in place, for a variable, field, element, map value or reference. Nothing is stored back; the expression is what the operator returns.
Compound, none bound
m += o is m = m + o.
Reflected
A lambda whose first parameter is another type and whose second is the class, C++'s free operator-(int64_t, const Money&), answers 5 - m.
No reflected form
Arithmetic never reverses on its own; the checker warns at a number left of a bound class's operator that has none.
Overload choice
An operator counts for an operand only when one of its overloads takes it.
operator<(const Money&) and operator<=>(int64_t) bound
m < 5 runs the <=>.
Only operator+(const Money&) bound
m + "!" is the builtin concatenation of m's text.
No match
No bound 'Money * int': bind operator "*" taking int on Money, or convert the operand
The script surface of a bound std::atomic (chapter).
Script
Types
Value
a.load(), int n = a;, float f = a;, bool b = a;, if (a)
all
The value. A typed slot converts through the atomic's own value type; an int or float one is true when non-zero.
a.store(v), a = v
all
= yields v.
a.exchange(v)
all
The value before.
a.compare_exchange(expected, desired)
all
C++'s compare_exchange_strong: stores desired and yields true when the value equals expected; else writes the value into the variable expected and yields false.
Registering a name again, or registering "filesystem" (the built-in flag)
Leaves the grant as it is.
An unknown name
A query is false. Enabling, disabling, requiring or attaching it to a callable is rejected, never auto-registered: register names before binding functions that require them.
A missing permission
Insufficient privilege: savegame.write.
Several missing
The text names a built-in first, then the first custom one in registration order, whatever the list order.
Several engines
Names are reusable across engines; registration and grants are per engine. There is no global registry.
Cost
A binding resolves its requirements once to engine-local slots: a call's check looks up no name, allocates nothing and takes no lock or atomic. Registration and binding may allocate.
Only script calls have a counted cap; the errors are catchable, the static listing's aside (chapter).
Depth
Bound
Error
Script calls
A per-engine cap for each backend: 256 active calls on the interpreter, 250,000 on the VM.
Maximum recursion depth exceeded - possible infinite recursion (uncaught, the error reads as this text alone)
Native stack at a call
Less than 256 KB of stack left, whatever the cap.
Native stack exhausted before the call-depth cap: the host thread's stack bounds this recursion
Which stack
The host thread's, or an engine::start run's own fiber stack. Windows, Linux, Android and macOS report its bounds; a stack the OS calls unlimited counts as 1 GB.
Source nesting: parser, checker
Each stops with 192 KB of stack left.
nesting too deep for the host thread's stack in the static listing, one error at the deepest point reached
Source nesting: VM compiler, interpreter's evaluation
The same margin.
Nesting too deep for the host thread's stack when the code runs
A platform that cannot report its stack's bounds
The source walks stop 1 MB below where the walk started: give a script-loading thread there more stack.
Saved-program nesting
The .jaibite writer and reader stop at the parser's margin: what loads from source also saves and loads as a bite on the same thread.
jaibite: AST nesting too deep or jaibite: type nesting too deep
Nested data
None but memory: JSON parsing, from_json/from_binary, to_json/to_binary and a deep copy walk a value on work lists.
Host record types
A host type whose own serialize/save/load/load_and_construct recurses natively stops at the stack's margin, or at 1024 levels (JAI_MAX_HOST_RECORD_DEPTH) where the stack cannot be asked.
Record nesting depth exceeds the host thread's stack or Maximum record nesting depth (1024) exceeded
A member missing from a fully known class is a load error (Members).
Member
Rule
Accessors
A member a user _get_x/_set_x declares anywhere in the chain is known, and typed when the declaration is unambiguous (the map lists the ambiguous shapes).
One parameterless, non-coroutine int _get_hp()
Makes char c = obj.hp an error.
One by-value void _set_hp(int)
Makes obj.hp = "s" the error cannot assign 'string' to property 'hp' declared 'int'.
Bare names in a class
A bare name in a method resolves locals, then members, then globals, the run-time order: a bare x is checked as the field and ::x names the global.
Enum values
Checked for existence: no value 'zzz' in enum 'Direction'.
Builtin methods
A method of a known string, array or map: its name, its argument count and each argument's shape (a number, text, an array, a map, a function) as the API reference states it.
Bound methods
A method the binder bound on a known receiver: its argument count and each argument against the C++ signature, unless a script class below the bound class declares the method or the object was extended.
Unchecked
Subscripts, and the handle builtins of a shared_ptr (use_count, cpp_ref_count, unique, same_as, reset).
A class method named as a builtin
Wins. Where the program holds that class or a subclass by shared_ptr, the method warns that the builtin is unreachable on its handles; a class held only by value keeps its reset() unwarned.
Open classes
An ancestor the checker cannot resolve opens the whole class: no member diagnostics.
The whole list of spots where the checker is silent: a mistake there is a run-time error, never a load error (chapter).
Lenient by design
Why
var anywhere, and everything computed from it; a null initializer
No declared type to check against.
auto without an initializer
auto locks at a store the checker cannot order.
Stores through an auto&, a var& or a reference to a class
The referenced variable's table runs at run time. A store through a T& of a scalar, text, container or math type is checked as a T slot's; binding the reference and member lookup through it are checked too.
Container elements the checker cannot type: map keys, elements of untyped containers and var receivers, subscripts, builtin container/string method results
The runtime element boundary is the check.
Literal elements, pushes and element stores into a declared array<T> or map<K,V>
Checked.
Engine globals (typed off the value tag; a later retag is invisible), host-object conversions, callables held in variables
No static signature in view.
Host parameters that take any value or a bound object, a bound method a script subclass declares again, binder properties, a name declared extern function f;
The C++ signature does not decide them. A host function's and a bound method's count and primitive, text and container arguments are checked.
A function or global two units declare, a class-typed global of another unit
More than one declaration could answer, or the slot may hold a subclass. One unit's function signature and typed global are checked in every other unit of the program.
Dynamic-path import(expr)/include(expr) units (undeclared identifiers drop to warnings, unknown types and namespace members too), untyped extern names, a parent found nowhere
Such a unit or name can define anything.
Accessor-bearing members whose declaration is ambiguous: two getter/setter overloads, var/auto/coroutine/parameterized getters, reference or untyped setter parameters, an accessor on a registered-only ancestor
No single declaration to trust.
Coroutines: yield and return types, and what a handle's next() returns
The checker does not model what a coroutine yields. The handle itself is typed: next and done are its only members.
Mixed-branch ternaries, this/super as values, extended classes and everything passed to extend(...)
The archives keep no class version: a reader finds a property by name (Versions and missing fields). The std::uint32_t version parameter of a member save, load or serialize carries a chosen number only for a record the code opened itself.
Case
Version
A save
Always receives 0.
A load reached through a named read (ar("name", obj), a JAI_PROPERTY, a non-polymorphic pointer's $val)
Receives the number stored in that record.
Every other load (ar(obj), a polymorphic pointer's $val)
Receives 0.
ar.get_version()
Answers the same number for the record opened last.
A record the archive opens itself
Stores none: JSON writes no "$type" or "$version" key, which reads back as 1; binary stores an empty type name and 0.
The root record of to_json / to_binary
Version 1, which JSON leaves unwritten.
begin_object(type_name, version) in a hand-written save
Binary keeps any number. JSON writes "$version" only above 1, so 0 reads back as 1.
A binder chain's .version(n)
Never reaches the C++ archives. A number (1 by default) the engine keeps for the class, used only by the script archives for bound objects inside script values.
Script to_binary / to_base64
Write it after the class name, beside each bound object.
Script to_json
Writes no "$version".
from_binary / from_base64
Hand it to the class's .post_load_hook only when the class also has a .deserialization_factory. Without a factory the object is rebuilt by its class name and the hook receives 1.
from_json
Runs the hook with the object's "$version" key (beside its "$type") when it has one, and 1 otherwise.
Both formats run the same calls. JSON writes a record as an object of its properties; a record opened with a type name leads with "$type", then "$version" when it is above 1. Binary is little-endian.
Binary value
Bytes
record, opening
0x01, the type name (a uint32 length, then the bytes; empty for a record the archive opens itself), a uint32 version.
record, closing
Its property count, every property name, every property's byte size, the values, and 0x02.
property byte sizes
A reader jumps to a property by name and skips unknown ones unparsed.
Parse errors and every checker error and warning, in the checker's text, on each change.
Diagnostics, imported file
An error in an imported file is reported in that file.
Diagnostics, unparsed file
Reports its one parse error. The names it declares stay declared for its importers.
Definition, references
Functions, classes, fields, methods, constructors, globals, locals, parameters, enums and their values, namespace members, across imports and includes.
Definition on a path
On an import or include path, opens the file.
Hover
A variable's declared type, or the type inferred for auto; a function's signatures; a class with its fields and methods; an enum value with its number; a host function with its registered parameter types.
Completion
Names in scope, the enclosing class's members, the program's and the host's declarations, keywords.
Completion after .
The receiver's members (inherited and bound C++ ones included) or the builtin methods of an array, map or string.
Completion after ::
A namespace's members, a class's statics, an enum's values.
Signature help
Every overload inside a call's parentheses, the current parameter highlighted.
Rename
The declaration and every use across the program (rules).
Symbols
The document outline, and every declaration in the workspace's .jai files, fuzzy-matched.
Opt-in headers that nothing in the library includes: The HTTP transport.
Header
Provides
<jaiscript/net/http_socket_transport.hpp>
jai::net::http_socket_transport: HTTP/1.1 over Winsock or POSIX sockets, one connection per call. Every wait is bounded by the timeout, name resolution excepted.
http_socket_transport, https
An https URL needs a jai::net::tls_layer passed to the constructor. Without one it fails with HTTPS needs a transport with TLS: this one speaks plain HTTP only.
<jaiscript/net/openssl_tls.hpp>
jai::net::openssl_tls: that layer on OpenSSL 1.1.1 or 3.x (TLS 1.2 and up, SNI, the chain and host name verified). Include it only where OpenSSL is linked.
openssl_tls, trust setup
The constructor takes the trust setup, a function of the SSL_CTX*. Without one it uses OpenSSL's default certificate paths.
openssl_tls, platform stores
On Android and iOS the trust setup fills the store from the platform (SecTrust, /system/etc/security/cacerts); on Windows, from the ROOT store.
The port (default 8080; 0 picks a free one, named in the spinning line).
--host ADDR
The IPv4 address to listen on (default 127.0.0.1; 0.0.0.0 for every interface).
--root DIR
The static root (default: the app's public/ folder when it exists).
--max-body N
The largest request body, with K, M or G (default 2M).
--timeout S
Seconds a request may take to arrive and a response to leave (default 30).
--budget S
The execution budget per request (default 30; 0 is unlimited).
--backend vm|interp
The engine's backend.
--dev
Error pages show the error and its trace, absolute paths included. The library is imported from the source checkout's lib/, so edits to it reload too.
--allow-shutdown TOKEN
POST /__spider/shutdown from the same machine with the header X-Spider-Shutdown: TOKEN stops the server; without the token it is 403. A cross-site page cannot send the header without a preflight the server never grants.
A body with its Content-Type. Each takes an optional status; spider::html takes a web::html too.
spider::redirect(url)
303 See Other by default, the answer to a form post.
spider::file(path, content_type)
A file streamed from disk, relative to the app's folder, typed by its extension unless one is given.
spider::file, the header
The response names the file by a random token in an X-Sendfile header that the host consumes, so an X-Sendfile header from anywhere else serves nothing.
jaiscript, the reference embedding: the jaiscript_runner target (on by default) builds it into build/bin/. Full notes: tools/jaiscript/README.md. It 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 console-game surface (A Terminal Game in One File: read_key, key_down, term_cols, term_rows, env_var, 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
Invocation
What happens
jaiscript file.jai [-- args...]
Runs the file as a suspendable run (each sleep returns to the runner, which waits on the engine's clock and resumes it), then exits.
Exit code
The script's trailing int expression if any; 1 on a script error; 2 on a usage error (unknown flag, missing or malformed value).
Exit code outside 0 to 255
Its low byte, on every platform: 300 exits 44, -1 exits 255.
--
Everything after it is the global ARGS.
A .jai dropped onto the executable
Runs the same and pauses before closing.
jaiscript -e "source" [-e ...] [-- args...]
Runs the source text as a file run runs a file's text, then exits. Combines with the flags below.
Several -e
Run in order in one engine: declarations carry over, the first error stops the run.
-e exit code
The last source's int result; 1 on a script error.
-e output
Only what the source prints (jaiscript -e "print(1 + 2)"). The REPL is the mode that echoes values (echo 1 + 2 | jaiscript).
-e with include/import
Resolve from the working directory.
-e with a script file or --watch
A usage error.
jaiscript
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.jai
Re-runs the file on every save, in a fresh engine each time. To keep the world, reload with a guarded import: Reloading from a running program.
Flag
Meaning
--backend=vm|interp|vm-reference
Execution backend, default vm; the backends agree. vm-reference: the VM with every optimization off (engine::vm_reference_mode).
--warnings
Lists the checker's warnings on stderr before the run. Typed mistakes and undeclared names always fail the load.
--workers=N
Worker count of parallel_for, parallel_transform and parallel_reduce; default the engine's own.
--budget=N
Execution budget in seconds per execute. Default 0 = unlimited (the embedded default is 5.0 s).
--time
Prints wall time per run.
--debug / --debug-port=N
Starts the DAP debug listener on localhost (VS Code attaches) and names the bound port on stderr. Without either, nothing listens.
Debug port
--debug-port=N tries port N first. Default 52477, probing upward in steps of 10.
--pause / --no-pause
Force or suppress the "press Enter to close" hold after a file run.
--version / --help
--benchmark-main=N
Runs the file once as a warm-up, then calls its main() N times.
--no-param-alias / --no-local-alias
No read-only copy elision for by-value parameters / locals: every one copies. Results never change.
--no-proofs
No type proofs, typed ops or cell blocks: the general form, same results.
--miss-every=N
The VM misses at every Nth plain cell instruction, exercising the flush at every position (a build configured with JAISCRIPT_MISS_INJECTION).
--disasm=FILE / --manifest=FILE
Every chunk the VM compiles, written to FILE: readable listings, or exact semantic manifests.
--coverage=FILE
The VM's execution-coverage census at exit (a build configured with JAISCRIPT_VM_COVERAGE).
--selftest
Runs the runner's built-in smoke tests and exits.
Ctrl+C. Ctrl+C or Ctrl+Break on Windows, and SIGINT, SIGTERM or SIGHUP on Linux and macOS, end the runner at once, in every mode the REPL included.
The script stops where it is, without running its destructors. Exit code 130; on Linux and macOS the signal itself ends the process, so the shell reports 130, 143 or 129.
A signal the runner was started with ignored (nohup, a background job) stays ignored.
Before it exits, and when a script ends or fails, the runner gives a terminal back: colours reset, cursor shown, alternate screen left, each only where the script changed it. The input mode, and on Windows the console mode and code pages, come back too. A pipe or a file receives none of it.
A script that must stop itself has the execution budget (--budget=N).
Registered on every engine the runner builds; a host embedding JaiScript registers its own. Signatures: API reference, under the standalone runner.
Calls that name a file (the file functions, Download.start, load_sound, play_sound) need the engine's filesystem privilege, else Insufficient privilege: filesystem.
Download.start needs the network privilege too.
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: Modules.
Files and folders
Behavior
a relative path
Resolves from the working directory (import and include: from the script's folder). A path built from root_script_dir() names the same file from any working directory.
Writes the bytes exactly, creating missing folders; false when it cannot.
file_exists(path), dir_exists(path)-> bool
Whether the path names something, or a folder.
delete_file(path) -> bool
false when nothing was removed.
move_file(from, to) -> bool
Replaces to; false when from is not a file. One volume: a single operation, so a reader sees the old file or the new one. Across volumes: copies, then removes the source.
Safe save
Write save.tmp, then move it over save.txt.
make_dirs(path) -> bool
Creates the folder and its parents; true when the folder exists afterwards.
list_dir(path) -> array<string>
Entry names, sorted; empty for a folder that cannot be read.
writable_dir(path) -> bool
Creates the folder if missing, then writes and removes a probe file: whether it can take a save.
A zip archive's entry names; one entry written to dest, CRC-32 verified.
Download()
Asynchronous download, polled from the script's loop. A failed transfer leaves no partial file.
start(url, path)
Begins it (http or https); false with error() set when it cannot begin.
pump()
true once it is done.
success(), error()
The outcome.
received(), total(), fraction()
Progress; total() is -1 until known.
cancel()
Stops it.
Console games
Behavior
read_key() -> string
The 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) -> bool
Whether the key is held now: a letter, a digit, "space", an arrow, "enter", "esc", "tab", and on Windows "shift", "ctrl" and "alt".
key_down, POSIX terminal
No key releases arrive: a key reads as held for half a second after its last press or auto-repeat; modifiers never read as held.
term_cols(), term_rows()-> int
Console size in characters; 80 by 25 when it cannot be read.
env_var(name) -> string
An environment variable, "" when unset. env_var("COLORTERM") is "truecolor" or "24bit" in a terminal that takes 24-bit colour.
Sound
Behavior
load_sound(path) -> Sound
Opens a WAV, MIDI or MP3 file and returns at once; it preloads in the background. null for a missing file or another format.
Format
Named by the first bytes (RIFF/WAVE, MThd, an ID3 tag or an MPEG frame); an unrecognized header falls back to the extension (.wav, .mid, .midi, .mp3).
s.play(), s.play(true)-> bool
Starts from the beginning, looping forever with true; returns at once. A playing Sound restarts. false once the file proved unplayable (its data did not decode).
s.stop(), s.playing() -> bool
playing() is true from play() until stop(), stop_sounds() or the end of a sound played once.
s.volume(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)-> Sound
load_sound then play. Plays to its end (or forever, looped) even when the result is dropped; the result stops it. null as load_sound.
stop_sounds()
Stops every Sound.
Voices
Every Sound is its own voice; several play at once, mixed.
Lifetime
A Sound closes when the last variable holding it lets go; a copy of a Sound is the same sound.
Cost
Loading costs more than playing: load once, play again. No call waits on the audio device (one audio thread drives the devices).
Platforms
Windows, and the site's Play and Sandbox pages. Elsewhere load_sound and play_sound return null: a script that checks for null 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
Networking
Behavior
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.
on_connect(), on_packet(bytes) and on_disconnect(reason)
NetChannel.connect is asynchronous
false only when the attempt fails at once (the host name does not resolve, the socket cannot be made), reason in status(). Otherwise true while the connection is still being made.
Connect outcome
Arrives through pump(): on_connect() when the peer accepts, on_disconnect("connect refused") when not. Packets sent before then wait in the queue.
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, never reused by one server.
NetServer signals
on_client_connect(id), on_client_packet(id, bytes) and on_client_disconnect(id, reason)
Packet
One send's bytes, delivered whole and in order. On the wire: a 4-byte big-endian length, then the bytes.
Threads
None. The sockets do not block; signals fire only inside pump(), on the script's own call stack. A game calls pump() once a frame.
Disconnect reason
"closed by peer", "recv error", "send error", "oversized frame" (a length over 64 MiB) or, for a server's client, "kicked".
This site is the specification of the language and its embedding API: a rule stated here is the rule, and a disagreement with the code is a bug worth reporting. The repository's other documents open on GitHub.