# Smilium 2.1 — Language Reference

Smilium is a strict, kid-friendly programming language born inside SmileyOS
(a from-scratch Rust OS). This file is the canonical reference for humans AND
for AI coding tools (Cursor, Copilot, Claude, …). Everything here is Smilium
2.1 as shipped — if it's not in this file, it's not in the language.

Try it online: https://smileytech.mk/smilium#try
Docs with runnable examples: https://smileytech.mk/smilium
File extension: `.smy`

## Hard rules (follow these when generating Smilium)

- Every statement ends with `;`. No exceptions.
- BUT: directives (`@core`, `@shell`, …) take NO semicolon, and a block's
  closing `}` takes NO semicolon. The ONLY `};` in the language is a one-line
  list transform, because it sits inside a statement: `xs.map(e){ return (e * 2); };`
- Blocks ALWAYS use `{ }`. There is no brace-less `if`.
- Struct values are PLAIN literals: `Player p = { name: "Ana" };` — never
  `Player p = Player { ... };` and there is no `new`.
- `ask()` cannot sit inside a struct literal or another expression. Ask first,
  store it, then use the variable.
- One-line `struct` declarations ARE allowed: `struct Player { string name; number hp; }`
- Types are strict and explicit. Scalars: `number`, `string`, `boolean`.
- English keywords only. Single-word names for everything.
- No classes, no inheritance, no exceptions. Structs + methods, `catch` nets.
- An UNSET variable is `null` ("no value yet") — never a silent zero.
  Using a null value in an operation is a friendly runtime stop.
- Comments: `//` — own line or after code.
- String literals use `"` only. Multiline strings with `"""..."""`.
- Interpolation inside strings: `"Hi {name}, next year {age + 1}"` — works in
  `print(...)` and `ask(...)` prompts.
- House style: block returns are parenthesized — `return (p over 20);`
- Bracket attributes (`[const]`, `[state]`, `[persist]`, `[csv]`, `[json]`)
  go on their OWN line, one per line, directly above the declaration.
- Do NOT use these legacy functions (they parse but are deprecated):
  `in()`, `out()`, `outLine()`, `writeFile()`, `readFile()`, `fileExists()`,
  `jsonSet()`, `jsonGet()`, `rand()`, `inNumber()`, `toNum()`, `toStr()`,
  `Player.loadArray()` (use `Player.load` — the declared type decides),
  `.listen` (renamed `.watch`; old spelling still parses).

## Program shape: directives

```smilium
@core      // always required: variables, if, loops, functions, structs
@shell     // text programs: print, ask
@system    // random, round, clamp, format, pad, type, sleep, clear, time
@date      // today, getYear..getSecond
```

`@ui` (windows/buttons) and `@game` (sprites) exist but ONLY inside SmileyOS —
never use them for programs meant for the online playground or CLI.

## Types, variables & null

```smilium
number score = 42;         // whole or decimal
string name = "Marija";    // text
boolean happy = true;

number age;                // NO initializer -> age is null
print(age is null);        // true ("never set?")
print("".empty);           // true (.empty = null OR zero-length)

number[] nums = [3, 1, 4]; // typed lists (auto-grow on indexed write, cap 1024)
string[] words = ["a"];

number<> points;           // MAP: values by string key (keys are always strings)
points["ana"] = 10;        // number<> / string<> / boolean<> / Player<>
print(points["ghost"] is null);   // missing key -> null, never a crash

score += 5;                // also -= ; full form: score = score + 1;
```

## Operators — two equal spellings

Symbol style: `==  !=  >  <  >=  <=  &&  ||  !`
Word style:   `is  isnt  over  under  and  or  not`
Both work and mix freely. Math: `+  -  *  /  %`.
Null checks: `x is null`, `x isnt null`, `x.empty`.
Numbers: `x.between(a, b)` — inclusive both ends.
Strings -> numbers: `"5".number()` (friendly stop if not a number).

## Control flow

```smilium
if(score over 50){ print("passed"); }
else{ print("try again"); }

repeat(3){ }               // simplest loop
repeat(3, i){ }            // with counter i = 0,1,2

while(n over 0){ n = n - 1; }
for(i = 0; i < 3; i = i + 1){ if(i is 1){ continue; } }
break;                     // leave the loop
exit;                      // end the WHOLE program
// runaway protection: a loop that runs 50000 times is stopped with a
// friendly "does it ever end?" — programs never hang.
```

