Language JSON
A language file is one JSON object. ReScripted reads it and then knows how to parse your scripts.
Every language uses the same keys. You change the values.
"declaration_keywords": ["let"] → let x = 1;
"declaration_keywords": ["var"] → var x = 1;
Do not add new top-level keys. If you need a word the template does not have (command, quest), put it under custom (below).
Start from a complete file
Copy examples/mini/mini.json and change the words. Or copy ../agents/language_template.json and fill it.
The short JSON in Getting started is only for the hello-world program. A real language keeps the full file, including keys you are not using (leave them disabled).
If a coding agent is filling the file, give it ../agents/AGENTS.md.
Keys you will actually edit
Each block below is: the JSON, then a script it allows.
Name and file suffix
"name": "Mini",
"extensions": [".mini"]
name is what C++ passes to eval and load. extensions is how load_file("ai.mini") picks the language. Use a real name, not the template placeholder.
Comments
"comments": {
"line": { "enabled": true, "markers": ["//", "#"] },
"block": { "enabled": true, "pairs": [["/*", "*/"]] }
}
// a line comment
print(1); # also a line comment
If # starts a comment, do not also use # as an import keyword or decorator prefix.
Variables
"variables": {
"enabled": true,
"declaration_keywords": ["let"],
"assignment_operators": ["="]
}
let hp = 10;
hp = 9;
You can list += -= *= /= in assignment_operators (and in operators.assignment) to allow hp += 1.
Assignment to a field or a list slot is not implemented: hero.hp = 9 and items[0] = 1 will not work. Reading hero.hp and items[0] does work.
Functions
"functions": {
"enabled": true,
"declaration_keywords": ["fn"],
"return_keywords": ["return"]
}
fn add(a, b) {
return a + b;
}
After load, C++ can engine.call("add", {Value(2.0), Value(3.0)}).
Type-first headers like int add(int a, int b) work when you set functions.header_style to "c_type_first".
Blocks and semicolons
"tokens": { "statement_terminators": [";"] },
"blocks": { "delimiter": { "enabled": true, "open": "{", "close": "}" } }
if hp > 0 {
print("alive");
}
Braces around a single statement are optional. Semicolons are optional unless you set statement_terminator_required.
Control flow
Use the keyword lists under keywords / control_flow for if, else, while, for, break, continue.
if hp <= 0 {
return;
}
while hp > 0 {
hp -= 1;
}
for i 5 { // i is 0, 1, 2, 3, 4
print(i);
}
for item [1, 2, 3] {
print(item);
}
There is no for (i = 0; i < n; ++i).
Import
"imports": {
"enabled": true,
"import_keywords": ["import"],
"module_separator": ".",
"bind_as": "namespace"
}
import math
print(math.twice(3));
import helpers with no C++ registration loads helpers next to the script (same language, same folder). A registered library needs add_library in C++. See Libraries.
Run the top of the file
"entry_point": { "top_level_execution": true }
With this, engine.run(id) executes the lets and bare calls at the top of the script. Functions are registered on load even without it.
Extra words: command, entity, @export
A game language often wants more than fn and let. Add them under custom. ReScripted will parse them. Your C++ decides what they do.
"custom": {
"function_kinds": [
{
"name": "command",
"declaration_keywords": ["command"]
}
],
"types": [
{
"name": "entity",
"declaration_keywords": ["entity"]
}
],
"decorators": [
{
"name": "export",
"prefix": "@"
}
],
"host_functions": ["print", "spawn"]
}
@export
command ping() {
return "pong";
}
entity Hero {
hp = 10;
}
| JSON | In a script | After you load / run |
|---|---|---|
function_kinds |
command ping() { ... } |
engine.call("ping", {}) works after load (same as fn) |
types |
entity Hero { hp = 10; } |
a map named Hero exists after run |
decorators |
@export above a function |
your bind_decorator("export", ...) runs at load |
host_functions |
spawn("goblin") |
you still must bind("spawn", ...) |
List host function names in custom.host_functions and builtins.functions so the file documents the C++ contract.
Classes
When classes.enabled is true, scripts can declare class or actor (whatever keywords you set), optional : Base, fields, and methods. engine.call("OnSpawn") calls that method on the instance created for the declaration.
What the engine ignores (for now)
The JSON has fields for async, generics, macros, Python-style indentation, and more. Leave them in the file. They do not run. Do not promise them to script authors.
You also cannot assign through . or []. Closures do not capture inner lets.