On this page
The Atlas scripting language
The grammar and value model of the Atlas scripting language: source text, literals, declarations, every data type, arrays, enums, structs, expressions, control flow, scope, defaults, and safety limits.
Source files and program shape
Atlas scripts are plain-text files conventionally saved with the .atlas
extension. A file is a module containing declarations, functions, and event handlers.
Executable statements belong inside a function or event; a bare statement at the top
level is an error.
// Global declarations are initialized when the script is loaded.
const MAX_SAMPLES = 8;
var framesSeen = 0;
function recordFrame() {
framesSeen++;
}
event on_frame() {
recordFrame();
}
Declare every function with function, as recordFrame does. Atlas
Script functions have no return type; see
Function declaration and call syntax.
return,
break, and continue end with ;. Function and event
bodies do not take a trailing semicolon. Atlas accepts an optional semicolon after an
enum or struct declaration; using one consistently is recommended.
Whitespace and comments
Spaces, tabs, carriage returns, and line breaks separate tokens but otherwise have no
meaning. Use // for a comment through the end of a line or
/* ... */ for a block comment. Block comments do not nest.
// A single-line comment.
var sampleCount = 4;
/* A block comment may
span several lines. */
var threshold = 0.65;
Always close a block comment. An unfinished block comment consumes the rest of the file
as comment text and reports atlas.unclosed_block_comment.
Identifiers and reserved words
A name begins with a letter or underscore and may then contain letters, digits, or
underscores. Names are case-sensitive: sampleCount and
SampleCount are different variables. A name cannot start with a digit.
Valid names
count, sample_count, point2, _cached
Invalid names
2d, 3samples, every reserved word, and every reserved API name.
Reserved words
The following spellings are recognized as language keywords and cannot be reused as variable, field, enum-member, struct, function, or event names.
| Group | Reserved words |
|---|---|
| Declarations | const, var, let, auto, define, enum, struct, function, event |
| Control flow | if, else, switch, case, default, while, do, for, break, continue, return |
| Core types | bool, string, char, font, model, target, type |
| Vision types | color, contour, motion, edges, circle, line, template, feature, match, qr, barcode, ocr, track, face, person |
| Literals | true, false, null |
| Removed type spellings | int, float, double are reserved so Atlas can report that var should be used instead. |
Reserved names
The names of the API are reserved as well, so output always means the output
API. A variable, constant, define, parameter, for variable,
function, struct, or enum cannot use one of them: Atlas reports
atlas.reserved_api_name, such as 'output' is an Atlas name; choose
another name. The editor's quick fix renames the declaration and every use of it,
for example output to outputValue.
accel, controller, cronuszen, ctx, font, frame, gyro, host, input, lightbar, math, models, network, output, packet, ps4, ps5, rgb, rumble, string, system, time_ms, type, ui, video, vision, xb
A struct field and an enum member may use one of these names, as in cfg.output
or Mode::input, and so may the parameters of
event on_input(input, output), which name the API objects themselves.
font, string, and type are keywords too, so a
declaration with one of them is reported as atlas.reserved_identifier.
Assigning to a name nothing declares, as in output = 5;, declares it as well,
so that is reported the same way. Assigning to an event's own object, as in
output = 5; inside event on_input(input, output), is
atlas.assign_to_event_object: use its members instead.
A script written before this rule that declares one of these names no longer compiles until the name is changed. Open it in the Atlas editor and apply the quick fix on the error: it renames the declaration and every use of it, and leaves calls to the API alone.
Literals
Numbers
Atlas has one numeric value category. Whole values and decimal values share the same VM number representation. A leading minus is a unary operator, not part of the literal.
var whole = 42;
var decimal = 42.75;
var leadingDot = .5;
var negative = -12.25;
var sameValue = 42;
- A decimal may contain only one decimal point.
- Write a trailing decimal digit: use
1.0, not1.. - Scientific notation such as
1e3is not supported. - Hexadecimal literals begin with
0xor0X.
Strings
Single and double quotes both create a string. char is a synonym for
string; it is not a one-character numeric type.
string title = "Atlas";
string shortLabel = 'A';
string twoLines = "first\nsecond";
string tabbed = "left\tright";
string quoted = "Say \"ready\"";
string path = "assets\\templates\\marker.png";
Recognized escapes are \n, \t, \r,
\\, \", and \'. A string cannot continue through
an unescaped physical line break.
Booleans and null
bool enabled = true;
bool paused = false;
target selected = null;
null means that no value is present. It is commonly returned when a target
or optional result is unavailable. A numeric var cannot be initialized with
null. A string initialized with null becomes an empty string.
Array literals and aggregates
Square-bracket lists are array values. Brace aggregates are accepted in a declaration or plain assignment where Atlas expects an array, font, or model initializer.
const region[] = [0.1, 0.1, 0.8, 0.8];
var samples[4] = { 4, 8, 15, 16 };
var grid[2][2] = {
{ 1, 2 },
{ 3, 4 }
};
Values, data types, and defaults
Declarations communicate the value a name is intended to hold and enable useful validation. Atlas is not a fully static type system, so keep later assignments consistent with the declared type rather than relying on every mismatch to be rejected.
| Declaration or value | Purpose | Default when omitted | type(value) |
|---|---|---|---|
var, let, auto | General numeric values. The three spellings are synonyms. | 0 | "int32" when whole; "float" when fractional |
bool | Logical state: true or false. | Currently null, which is falsey. Initialize bools explicitly. | "bool" after a bool value is assigned |
string, char | Unicode text. Both declaration spellings mean string. | "" | "string" |
| Array | Ordered, zero-based values. Arrays may contain mixed value kinds. | Each fixed element receives its declared element default. | "array" |
| Enum member | A readonly named integer constant. | First member starts at 0. | "int32" |
| Struct type | A user-defined group of named fields. | A fresh recursive copy of all field defaults. | The struct's declared name |
font | Overlay text style created with font.create or font.default. | null | "font" |
model | A model resource/value used by model and vision APIs. | null | "model" for a model resource |
target | A detected target with position, score, label, and box fields. | null | "target" |
| Vision tool types | Live detector configuration and results for one tool family. | null | The specific tool name, such as "color" |
| Handle | A runtime-owned UI/resource handle returned by selected APIs. | Not directly declarable as handle. | "handle" |
null | No current value. | — | "null" |
| Reference alias | An internal writable binding made with var alias = ⌖. | Requires a target. | The referenced value's type because aliases auto-dereference |
Atlas reports a struct, a vision tool, a target, or a model stored where a
var, string, or bool is declared. This is a report on the values
Atlas can name a type for, not a rule the VM enforces: a value whose type Atlas cannot work out still
reaches the name at run time (see What is not reported below). Storing one is
atlas.type_mismatch, or atlas.argument_type for an argument, in a declaration
or an assignment such as var best = saved;, or var best = makeProfile(); when
the function returns a struct; in a struct field; and as the argument of a call. Declare the name with
the value's own type instead: Profile best = makeProfile();.
The same check runs the other way round for a field or a parameter declared as a
target, a model, a vision tool, or a font: a value of a
different kind stored there is reported too, as in hit.t = makeProfile();
(Struct field 't' expects a target, but this is a struct.) or
show(makeProfile()) for function show(target t)
(Parameter 't' expects a target.). Two values of the same kind are accepted, so a
color field takes any vision tool.
What is not reported. A call is only reported when every one of its
return paths names that same type, so these stay accepted: a call whose result type is
unknown (var kept = pass(saved); for
function pass(value) { return value; }, where type(kept) logs
Profile); a call with even one path Atlas cannot type, such as
return pass(1); or return ctx.math.abs(1); beside a
return saved;; a call that can return more than one kind, such as a find-or-null helper
(var found = find(0); where find returns a Profile or
null) or one that returns a number on another path. Nor are these, which are not calls:
an array element
(var first = list[0];); a store into an element of a var array
(slots[0] = saved;); and a compound assignment (total += saved;), which
computes a number instead.
A script written before this rule that keeps a struct or a vision value in a var,
string, or bool no longer compiles until the declaration is changed.
There is no quick fix for this one — unlike a reserved name, the editor cannot
rename anything to repair it. Change the declaration to the value's own type
(Profile best = makeProfile();), or keep the value in a separate name of that type and
leave the var holding a number.
Typed vision values
The detector type keywords are color, contour,
motion, edges, circle, line,
template, feature, match, qr,
barcode, ocr, track, face, and
person. A value must be initialized from the matching singular
vision.* method.
color marker = vision.color([0, 90, 80], [15, 255, 255]);
motion movement = vision.motion();
template badge = vision.template("assets/templates/badge.png");
ocr reader = vision.ocr();
target best = null;
Vision values are live handles. Copies refer to the same running tool configuration;
changing a writable configuration member updates that tool in the pipeline. Result
members such as results, count, and target are readonly.
Runtime type inspection
type(value) returns a string. Compare it to constants such as
type.int32, type.float, type.string,
type.bool, type.null, type.array,
type.font, type.model, type.target, or
type.handle.
var sample = 12.5;
event on_frame() {
if (type(sample) == type.float) {
system.log("sample is fractional");
}
}
Variables, constants, and defines
Mutable declarations
Declare a name before reading or assigning it. Several declarators of one type may share a statement, although one declaration per line is often clearer in documentation.
var count = 0;
var x = 10;
var y = 20;
let opacity = .75; // synonym for var
auto delay = 12.5; // synonym for var
bool active = false;
string label = "waiting";
Constants
A const requires a value and cannot be assigned later. Const arrays also reject element writes. Keep constant expressions literal and deterministic; calls are rejected.
const MAX_ATTEMPTS = 5;
const string READY_TEXT = "Ready";
const WEIGHTS[] = { 0.25, 0.5, 0.25 };
const GRID[2][2] = { { 1, 2 }, { 3, 4 } };
An empty dimension may be inferred only for a const array with an initializer, as in
const WEIGHTS[] = { ... };. Mutable arrays need explicit dimensions.
Defines
define declares one readonly named value. It is not a textual macro and does
not accept parameters.
define DEFAULT_THRESHOLD = 0.65;
define RETRY_LIMIT = 3;
var threshold = DEFAULT_THRESHOLD;
Arrays
Arrays are ordered, zero-based containers. Declared mutable arrays have fixed dimensions;
read or write an element with [index] and inspect a dimension's current length
with .count().
Declaration and initialization
var history[4];
var scores[4] = { 10, 20, 30, 40 };
string labels[3] = { "left", "center", "right" };
const OFFSETS[] = { -1.0, 0.0, 1.0 };
const REGION[] = [0.1, 0.2, 0.8, 0.7];
- Every explicit size must be a positive whole-number literal without a leading zero.
- A fixed initializer must supply exactly the declared number of elements.
- An empty initializer for a declared array is rejected.
- Arrays may contain mixed value kinds, although uniform arrays are easier to validate and maintain.
- One declaration may allocate at most 1,000,000 total elements.
Reading, writing, and counting
var samples[4] = { 2, 4, 6, 8 };
event on_frame() {
samples[0] = samples[0] + 1;
for (var index = 0; index < samples.count(); index++) {
system.log(samples[index]);
}
}
Constant indices are checked during analysis when Atlas knows the array length. A dynamic out-of-range read yields null; a dynamic out-of-range write has no effect. Use whole-number indices. A literal fractional index is rejected, while the runtime currently rounds a fractional value stored in a variable.
Multidimensional arrays
var matrix[2][3] = {
{ 1, 2, 3 },
{ 4, 5, 6 }
};
event on_frame() {
var value = matrix[1][2]; // 6
}
Each subscript removes one dimension. A scalar context requires all dimensions to be
subscripted: matrix[1] is still an array, while
matrix[1][2] is one value.
Enums
An enum groups readonly named integer constants. The first implicit member is 0; each following implicit member is the previous value plus 1. An explicit value changes where that sequence continues.
enum RunState {
idle, // 0
scanning, // 1
paused = 10,
stopped // 11
};
var state = RunState::idle;
event on_frame() {
if (state == RunState::scanning) {
system.log("Scanning");
}
}
- Access a member with
EnumName::member, not dot notation. - A bare enum name is a type-like namespace, not a value.
- Member names must be unique and cannot use a keyword.
- A trailing comma is rejected.
- Initializers may use whole-number literals, earlier members, qualified enum members, unary
+/-, and+ - * / %. - Use whole-number initializer expressions. The runtime stores enum members as numeric values.
Structs
A struct defines a named collection of public fields. Declare struct types at the top level, then declare instances by writing the struct name as the variable type.
enum TrackingMode {
nearest,
highestScore
};
struct Point {
var x = 0.0;
var y = 0.0;
};
struct TrackingSettings {
string label = "target";
bool enabled = true;
var threshold = 0.65;
var smoothing[3] = { 0.2, 0.3, 0.5 };
var mode = TrackingMode::nearest;
Point offset;
};
TrackingSettings settings;
Field rules and defaults
- Every field has a type and name and ends with
;. - Fields of one type may be comma-separated:
var x, y;. - Array fields require positive fixed dimensions.
- Field names must be unique within the struct.
- Nested struct fields receive their nested type's defaults and cannot have an explicit initializer.
- Direct or indirect recursive layouts are rejected, including recursion through fields.
A field default must be constructible without runtime state. Supported forms include
number, bool, string, and null literals; enum members; unary +/-; arithmetic;
string concatenation; and nested array aggregates. Calls and variable reads are rejected.
Instances, members, and copies
struct Point {
var x = 0.0;
var y = 0.0;
};
struct TrackingSettings {
var threshold = 0.65;
var smoothing[3] = { 0.2, 0.3, 0.5 };
Point offset;
};
TrackingSettings primary;
TrackingSettings backup = primary; // deep copy
event on_frame() {
primary.threshold = 0.8;
primary.offset.x = .05;
primary.smoothing[1] = 0.4;
// backup retains the values it had when copied.
system.log(backup.threshold);
}
A named struct declaration initialized from another struct and a plain assignment between named struct variables recursively copy the struct and its nested arrays. Function parameter behavior is covered in Functions & References.
Expressions and operator precedence
Operators higher in this table bind before operators lower in the source expression. Parentheses override precedence. Conditional expressions and assignment are right-associative; ordinary binary operators group left-to-right.
| Precedence | Operators and forms | Meaning |
|---|---|---|
| Highest | EnumName::member, value.member, value[index], callee(args), postfix ++ -- | Enum member, member, index, call, and postfix update chains |
| Prefix | &target, !, unary +, unary -, prefix ++ -- | Alias target, logical not, sign, and update |
| Multiplicative | * / % | Multiply, divide, remainder |
| Additive | + - | Add/subtract; + concatenates if either operand is a string |
| Relational | < <= > >= | Numeric ordering comparison |
| Equality | == != | Equality and inequality |
| Logical AND | && | Short-circuits when the left operand is falsey |
| Logical OR | || | Short-circuits when the left operand is truthy |
| Conditional | condition ? when_true : when_false | Right-associative; evaluates exactly one result arm |
| Lowest | = += -= *= /= %= | Right-associative assignment |
:: is used only to select a declared enum member. Although & is
parsed in the prefix position, it is legal only as the direct initializer in
var alias = ⌖; it is not a general address operator. See
True writable aliases for its valid targets and
restrictions.
?: conditional expressions
var score = 72;
event on_frame() {
string label = score >= 60 ? "ready" : "keep tuning";
system.log(label);
}
Atlas evaluates the condition once, then evaluates exactly one result arm: the expression
after ? when the condition is truthy, or the expression after :
when it is falsey. The unselected arm is not evaluated, so calls and updates in that arm
do not run.
Conditional expressions associate right-to-left, so
a ? b : c ? d : e means a ? b : (c ? d : e). The two result
arms must have compatible kinds. Matching kinds are compatible, number and bool may be
mixed, null may pair with another result kind, and struct results must use the same named
struct type.
Arithmetic and strings
var result = 2 + 3 * 4; // 14
var wrapped = (2 + 3) * 4; // 20
var ratio = 7 / 2; // 3.5
var remainder = 7 % 2; // 1
string message = "score: " + result;
Division or remainder by zero currently produces 0. Booleans are rejected in ordinary
arithmetic; use an explicit numeric value. A bool may be used as a condition and may be
initialized into a numeric var, where false/true behave as 0/1.
Equality, ordering, and truthiness
Null equals only null. If either equality operand is text or a handle, Atlas compares string representations; otherwise it compares numeric representations. Restrict ordering comparisons to numeric values and equality comparisons to scalar values. Array and struct equality is not a supported semantic contract.
Conditions use these truthiness rules:
| Falsey | Truthy |
|---|---|
null, false, numeric 0, and "" |
Nonzero numbers, true, nonempty strings, and object-backed values such as arrays, structs, fonts, models, targets, and vision tools |
&& and
|| short-circuit correctly, but the current VM keeps the selected operand
value while analysis classifies the result as bool. Do not depend on a stored logical
expression being normalized to true or false.
Assignment and increment
var count = 1;
var values[2] = { 5, 10 };
event on_frame() {
count += 2;
count++;
++count;
values[0]--;
}
The supported update targets are variables and array elements. Use increment/decrement as
standalone statements. The current VM gives prefix and postfix forms the same updated-value
result, and member-field increment/decrement is not fully lowered; write
settings.threshold += .1; rather than settings.threshold++;.
Control flow
if, else if, and else
var score = 72;
event on_frame() {
if (score >= 90) {
system.log("excellent");
} else if (score >= 60) {
system.log("ready");
} else {
system.log("keep tuning");
}
}
switch, case, and default
const idle_mode = 0;
event on_frame() {
var mode = 1;
switch (mode) {
case idle_mode:
system.log("idle");
break;
case 1:
system.log("active");
// No break: execution falls through to case 2.
case 2:
system.log("armed");
break;
default:
system.log("unknown");
}
}
The grammar is
switch (expression) { case constant: statements default: statements }.
Atlas evaluates the switch expression once, compares it with the case labels, and begins
at the first matching label. If no case matches, execution begins at
default when one is present; otherwise the switch is skipped.
- Every
caselabel must be a compile-time scalar constant: a literal, enum member, define, const value, or constant expression. - Case values must be unique and compatible with the switch value. Number and bool are compatible for matching.
- A switch may contain at most one
defaultlabel, and it may appear anywhere among the cases. - Execution falls through into later case bodies until a
break,return, or another control transfer stops it. caseanddefaultlabels are legal only directly inside a switch.
while and do while
event on_frame() {
var index = 0;
while (index < 3) {
system.log(index);
index++;
}
var attempts = 0;
do {
attempts++;
} while (attempts < 2);
}
A while checks before its first iteration. A do while runs its
body once before checking and requires a semicolon after the closing condition.
for, break, and continue
var samples[5] = { 3, 6, 9, 12, 15 };
event on_frame() {
for (var index = 0; index < samples.count(); index++) {
if (samples[index] == 6) {
continue;
}
if (samples[index] > 12) {
break;
}
system.log(samples[index]);
}
}
- The iterator declaration in a
forinitializer must usevar. - Any of the three clauses may be empty:
for (;;) { ... }. continuejumps to the nearest enclosing loop's next iteration; in afor, the step runs first. A switch alone is not a continue target.breakexits the nearest enclosing loop or switch.- Using
breakoutside a loop or switch, orcontinueoutside a loop, is an error.
Scope and name resolution
Global scope
Top-level variables are initialized once when a script is loaded and are visible to every function and event. Global declarations, functions, structs, and enums are collected before bodies are checked, so forward references resolve.
Block scope
A name declared inside { ... } exists through that block. Inner blocks may shadow outer names. Redeclaring a name in the same block is an error.
For-loop scope
A for creates one scope spanning its initializer, condition, step, and body. Its iterator is unavailable after the loop.
Switch scope
A switch creates one scope shared by every case and default body. Labels do not create separate scopes, so wrap a case's statements in an explicit { ... } block when names should be isolated.
Separate function namespace
A variable and function may share a name. A bare call resolves the function; an ordinary value read resolves the variable.
API names
An API root such as output, input, or xb is always the API, in every scope: a script cannot declare one of those names (see Reserved names).
var value = 10;
event on_frame() {
var value = 20; // shadows the global
{
var value = 30; // shadows the event local
system.log(value);
}
system.log(value);
}
A local becomes available after its initializer has been analyzed, so a later declarator can
read an earlier one: var first = 1, second = first + 1;. There are no closures,
imports, namespaces declared by scripts, classes, access modifiers, or dynamically scoped
variables.
Language and runtime limits
| Limit | Current value | Purpose |
|---|---|---|
| Parser nesting and expression-chain budget | 200 | Rejects pathologically deep source before it can exhaust the native stack. |
| Elements in one declared array | 1,000,000 total across dimensions | Prevents a declaration from allocating unbounded memory. |
| User-function call depth | 40 active calls | Turns runaway recursion into a runtime error. |
| VM instruction budget | 2,000,000 instructions per event (and for the global initializers) | Stops a likely infinite loop instead of hanging the application. Each event starts with the full budget. |
| Arguments to a user function | Exactly its declared parameter count | Too few or too many arguments are validation errors. |
Stay far below the hard limits. Event handlers should do bounded work and return quickly so video and controller packets continue to flow. See Diagnostics for runtime and validation failures.
Constructs Atlas does not provide
Atlas intentionally uses a compact event-driven language. The current lexer, parser, compiler, and VM do not implement the following constructs:
sequence,yield,sleep,await, coroutines, or scheduled blocksforeach, for-in loops, or null-coalescing operators- Bitwise operators, shift operators, pointers, casts, or manual allocation (for bit work, use the
math.bit_*and shift helpers) - Classes, inheritance, interfaces, lambdas, closures, or exceptions
- Imports, includes, modules declared in source, macros, or function-like defines
- Function overloads, optional parameters, default parameters, or variadic user functions
- Dynamic array append/remove methods
Use events for scheduling, ordinary functions for reusable calculations, structs for grouped state, fixed arrays for collections, and explicit state variables for work that spans multiple event dispatches.
Next steps
Functions & References
Learn return inference, scalar values, deep-copied struct parameters, shared struct members, and true writable aliases.
Events
Choose the right callback for startup, video, controller input, connection state, network packets, and cleanup.
API Reference
Browse every current built-in namespace, method, constant, typed vision member, and network packet member.
Diagnostics
Understand errors, warnings, unsupported spellings, and current implementation caveats.