### when — match one value

First true case wins, NO fall-through, empty `()` is the catch-all (last):

```smilium
when(score){
    (100)          { print("perfect"); }
    (over 90)      { print("A"); }
    (80 to 89)     { print("B"); }         // inclusive range
    ("a", "b")     { print("comma = OR"); }
    ()             { print("everything else"); }
}
```

Over an ENUM variable, `when` checks COMPLETENESS: every enum value needs a
case (or a `()` catch-all) — missing ones are a parse error listing them.

### One-line block limits

A block written on ONE line (`if(c){ print("x"); }`, a one-line `when` case)
may only hold SIMPLE statements — print, assignment, break/continue/exit.
A user FUNCTION CALL can NOT live in a one-line block; give it a multi-line
block:

```smilium
when(choice){
    ("1") {
        listContacts();      // OK — multi-line case
    }
    ("2") { print("bye"); }  // OK — print is simple
}
```

There is NO `between` operator. Range checks are spelled out —
`need(idx >= 0 and idx <= contacts.length - 1, "bad number");` — or use a
`when` range case `(0 to 9)`.

## Functions

```smilium
func double(number n) -> number {
    return (n * 2);
}
func spawn(number hp = 100, number speed = 2, boolean boss = false) { }

spawn(30, 5, true);        // positional
spawn(speed: 5);           // NAMED args: skip middle defaults
spawn(30, boss: true);     // positional first, then named
```

Named-arg rules: `name: value` must match a parameter (typo -> did-you-mean),
positional args come before named ones, no naming a param twice.

Parameters are SCALARS only — `number`, `string`, `boolean`. Arrays and
structs can NOT be parameters: `func f(Contact[] c)` is a parse error.
Instead, functions can read AND modify top-level variables directly — declare
shared lists/structs at the top of the program and use them inside functions.
For single structs, use a method (`func Contact.show(){ }`) so `self` is the
struct.

### Methods on structs

```smilium
struct Player { string name; number score; }

func Player.addPoints(number pts) -> Player {
    self.score = self.score + pts;     // self = the receiver, SHARED
}
func Player.total() -> number {
    return (self.score * 2);
}

Player p = { name: "Ana", score: 10 };
p.addPoints(5);                        // mutates p in place
p.addPoints(1).addPoints(2);           // methods chain (implicit `return self`)
number t = p.total();                  // methods work in expressions
```

No classes: a method is a normal function with `.`-call sugar and a `self`.
Methods may live in imported module files.

## Input / output

```smilium
print("plain line");
print("colored", Colors.green);          // optional 2nd arg: Colors.x
string name = ask("Your name? ");        // typed by the target; re-asks on bad input
string s = ask("Name: ", false);         // false = answer stays on the SAME line
string c = ask("Pick:", ["red", "green"]);   // fixed choices - re-asks until one matches
u.name = ask("Name? ");                  // ask() fills ANY target (field, element)
User u2 = ask("New user:");              // whole struct: one question per field
Direction d = ask("Way?");               // enum target -> choices offered automatically
clear();                                 // wipe output (@system)
```

Colors: `Colors.black white red green blue yellow magenta cyan orange purple
pink brown gray lightGray darkGray darkGreen`.

## Arrays — methods

`add(v) pop() insert(i, v) remove(i) sort() reverse() shuffle() sum() min()
max() avg() contains(v) find(v) first() last() pick() slice(a, b) join(sep)
each length empty lock() unlock()`

### List transforms — one-line blocks, `return ( )` required

```smilium
number[] doubled = prices.map(p){ return (p * 2); };
number[] cheap = prices.where(p){ return (p under 20); };
number n = prices.count(p){ return (p over 20); };
boolean all = prices.all(p){ return (p over 0); };
boolean any = prices.any(p){ return (p over 30); };

prices.each(p){ }          // block param types are OPTIONAL everywhere
prices.each(p, i){ }       // optional index
"text".each(c){ }          // strings walk char by char
csv.split(",").each(part){ }   // .each works on expression results too
```

## Maps — methods

`m["key"]` read/write (missing key reads null), `has(k) keys values remove(k)
length empty each(key, value)`. Map keys are ALWAYS strings; the declared left
type is the VALUE type (`number<>`, `Player<>`, …). Maps save/load as JSON.

