Language Guide
Syntax, declarations, control flow and data.
Declarations
A .sa file contains configuration, callbacks, helper functions and variables.
system::config() { ... }; // server.cfg
player::connect(int playerid) { }; // callback
function add(int a, int b) { return a + b; };
let total = 0; // variable
; after a callback, function or config block is required.Variables
let hp = 100; // int
let speed = 1.5; // float
let name = "CJ"; // string
let online = true; // bool
hp = 90; // reassign
hp += 5; // compound: += -= *= /= %=
Operators
| Operator | Meaning |
|---|---|
+ - * / % | Arithmetic; + also joins strings |
== != ~= | Equality (~= is an alias for !=) |
< <= > >= | Comparison |
and or not | Logical; also &&, ||, ! |
+= -= *= /= %= | Compound assignment |
& | ^ ~ << >> | Bitwise and/or/xor/not, shifts |
Control flow
player::command("/rank", int score) {
if (score >= 100) {
SendClientMessage(playerid, "Veteran");
} else if (score >= 50) {
SendClientMessage(playerid, "Regular");
} else {
SendClientMessage(playerid, "Rookie");
}
};
Loops
All the usual loop forms are available. break leaves the nearest loop or switch; continue jumps to the next iteration.
// while
let i = 0;
while (i < 10) {
i += 1;
}
// do / while: runs at least once
do {
i -= 1;
} while (i > 0);
// C-style for
for (let n = 0; n < 5; n += 1) {
if (n == 3) { break; }
}
// iterate an array
let names = ["amy", "bob", "cid"];
for (let name in names) {
SendClientMessage(playerid, "Hi {name}");
}
// iterate an array with the index
for (let index, name in names) {
SendClientMessage(playerid, "{index}: {name}");
}
// iterate a map (keys are visited in sorted order)
let scores = { "amy": 10, "bob": 20 };
for (let key in scores) {
SendClientMessage(playerid, "{key} = {scores[key]}");
}
// skip some iterations
for (let n = 0; n < 20; n += 1) {
if (n % 2 == 0) { continue; }
if (n > 15) { break; }
}
switch
switch compares one value against case labels. There is no fall-through: the first matching arm runs, then the switch ends, so you never need break at the end of an arm.
player::command("/class", int id) {
switch (id) {
case 0:
SendClientMessage(playerid, "Medic");
case 1:
SendClientMessage(playerid, "Police");
default:
SendClientMessage(playerid, "Civilian");
}
};
Arrays and maps
let scores = [10, 20, 30];
let player = { "name": "CJ", "score": 0 };
scores[0] = 15;
player["score"] = 42;
let first = scores[0];
let n = len(scores);
push(scores, 40); // mutate in place: [10, 20, 30, 40]
let last = pop(scores); // 40, scores is now [10, 20, 30]
let part = scores[1:3]; // [20, 30]
let sorted = sort([3, 1, 2]); // [1, 2, 3]
Literals
let hex = 0xFF00FF00; // hex int
let wait = 500ms; // duration -> milliseconds
let pos = [1.0, 2.0, 3.0];
let meta = { "hp": 100 };
Comments and includes
// line comment
/* block comment
spanning lines */
Includes are resolved relative to the including file. Circular includes are rejected with an error.
Functions
Use function to define a reusable helper. Functions can also be called by name from timers and database callbacks.
function clampHealth(float value) {
if (value < 0.0) { return 0.0; }
if (value > 100.0) { return 100.0; }
return value;
}
player::spawn(int playerid) {
SetPlayerHealth(playerid, clampHealth(150.0));
};
Parameters: types, defaults and rest
Function parameters are typed and checked at compile time. Trailing parameters may have default values, and a final ...rest parameter collects any extra arguments into an array.
function spawnAt(Player target, float x, float y, float z = 3.0) { ... }
function total(int first, ...rest) {
let sum = first;
for (let n in rest) { sum += n; }
return sum;
}
spawnAt(playerid, 100.0, 200.0); // z defaults to 3.0
total(1, 2, 3, 4); // 10
Constants
const MAX_HP = 100;
const RATIO: float = 0.5;
function cap(float hp) { return hp > MAX_HP ? MAX_HP : hp; }
Assigning to a const is rejected at compile time.
Calling a function by name
CallRemoteFunction(name, ...args) and its alias CallLocalFunction call a top-level function by its name. This is useful when the function is chosen at runtime, and it keeps Pawn code portable.
function add(int a, int b) { return a + b; }
function isBig(int n) { return n > 10; }
let sum = CallRemoteFunction("add", 2, 3); // 5
let big = CallLocalFunction("isBig", 50); // 1
let x = CallRemoteFunction("missing"); // 0 (logged)
The result is always an int, like PAWN: numbers truncate, true/false become 1/0, a string returns its length, an object handle returns its id and null returns 0. Calling a name that does not exist writes a warning to the log and returns 0.
Scopes
A variable declared at module level is shared by every callback for the whole server lifetime. A variable declared inside a block only exists there.
let total = 0; // module scope
player::connect(int playerid) {
let local = 5; // callback scope
total = total + local; // module variable is writable
};
Loop safety
Loops are bounded. A single callback, command or timer may run at most 10 million iterations; exceeding that stops the callback with loop iteration limit exceeded and leaves the server responsive, so one bad line cannot freeze it.
Truthiness
| Value | Truthy? |
|---|---|
true / non-zero number | yes |
false / 0 / 0.0 | no |
| non-empty string | yes |
empty string "" | no |
null | no |
| non-empty array / map | yes |
Operator precedence
From highest to lowest:
| Level | Operators |
|---|---|
| 1 | ! not - + ~ (unary) |
| 2 | * / % |
| 3 | + - |
| 4 | << >> |
| 5 | < <= > >= |
| 6 | == != ~= |
| 7 | & |
| 8 | ^ |
| 9 | | |
| 10 | and && |
| 11 | or || |
| 12 | ? : (ternary) |
Use parentheses when the intent is not obvious.
Keywords
| Keyword | Meaning |
|---|---|
let | Declare a variable |
if / else | Conditional |
return | Return from a function or callback |
function | Declare a helper function |
true / false | Boolean literals |
null | No value |
and / or / not | Logical operators |
while / do / for | Loops |
in | Range iteration in for |
break / continue | Loop control |
switch / case / default | Multi-way branch (no fall-through) |
Absent by design
- No classes, inheritance or methods — callbacks and functions only.
- No
selfor namespaces on natives — call them by name. - No
goto, labels or C-styleswitchfall-through. - No
foreachkeyword — usefor (let x in arr).