Hello & Basics
JaiScript is an embedded scripting language that reads like C++: typed or dynamic variables, value semantics, classes, lambdas, and a static checker that reads the whole program before its first statement runs. A host program embeds it as a library and can reload scripts while it runs.
One engine runs a script on one of two backends: a bytecode VM (the default) and a tree-walking interpreter held to the same behavior. Every standalone sample on this site was run on both, and the output shown is what both printed. The one documented difference is recursion depth: a deep non-tail recursion that returns on the VM can stop on the interpreter with Native stack exhausted (Recursion).
Running a script from C++
#include <jaiscript/core/engine.hpp>
#include <jaiscript/stdlib/stdlib.hpp>
auto eng = jai::engine::make(); // shared_ptr<engine>, runs the VM
jai::stdlib::register_all(eng); // formatted print, format, to_string, type_of, to_json, ...
int v = eng->execute<int>("2 + 2"); // 4- The value of the last expression is the result of
execute(); top level needs noreturn.execute<T>()hands it back as aTand throws when it is not one. - The checker runs first and is strict: a type mistake it can see, or an undeclared name, throws out of
execute()before any statement runs. Chapter 13 has the whole story, including the[[warn_mode]]attribute that turns a region's errors into warnings. eng->set_backend(jai::backend_type::interpreter)selects the interpreter. Call it before the firstexecute()(later is an error) and linkjaiscript_interpreter.
Try it from a terminal
The guide needs no host program. The build produces a standalone runner, jaiscript; the installer in Get JaiScript puts it on your PATH, and in a clone it is one CMake target (named jaiscript_runner, because the library owns the name jaiscript):
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build --config Release --target jaiscript_runner # build/bin/jaiscript
jaiscript hello.jai # run a file, then exit
jaiscript # interactive REPL (:help inside)
jaiscript --watch hello.jai # re-run the file every time you save it| Flag | What it does |
|---|---|
--backend=vm|interp | Which backend runs the script. The default is vm. |
--warnings | Lists the checker's warnings before the script runs. Errors always fail the load and print. |
--budget=N | Seconds of wall clock per execute; an overrun is a terminal error no catch sees (Control Flow). Default 0: unlimited. |
The runner's defaults are not the engine's. An embedded jai::engine enforces a 5.0 second execution budget per execute(); the runner has none. Both start on the VM, and checking is strict in both.
The REPL runs each entry as one execute() on a persistent engine. An entry's last statement needs no ; (int hp = 100, hp += 1, import "rules.jai"); an expression prints its value, and a declaration prints nothing. After a file run the runner prints no result (wrap the last expression in print(...) to see it), and an int result becomes the exit code.
Output blocks on these pages follow three conventions:
== result: …is the value of the script's last expression, whatexecute()hands a host. The lines above it areprintoutput.== error: …is the message of an uncaught error. The runner prints it to stderr and exits 1.- A block that starts
jaiscript: static check failed:orjaiscript: parse failed:is the runner's listing when a load stops before anything runs.
Values and literals
// a line comment
/* a block
comment */
auto i = 42; // int: 64-bit
auto f = 3.14; // float: a double
auto s = "hello"; // string
auto c = 'x'; // char: one byte
auto b = true; // bool
auto n = null; // null
print(type_of(i), " ", type_of(f), " ", type_of(s), " ", type_of(c), " ", type_of(b), " ", type_of(n));
print("hex: {}, binary: {}, exponent: {}", 0xFF, 0b1010, 2.5e3);
print(R"(C:\games\save.dat)", " ", R"js(say("hi"))js");
i + f;int float string char bool null
hex: 255, binary: 10, exponent: 2500.0
C:\games\save.dat say("hi")
== result: 45.14- Numbers. Integer literals are decimal, hex (
0xFF) or binary (0b1010). Floats take C++'s spellings: an exponent (2.5e3), or digits on one side of the point alone (1.,.5,1.e5). - Strings take the escapes
\n,\r,\t,\",\',\\and\xNN, a byte as exactly two hex digits (Bytes and chars). - Raw strings, C++'s
R"(...)", take their text as written, backslashes and quotes included, and across lines (each line ending is one\n). A delimiter of up to 16 characters between the quote and the parenthesis lets the text hold)". autoinfers the variable's type once and keeps it. The next chapter coversauto, typed declarations and the dynamicvar.
print and format
jai::stdlib::register_all registers the formatted print, plus format and vformat. (A bare engine's print only concatenates its arguments.)
print("several", " ", "args", " ", "concatenate");
print("sequential: {} + {} = {}", 2, 3, 2 + 3);
print("positional: {1} before {0}", "cart", "horse");
print("{:.2f} and {{braces}}", 3.14159);
string typed = "a user typed {}";
print(typed, " and it stays as typed");
string pattern = "{} of {}";
print(vformat(pattern, 1, 2), " / ", format("{} hp", 99));
print("no newline ", skip_newline);
print("continues the line");several args concatenate
sequential: 2 + 3 = 5
positional: horse before cart
3.14 and {braces}
a user typed {} and it stays as typed
1 of 2 / 99 hp
no newline continues the line| Call | Result |
|---|---|
print("fmt {} {0}", a, b) | A string literal written first that holds placeholders is a format string: {} takes the next argument, {n} argument n (from 0), {{ and }} are braces, and {:spec} formats (format specs). |
print(a, b, c) | Any other first argument (a variable, an element, a concatenation, text a user typed) is text: the arguments concatenate, and braces print as they are. print("{{x}}"), one argument, prints {{x}}. |
format("fmt", args...) | The same rules, returning the string. |
vformat(fmt, args...) | Formats a text known only at run time, as C++'s std::vformat does. |
trailing skip_newline / skip_flush | No newline / no newline and no flush. |
- Placeholders and arguments must pair up. For a literal the count is checked at load:
the format text of 'print' has 2 placeholder(s) for 1 argument(s), and a literal alone with a placeholder (print("{}")) has none to take.vformatchecks when it runs:Format text has 2 placeholder(s) for 1 argument(s). formatwith a first argument the checker knows is a string but not a literal fails the load:the format text of 'format' must be a string literal: vformat(fmt, ...) formats a run-time text, and + or a template string joins text.- Only a call that names
printorformatformats: a function value holdingprinttakes every argument as text. - Every argument prints its display text, the text
to_string(x)gives (Printing values). A container or an object is never read as a format string.
to_string, to_json and type_of
print(to_string(2.0), " ", to_string(0.1 + 0.2), " ", "3.5".to_float() + 1);
print([1, "two"], " ", {"a": 1}); // display text: strings inside a container are quoted
print(to_json([1, "two"]), " ", to_json({"a": 1}));
class Foo { }
int x = 1;
auto& r = x;
print(type_of(Foo()), " ", type_of(r), " ", type_of([1]));2.0 0.30000000000000004 4.5
[1, "two"] {"a": 1}
[1,"two"] {"a":1}
Foo int arrayto_string(x)gives any value's display text. A float prints as the shortest text that reads back to the same value, with.0kept on a whole number, soto_string(x).to_float() == x. Infinities printinfand-inf, and every NaN printsnan.to_json(x)writes the JSON text thatfrom_jsonreads back (JSON).type_of(x)answersnull,bool,int,float,char,string,utf8_string,array,map,function,weak_ptrorcoroutine_handle; later chapters introduce the kinds not met yet. A class instance, or a shared handle to one, answers its class's name (Foo,math::vec2). A reference answers the type it refers to, and a typed array is stillarray.