## Strings — methods

`upper() lower() capitalize() trim() replace(a, b) split(sep) reverse() sort()
contains(s) find(s) starts(s) ends(s) count(s) repeat(n) slice(a, b) first()
last() number() length empty` — plus indexing `s[0]`.

## Enums

```smilium
enum Direction { up, down, left, right }

Direction way = Direction.up;   // values print/save as their NAME ("up")
way = Direction.left;
print(Direction.all);           // [up, down, left, right] - loop it
Direction r = Direction.all.pick();   // random value

way++;                          // step FORWARD through the values in order
way--;                          // step BACK
way += 2;                       // bigger steps too (way = way + 2)
way.reset();                    // back to the FIRST value
// stepping past either end = friendly stop (catchable)
```

Typos teach: `Direction.upp` -> "did you mean 'up'?". Enums can be struct
field types and `[persist]`ed. `when(way)` demands completeness (see when).

## Structs

```smilium
struct Address { string city; }
struct Player {
    string name;
    number score;
    Address address;        // nested structs
    number[] rolls;         // arrays inside
    -string secret;         // leading '-' = HIDDEN from all saved output
}

Player p = { name: "Ana", score: 10, address: { city: "Skopje" } };
print(p.address.city);          // deep paths read & write
Player[] team = [p];            // print(team) renders a TABLE

// shorthand + destructuring
Player q = { name, score };     // same-named variables fill fields
{ name, score } = q;            // pull fields OUT into variables
[ lo, hi ] = nums;              // array destructuring

// REFERENCES: = shares, .clone() copies
Player b = p;                   // same ONE object (b.score = 9 changes p)
Player c = p.clone();           // a real copy
print(p.same(b));               // true - identity check
print(p.id);                    // built-in unique id
```

You can NOT copy a struct OUT of an array: `Player one = team[0];` is an
error. Reach elements in place with deep paths — `team[0].score = 9;` assigns,
`print("{team[i].name}");` reads — or walk the list:
`team.each(pl, i){ print("{i + 1}. {pl.name}"); }`.

A struct literal — and the whole declaration around it — must be written on
ONE line. These work:

```smilium
Contact c = { name: n, phone: p };
Contact[] cs = [{ name: "Ana" }, { name: "Bo" }];
cs.add({ name: "Ico" });          // .add() takes a one-line literal too
```

Spreading the fields (or the array) over several lines is a parse error —
for long lists, declare `Contact[] cs = [];` and `.add({ ... });` one per
line.

A single condition (`if`/`while`/`need`) may be up to ~128 characters —
longer ones are a teaching error ("This condition is too long"). Split big
checks into boolean locals first:

```smilium
boolean hitWall = x < 0 or x > width - 1 or y < 0 or y > height - 1;
if(hitWall or hitSelf){ }
```

## Saving & persistence

```smilium
save("highscore", 120);              // simple values -> shared save.json
number best = load("highscore");     // typed by the variable
save("points", myMap);               // arrays and maps round-trip as real JSON

p.save("hero");                      // struct -> hero.json (extension auto)
Player back = Player.load("hero");
team.save("team", Format.csv);       // -> team.csv (opens in Excel)
Player[] t = Player.load("team", Format.csv);

[csv]                                // attribute above a struct = its DEFAULT
struct Row { string name; }          // format ([json] is the global default)
```

CSV is FLAT: a nested struct/array field in CSV is a teaching error that says
to use `Format.json`. Hidden `-fields` never appear in any saved output.

```smilium
[persist]                            // REMEMBERED across runs:
number runs = 0;                     // loads on start, auto-saves each change,
runs = runs + 1;                     // keyed by the variable's name
```

`[persist]` works on numbers/strings/booleans/scalar arrays/maps/enums — NOT
on structs or struct lists (use `p.save("hero")` / `team.save("t", Format.csv)`
and load with an `is null` guard). Stack `[state]` under it to also watch the
value.

`Type.load(...)` may ONLY be the right side of a DECLARATION:
`Player[] t = Player.load("team");` — a plain reassignment
(`team = Player.load("team");`) is an error, and a variable declared FROM a
load can not be reassigned later. The one safe pattern (works inside
functions too):

