Browse docsLanguage
On this page
Language reference

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.

program-shape.atlas
// 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.

Semicolons matter. Declarations, assignments, calls, 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.

Comments
// 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

Reserved words

The following spellings are recognized as language keywords and cannot be reused as variable, field, enum-member, struct, function, or event names.

GroupReserved words
Declarationsconst, var, let, auto, define, enum, struct, function, event
Control flowif, else, switch, case, default, while, do, for, break, continue, return
Core typesbool, string, char, font, model, target, type
Vision typescolor, contour, motion, edges, circle, line, template, feature, match, qr, barcode, ocr, track, face, person
Literalstrue, false, null
Removed type spellingsint, 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.

Number literals
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, not 1..
  • Scientific notation such as 1e3 is not supported.
  • Hexadecimal literals begin with 0x or 0X.

Strings

Single and double quotes both create a string. char is a synonym for string; it is not a one-character numeric type.

String literals and escapes
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

Boolean and null literals
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.

List forms
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 valuePurposeDefault when omittedtype(value)
var, let, autoGeneral numeric values. The three spellings are synonyms.0"int32" when whole; "float" when fractional
boolLogical state: true or false.Currently null, which is falsey. Initialize bools explicitly."bool" after a bool value is assigned
string, charUnicode text. Both declaration spellings mean string."""string"
ArrayOrdered, zero-based values. Arrays may contain mixed value kinds.Each fixed element receives its declared element default."array"
Enum memberA readonly named integer constant.First member starts at 0."int32"
Struct typeA user-defined group of named fields.A fresh recursive copy of all field defaults.The struct's declared name
fontOverlay text style created with font.create or font.default.null"font"
modelA model resource/value used by model and vision APIs.null"model" for a model resource
targetA detected target with position, score, label, and box fields.null"target"
Vision tool typesLive detector configuration and results for one tool family.nullThe specific tool name, such as "color"
HandleA runtime-owned UI/resource handle returned by selected APIs.Not directly declarable as handle."handle"
nullNo current value.—"null"
Reference aliasAn 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.

Typed detector values
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.

type()
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.

Mutable values
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.

Constants
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 values
define DEFAULT_THRESHOLD = 0.65;
define RETRY_LIMIT = 3;

var threshold = DEFAULT_THRESHOLD;
Keep constant initializers simple. The current native constant-purity pass rejects function calls but does not prove every identifier or property expression is a compile-time constant. Literals, enum members, arithmetic on them, and nested array literals are the portable forms.

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

Fixed and inferred arrays
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

Array operations
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

Two-dimensional array
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.

Preserve fixed sizes. Element writes are the clearest mutation form. The current analyzer checks replacement sizes for struct array fields but does not uniformly enforce the original size when an ordinary array variable is replaced wholesale.

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 declaration and access
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.

Struct declaration
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 values
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.

PrecedenceOperators and formsMeaning
HighestEnumName::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
Conditionalcondition ? when_true : when_falseRight-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 = &target;; it is not a general address operator. See True writable aliases for its valid targets and restrictions.

?: conditional expressions

Select one value
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

Expressions
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:

FalseyTruthy
null, false, numeric 0, and "" Nonzero numbers, true, nonempty strings, and object-backed values such as arrays, structs, fonts, models, targets, and vision tools
Use logical expressions as conditions. && 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

Updates
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

Conditional branches
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

Selection with explicit fallthrough
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 case label 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 default label, 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.
  • case and default labels are legal only directly inside a switch.

while and do while

Condition-controlled loops
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

Counted loop
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 for initializer must use var.
  • Any of the three clauses may be empty: for (;;) { ... }.
  • continue jumps to the nearest enclosing loop's next iteration; in a for, the step runs first. A switch alone is not a continue target.
  • break exits the nearest enclosing loop or switch.
  • Using break outside a loop or switch, or continue outside 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).

Block shadowing
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

LimitCurrent valuePurpose
Parser nesting and expression-chain budget200Rejects pathologically deep source before it can exhaust the native stack.
Elements in one declared array1,000,000 total across dimensionsPrevents a declaration from allocating unbounded memory.
User-function call depth40 active callsTurns runaway recursion into a runtime error.
VM instruction budget2,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 functionExactly its declared parameter countToo 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 blocks
  • foreach, 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.

Atlas scripting language reference (source snapshot). Behavior on this page is written from the native lexer, parser, analyzer, compiler, and VM sources.