Stability​

JaiScript 0.5 is a beta: the language and its contract are complete, and the version stays 0.x until 1.0. This page sorts the language and the embedding API into what is settled and what a release may still change.

  • A settled rule is the contract. Where the implementation departs from one, the implementation is the bug and changes to match.
  • Everything under May change can move in a minor release, and so can anything this page does not mention.

Versions​

A version is 0.minor.patch (0.5.2). On this page a release that may change something is a minor release: 0.5 to 0.6.

  • Within one minor version (0.5.2 to 0.5.3) the CMake package declares the releases compatible: find_package(JaiScript 0.5) accepts any 0.5.z and refuses 0.6.
  • Each release is a tag in the repository (v0.5.2). The repository keeps no changelog file; the history between two tags is the record of what changed.
  • Precompiled .jaibite files and saved archives are tied to the build that wrote them, at any version step (below).

Settled: the language​

AreaThe rule
Value semanticsAssignment, by-value parameters and returns copy. Copying an instance, array or map copies its values and shares its handles. Aliasing exists only where a declaration asks: T& or shared_ptr<T>. A non-const reference binds storage of its own type; a const T& also binds a temporary, which lives as long as the reference; a store through a reference follows the target's declared type.
The type ladderA typed slot is enforced, auto is inferred then enforced, var stays dynamic. Declarations, assignments, by-value parameters, typed returns and field stores convert through one table, with at most one converting constructor.
Strict checkingThe checker always runs before anything executes. A typed-to-typed mistake it can see fails the load; a value that came through var fails at run time, where catch sees it. [[warn_mode]] and [[strict_mode]] apply to a region, extern declares a host name, cast<T>, cast<T&> and cast<shared_ptr<T>> are the explicit boundary. A program that runs clean is never rejected.
Typed containersarray<T> and map<K, V> hold what they declare: a store converts by the element rules or raises. array<var> is the dynamic form. A copy into a var or a slice keeps the element type, map keys never coerce, array<Base> holds subclasses, handle-typed elements take null. Maps iterate and serialize in key order.
Integers and charsint is 64-bit. Overflow raises a catchable error (or wraps in a build that defines JAISCRIPT_WRAP_ON_OVERFLOW) and is never undefined. Shifts operate on the 64-bit pattern. char is a byte that enters arithmetic as 0..255 and converts only through cast.
Evaluation orderA call evaluates its callee, then its arguments left to right; a by-value argument keeps the value it had when evaluated. A binary operator evaluates its left operand first. Errors surface in that order.
CapturesA lambda captures locals and parameters when it is created: [] nothing, [=]/[x] a copy, [&]/[&x] the variable itself (it outlives its frame), [this] the instance. Named lists are exhaustive. An anonymous function(...) captures as [&]. A global is never captured: the lambda reads it when it runs. A named function declared inside a function body captures nothing.
Names and scopesBlocks scope as in C++. In a method a bare name resolves to a local, then a member, then a global. A namespace is a scope, and a catch variable belongs to its block.
Access controlTwo levels: public: reaches everyone, private: the declaring class and its derived classes; protected: is a synonym of private:.
OperatorsA class overloads binary + - * / % & | ^ << >> < > <= >= == != <=>, unary - + ~, and =, [], (). a op= b is a = a op b. Truthiness is bool to_bool(), and !v is !to_bool(v). == compares arrays, maps and instances member by member (a user operator== first); same_object asks identity. obj.method without a call is an error; reflect::bind makes the callable.
Statementsswitch breaks by default and fallthrough; continues. ++ and -- work on every lvalue. An assignment expression's value is the value the destination stored. A declaration without an initializer takes its type's default.
StringsA string is bytes; utf8_string holds the same bytes and counts characters.
Modulesinclude is textual and runs each time it is reached. import runs a module once per engine, may appear wherever a statement may, and re-runs a module whose file changed when reached again.
Coroutinescoroutine prefixes any function or method; it runs to each yield and is driven by a range-for or next()/next(n).
Hot reloadRedefining a class migrates its live instances in place, and migration never fails. A source that does not parse or check does not load; the previous definition keeps running.
Edge rulescontains, index_of, has and switch cases compare with == (switch (1) matches case 1.0). to_string(x) uses a class's own to_string(). 0.0 and -0.0 are one map key. Reading a missing map key raises, as does a compound assignment on one; m[k] = v inserts. A JSON round trip keeps chars and non-string map keys, and from_json propagates an error from a class's constructor. A bare throw; with no active exception raises the catchable Bare throw with no active exception thrown. A compound assignment with an operand of the wrong type raises one text at every destination. A derived value in a base-typed slot keeps its class.
Two backends, one behaviorThe interpreter and the VM produce the same values, output and error text for every program. The one exception is recursion depth: where non-tail recursion meets the recursion error, and the VM reusing a self tail call's frame.

Settled: embedding​

  • An engine is an object. jai::engine::make() returns one. There is no global state: engines share nothing and run on separate threads without locks. Quickstart
  • Registration is the allowlist. Beyond the core builtins, a script reaches only what its engine registered. Three doors
  • The backend is chosen before an engine's first execute. Backends
  • A script cannot outlast its limits. Exceeding the execution budget is terminal: no catch sees it. The memory cap raises once per execute as an ordinary catchable error, so a script can free memory and continue; a second raise in the same execute is terminal. Safety Rails
  • Each way of calling script fails one way. execute and execute_file throw on every failure (jai::parse_error, jai::static_check_error, or a jai::runtime_error) and never report one through their return value. A call that returns a checked_result (as_function(), a coroutine handle's next) reports every failure in it. A std::function from convert_script_function throws jai::runtime_error, and a signal receiver's error throws out of the emit. Error surfaces
  • Precompiled equals source. Running a .jaibite behaves exactly like executing the source it was parsed from. jaibite

May change​

WhatHow it may changeWhat holds
The C++ API's shapesignatures, header paths and helper type namesthe model above, and the meaning of every call the embedding pages and the reference describe. Anything in a detail namespace (jai::detail::engine_access included) is the library's own and can change or go.
Performancewhat each construct costs, which shapes the VM compiles to its fastest forms, every published figurea performance change never changes what a program computes
The .jaibite format versionit moves whenever the parser's output changes, and a bite loads only in a build with the same versionship bites built by the release that loads them; the automatic disk cache re-parses a stale file
What the checker reports at loada mistake that raises at run time can become a load errora program that runs clean keeps loading clean
Error text wordingthe catalogued texts can be rewordedboth backends raise the same text
Serialized datathe layout to_json, to_binary and the C++ archives writenothing across releases: data saved by one is not promised to load in the next
Parallel admissionwhich parallel_for, parallel_transform and parallel_reduce bodies are admitteda body not admitted is refused with a catchable error, never run serially; results are deterministic at any worker count
String mutatorswhether to_upper, trim and the other mutating string methods change their receiver in place
Script-extensible objectsextend(obj, Class) and its host side
Coroutine lambdasabsent; they may be added