```smilium
Contact[] contacts = [];           // the working list, declared plain

func loadBook() {
    Contact[] l = Contact.load("contacts");
    if(l isnt null){
        contacts = l;      // copy into the shared top-level list
    }
}
```

## Reactive: .watch

```smilium
number score = 0;
score.watch(old, new){               // param types optional
    print("score: {old} -> {new}");
}
score = 10;    // fires; assigning the SAME value again does not

watch(hp, gold, title){              // one block for a TEAM of variables
    print("{changed} moved");        // `changed` = which one fired
}

[state]                              // arrays opt IN to watching
number[] xs = [];
xs.watch(old, new){ }
xs.add(1);                           // fires
```

One watcher per variable; writes inside a watcher never fire another one.
Structs are watchable too: `p.watch(old, new){ }` fires on ANY field change —
old/new are full snapshots (`old.score` works).

## Guards & error handling

```smilium
need(cond, "message");               // false -> print message + leave the
                                     // NEAREST door: loop breaks, func returns,
                                     // top level exits

catch(string err){                   // ONE program-wide net, declarative
    print("problem: {err}", Colors.red);
}

User u = User.load("f").catch(string e){   // LOCAL net for one call;
    u = { name: "fresh" };                 // result is null unless assigned
};
```

Runtime problems (null use, missing file, enum step past the end, locked
writes…) run the nearest net and the program CONTINUES. Without any net the
error prints and the program stops — always with a teaching hint.

## Constants

```smilium
[const]
number maxLives = 3;      // value REQUIRED at declaration; assigning later
                          // is a PARSE error. The one variable that can't be null.
age.lock();               // runtime, undoable protection
age.unlock();
print(age.locked);
```

## Events (programs that stay alive)

```smilium
every(1000) { print("tick"); }
onKey(Key k) {
    if(k == Keys.space){ print("jump!"); }
    if(k == Keys.q){ exit; }
}
```

Keys: `Keys.a`–`Keys.z`, `Keys.0`–`Keys.9`, `Keys.left right up down space enter`.

## Builtins (@system unless noted)

```smilium
random(1, 6)      random(list)      round(x, 2)    clamp(v, min, max)
abs(x)  floor(x)  ceil(x)  sqrt(x)  pow(b, e)  min(a, b)  max(a, b)
sin(x)  cos(x)  tan(x)                       // radians
format(x, 2)     // number -> text, fixed decimals
pad(42, 4)       // "0042"
type(x)          // type name as text
len(x)           // length of text or list (also .length property)
sleep(500)       // ms
time()           // ms since program start
today()  getYear()  getMonth()  getDay()  getDayOfWeek()   // @date
getHour()  getMinute()  getSecond()                        // 1 = Monday
```

## Modules (split code across files)

A file becomes a module with `#name` on its first line; import with `@name`.
Works in SmileyOS and in the web playground (tabs are the filesystem).
Functions, structs AND struct methods travel through an import.

```smilium
// mathlib.smy
#mathlib
@core
func double(number n){ return (n * 2); }

// main.smy
@core
@shell
@mathlib
print("double 5 = {double(5)}");   // 10
```

## Complete example

```smilium
@core
@shell
@system

enum Mood { happy, calm, grumpy }
struct Player {
    string name;
    number score;
}

func Player.addPoints(number pts) -> Player {
    self.score = self.score + pts;
}

catch(string err){
    print("problem: {err}", Colors.red);
}

[persist]
number best = 0;

string name = ask("Your name? ");
Player p = { name: name, score: 0 };
Mood mood = Mood.all.pick();
print("Hello {p.name}! Mood today: {mood}");

repeat(3, i){
    number roll = random(1, 6);
    need(roll isnt null, "no roll!");
    p.addPoints(roll);
    print("roll {i}: {roll}");
}

when(p.score){
    (over 14)  { print("CHAMPION!", Colors.green); }
    (8 to 14)  { print("Nice!", Colors.cyan); }
    ()         { print("Try again!"); }
}

if(p.score over best){
    best = p.score;          // [persist] auto-saves the new best
}
p.save("hero");
print("saved, {p.name}!", Colors.green);
```

---
Smilium 2.1 · SmileyTech.mk · feedback: https://smileytech.mk/smilium#suggest
What's next (2.2 & 3.0 design phase): https://smileytech.mk/smilium#roadmap
