# The Code Runner language

A small JavaScript-flavoured DSL. It is **not** JavaScript and **not** PHP — the differences below
are where scripts actually break. Everything here was verified against the lexer, parser and
interpreter, not inferred from the syntax.

## Program shape

There are no functions, no classes, no imports and no modules. A script is a flat list of
statements executed top to bottom in a single global scope.

```
/* Every statement ends with a semicolon. */
var boardId = 42;
var cards = get_json(send_to_bm("v2", "GET", "/boards/" + boardId + "/cards", ""));
```

## Comments

**`/* … */` only.** There are no `//` line comments — a `/` that is not followed by `*` lexes as
the division operator, so `// note` is a syntax error, and an unterminated `/*` is too.

## Variables

```
var total = 0;      /* declares */
total = total + 1;  /* assigns; `var` again is also fine and simply overwrites */
```

Reading a variable that was never assigned **throws** (`Undefined variable 'x'`) — unlike PHP,
where it would warn and yield null. Declare before use, always.

## Types and literals

| Type | Literal |
|---|---|
| integer | `42` |
| float | `3.14` — a `.` must have digits on both sides |
| string | `"text"` or `'text'`, escapes `\n \t \r \\ \" \'` |
| boolean | `true` / `false` |
| null | `null` |
| array | `[1, 2, 3]` |
| associative array | `["key": value]` or `["key" => value]` |
| object | `{key: value}` or `{"key": value}` |

Objects and associative arrays are the same underlying thing — an ordered map. `{…}` and
`["k": v]` differ only in punctuation.

> **Identifier keys are taken literally.** In an array literal, `[foo: 1]` produces the key
> `"foo"` — the *name* of the identifier, never the value of a variable called `foo`. To key on a
> variable's value, assign it: `arr[foo] = 1;`.

## Reading values

```
arr[0]          /* array index          */
map["key"]      /* associative key      */
obj->property   /* property access      */
obj->a->b       /* chained reads are fine */
```

**A missing key or property returns the string `"undefined"`, not null.** This is the single most
common source of broken scripts, because `"undefined"` is a non-empty string and therefore
**truthy**:

```
var name = card->nosuchfield;   /* "undefined" */
if (name) { … }                 /* TAKEN — the guard does nothing */
if (name != "undefined") { … }  /* correct */
```

Only one level of indexing is parsed. `a[0][1]` and `a["x"]["y"]` are **syntax errors**. Walk
nested data one step at a time:

```
var row = a[0];
var cell = row[1];
```

## Writing values

```
name = "x";            /* variable            */
arr[2] = "x";          /* by index or key     */
arr[] = "x";           /* append, PHP-style   */
obj->field = "x";      /* property, ONE level */
```

- Reading `arr[]` is an error; empty brackets are for appending only.
- The assignment target must bottom out in a **top-level variable**. `a->b->c = 1;` parses but
  throws at runtime (`Cannot assign to non-variable object base`). Rebuild the branch instead:

```
var inner = a->b;
inner->c = 1;
a->b = inner;
```

- Assigning into an undeclared name auto-creates it as an empty array.

## Reserved words

Fifteen words are claimed by the lexer and **cannot be used as variable names**:

```
var  if  elseif  then  else  for  break  continue  foreach  as  push  pull  true  false  null
```

`push`, `pull`, `as` and `then` are the ones that bite, because they are ordinary English words
someone reaches for naturally. `var push = 1;` is a syntax error.

**`then` is reserved but unusable.** The lexer recognises it and the parser accepts it nowhere, so
it can only ever produce a confusing error. Never write it.

**A reserved word cannot follow `->`.** If an API response has a field with one of these names,
`obj->then` will not parse. Read it with a string key instead:

```
var value = obj["then"];        /* works */
var value = obj.pull("then");   /* also works */
var value = obj->then;          /* syntax error */
```

## Array methods

Exactly two exist, both treating the array as a map:

```
users.push("alice", 42);          /* set key -> value; returns null       */
var score = users.pull("alice");  /* get; returns "undefined" if missing  */
```

This is enforced by the **parser**, not at runtime: `arr.pop(x)` fails to parse with
`Expected: METHOD`, because `push` and `pull` are the only two words the grammar accepts after a
dot. There is no way to add a third.

## Operators

| | |
|---|---|
| arithmetic | `+` `-` `*` `/` |
| comparison | `==` `!=` `>` `>=` `<` `<=` |
| logical | `&&` `\|\|` (short-circuit), `!` (unary, chainable as `!!x`) |
| other | unary `-`, `++` |

`+` **concatenates when either side is a string**, otherwise it adds: `"n=" + 5` is `"n=5"`.
There is no separate concatenation operator, so build URLs with `+`.

`++` returns the **incremented** value in both positions — `i++` and `++i` are identical here,
unlike every C-family language. Never rely on the "old value" of `i++`. It only applies to numeric
variables and throws otherwise.

Comparison uses loose equality (`==` is PHP's `==`), so `0 == "abc"` and `"1" == 1` behave as they
do in PHP 8.

## Control flow

```
if (cond) {
    …
} elseif (other) {          /* one word — `else if` is a syntax error */
    …
} else {
    …
}

for (i = 0; i < 10; i++) {
    …
}

foreach (items as key => value) {   /* BOTH key and value are required */
    …
}

break;      /* leaves the innermost loop  */
continue;   /* next iteration; the for-increment still runs */
```

- Parentheses and braces are **mandatory**. `if (x) doThing();` without braces is a syntax error.
- `elseif` is one word. `else if` fails to parse.
- **No `var` in a `for` initialiser.** `for (var i = 0; …)` is a syntax error — write
  `for (i = 0; …)`, which creates `i` on assignment.
- `foreach` requires `key => value`; there is no value-only form. Use `_` as the key name if you
  do not need it. Iterating a non-array **throws**.

## Not in the language

`//` comments · `while` · `do` · `switch` · `try`/`catch` · ternary `?:` · `+=` `-=` `*=` `/=` ·
`%` modulo · `**` · user-defined functions · `return` · nested scopes · multi-dimensional
indexing · nested property assignment · string interpolation.

There is **no error handling**. A thrown error ends the script; write defensive guards instead.

## Execution limits

| Limit | Value | Where |
|---|---|---|
| Iterations per loop | 10 000 | counted per loop invocation |
| Wall-clock runtime | 180 s | checked inside loop bodies |
| PHP time limit | 60 s | the async worker |

The 180 s guard exists because the 60 s PHP limit measures **CPU** time on Linux and does not count
time blocked on API calls — an API-heavy loop can run for many minutes without PHP intervening.
Both `sleep()` and every API round-trip spend the same budget, so batch requests rather than
looping over single-item calls.

## Reading error messages

Runtime errors carry the DSL line: `Line: 12 - …` or `Error at line 12: …`. Lexer and parser errors
quote the offending source line back with the position highlighted. A wrong argument count reports
as `Function \`name()\` was called with the wrong number of arguments.`

## The `esc()` preprocessor

`esc("…")` is **not a runtime function**. Before the script is even tokenised, the text
`esc("…")` is rewritten into an escaped string literal, which is what lets its argument contain
raw unescaped quotes:

```
var t = esc("He said "hello" to me");   /* becomes "He said \"hello\" to me" */
```

Because it runs on raw text it has rules of its own: it only triggers on a standalone `esc(`
immediately followed by a quoted string, and a genuine multi-argument call such as
`esc("a", "b")` is deliberately left alone for the parser to reject.
