Syntax Cheatsheet
Every construct in the language, at a glance. All snippets on this page compile with swoftc check.
File structure
A .sw file is a sequence of top-level declarations, in any order:
| Declaration | Purpose |
|---|---|
import "name" / import "./file.sw" | Pull in a module (libraries) |
var name = expr | Module-level variable, private to the file |
export <declaration> | Make a symbol importable (libraries) |
command "name" { ... } | Chat command (aliases share one body) |
event Name { ... } | Server event handler |
function name(...) { ... } | Reusable function |
async function name(...) { ... } | Function that may wait |
item "id" { ... } | Custom item (reference) |
mob "id" { ... } | Custom mob (reference) |
gui "name" { ... } | Declarative inventory GUI (reference) |
scoreboard "name" { ... } | Sidebar (reference) |
tablist "name" { ... } | Player-list columns (reference) |
bossbar "name" { ... } | Boss bar (reference) |
api "/path/:param" { ... } | HTTP route (reference) |
every <duration> { ... } | Boot-started repeating task (reference) |
on packet "Name" { ... } | Inbound packet listener (reference) |
server { ... } | Server bootstrap config (reference) |
storage { ... } | Persistence backend (reference) |
persistent name: Type = default | Persistent variable (guide) |
Comments: // line and /* block */. Statements are newline-insensitive — there are no semicolons, and you can split expressions across lines freely.
Commands
command "tp",
command "teleport" { // aliases share one body
permission: "swoftlang.teleport"
description: "Teleport a player"
arguments {
player: Player = sender // one-token default
target: either<Player|Location> // union type, no default
}
execute {
if args.player is not a Player {
send "<red>You can only teleport players" to sender
halt
}
teleport args.player to args.target
send "<lime>Teleported ${sender} to ${args.target}"
}
}- Alias groups:
command "tp", command "teleport" { ... }or the short formcommand "tp", "teleport" { ... }. - Arguments are readable as
args.nameand also bound unprefixed (player,target). execute async { ... }makes the whole handler async.senderis always bound to the executingPlayer.
Events
event PlayerChat {
priority: 0
execute {
set event.message to "[Filtered] ${event.message}"
if event.message contains "badword" {
cancel event
}
}
}Known events (the compiler rejects unknown names):
| Event | Cancellable | Properties on event | Shortcut aliases |
|---|---|---|---|
PlayerChat | yes | player (ro), message (rw), cancelled (rw) | player, message |
PlayerJoin | no | player (ro), first_spawn (ro), world (ro) | player, name |
PlayerUseItem | yes | player (ro), item (ro), custom_id (ro, optional<String>), cancelled (rw) | player, item |
BlockBreak | yes | player (ro), block (ro), location (ro), cancelled (rw) | player, block, location |
BlockPlace | yes | player (ro), block (ro), location (ro), cancelled (rw) | player, block, location |
MobSpawn | no | mob (ro) | mob |
MobDeath | no | mob (ro), killer (ro, optional<Player>) | mob, killer |
MobDamage | yes | mob (ro), damage (rw), cancelled (rw) | mob, damage |
TpsChange | no | past (ro), current (ro) | past, current |
ServerPing | no | motd (rw), online (rw), max (rw) | motd |
cancel event on a non-cancellable event is a compile error:
e_cancel.sw:3:9: error: event 'PlayerJoin' is not cancellable
cancel event
^Types
| Type | Also written | Values |
|---|---|---|
String | — | "text", with ${...} interpolation |
Integer | int | 42, -3 |
Double | double | 2.5 |
Boolean | bool | true, false |
Player | — | an online player |
Location | — | from location(x, y, z[, yaw, pitch]) |
World | — | from world(name) |
Item | — | from item(material[, amount]), custom_item(id) |
Mob | — | from spawn mob ... as m, all_mobs() (mobs) |
Display | — | from spawn_text_display(...) etc. (displays) |
Song | — | from song(file) (songs) |
Skin | — | from skin(t, s), fetch_skin(name) (skins) |
Canvas | — | from map_canvas() (maps) |
Schedule | — | from a schedule expression (schedulers) |
WorldLoader | — | from anvil_loader(...) etc. (worlds) |
either<A|B> | — | union; narrow with is a |
optional<T> | — | maybe-missing; narrow with exists / otherwise |
list<T> | — | [1, 2, 3], all_players() |
There is no null. none is the missing value of an optional<T>, and the typechecker forces you to prove presence before use — see Options. Number in is a Number checks accepts both Integer and Double. Unknown type names in argument lists are accepted with a compile warning (unknown type 'Thing', treating as UNKNOWN).
Variables and assignment
command "vars" {
execute {
set x to 10 // declare/assign a local
set x to x + 1 // reassign
set sender.health to 20.0 // property write (see Properties)
set held to sender.held_item // property read into a local
}
}set <lvalue> to <expr> is the only assignment form. An lvalue is a plain variable or a dotted property path.
Operators and precedence
Lowest to highest:
| Level | Operators |
|---|---|
| 1 | or, || |
| 2 | otherwise (optional fallback) |
| 3 | and, && |
| 4 | not <expr> |
| 5 | =, ==, !=, <, >, <=, >=, contains, is, is not, is a / is an, is not a, exists, is missing |
| 6 | +, - |
| 7 | *, /, % |
| 8 | unary - |
| 9 | . property access, calls, literals, ( ) |
English-y aliases: is ≡ =; is not ≡ !=; is a Player / is an Item is a type check; x exists / x is missing test optionals. + on strings concatenates.
command "ops" {
execute {
set x to 1 + 2 * 3 // 7 — precedence
set y to (1 + 2) * 3 // 9 — parens group
set ok to x > 6 and not (y < 5)
if "SwoftLang" contains "swoft" { // case-insensitive substring
send "yes" to sender
}
if x is a Number and ok is not a String {
send "typed" to sender
}
}
}String interpolation
${...} inside any string literal:
command "interp" {
execute {
set who to sender.name
send "Hello ${who}, health ${sender.health}" // simple paths
send "Sum: ${1 + 2 * 3}, upper: ${uppercase(who)}" // full expressions
}
}Simple dotted paths (${a.b.c}) stay in the string and are resolved at runtime through the property registry. Anything more complex is desugared at compile time into a concatenation chain of real expressions. Both forms are fully type-checked — a typo like ${sender.latencey} is a compile error with a suggestion.
Control flow
command "flow" {
execute {
// if / else if / else
set x to 7
if x = 5 {
send "five" to sender
} else if x = 7 {
send "seven" to sender
} else {
send "other" to sender
}
// counted loop; optional counter runs 1..N
loop 3 times as i {
send "iteration ${i}" to sender
}
loop 3 times {
send "again" to sender
}
// while (runtime guard stops runaway loops at 100,000 iterations)
set n to 3
while n > 0 {
set n to n - 1
}
// iterate a list
loop all_players() as p {
send "hi ${p.name}" to p
}
loop all players as p { // sugar for all_players()
send "hey ${p.name}" to p
}
loop first 5 of all_players() as p { // cap the iteration count
send "you're early" to p
}
halt // stop this script run
send "never reached" to sender
}
}Brace-free bodies
Every body position — if/else, loops, function bodies, execute, async blocks, GUI handlers — also accepts exactly one statement with no braces:
command "terse" {
execute {
// any body can be exactly one brace-free statement
set x to 7
if x > 5 send "big" to sender
else if x = 5 send "exact" to sender
else halt
loop 3 times as i send "i = ${i}" to sender
async send "from a task" to sender
// a dangling else binds to the nearest if
if x > 0 if x > 100 send "huge" to sender
else send "positive, not huge" to sender
}
}Scoreboard lines { } and tablist column { } bodies still require braces.
Functions
function factorial(n: int) {
if n <= 1 {
return 1
}
return n * factorial(n - 1) // recursion (depth cap 256)
}
function greet(player: Player) {
send "Welcome, ${player}!" to player
}
command "demo" {
execute {
set f to factorial(5) // call as expression
greet(sender) // call as statement
call greet(sender) // optional 'call' keyword
return // bare return = stop, like halt
}
}Parameter types are optional (untyped params are Any). A function whose paths sometimes return a value gets return type optional<T> — callers must narrow it.
Inline functions (lambdas)
function(...) without a name is an expression — a first-class function value:
function double(x: Integer) return x * 2 // brace-free body
command "lambda" {
execute {
set triple to function(x: Integer) return x * 3
send "${triple(4)}" to sender // 12: call through the variable
set count to 0
set inc to function() set count to count + 1
inc() // closures capture by reference
send "${count}" to sender // 1
set task to async function(p: Player) {
wait 1 seconds
send "later" to p
}
spawn task(sender) // spawn works on async lambdas
}
}Call names resolve in order: local variable holding a callable → declared function → builtin. Calling a value the checker knows is not a function is a compile error ('x' is an Integer, not a function). Details on the Functions page.
Options (no more null)
command "find" {
arguments {
who: optional<Player> // optional command argument
}
execute {
set found to player("Notch") // player() : optional<Player>
if found exists {
send "hi ${found.name}" to found // narrowed to Player here
} else {
send "offline" to sender
}
if found is missing {
send "still missing" to sender
}
set target to args.who otherwise sender // optional<T> otherwise T -> T
set label to none // the missing value
set title to label otherwise "guest"
send "${title}" to target
}
}Using a possibly-missing value where a concrete one is needed is a compile error:
e_optional.sw:4:22: error: the send target is optional<Player> and may be missing; check it with 'if ... exists' or provide a fallback with 'otherwise'Properties
Dotted paths read and write real game state through a compile-time-checked whitelist (no reflection). Read-only writes and typos are compile errors.
| Owner | Read/write | Read-only |
|---|---|---|
Player | health, max_health, food, food_saturation, level, exp, gamemode, location, world, held_item, held_slot, display_name, flying, allow_flying, flying_speed, skin | name, uuid, latency, online |
Location | x, y, z, yaw, pitch | block_x, block_y, block_z |
Item | material, amount, name, lore | stats.*, tags.* (tags writable — items) |
Mob | health, name, location | max_health, type, custom_id (mobs) |
World | time, time_rate | — (worlds) |
Display | text, item, block, scale, translation, rotation, billboard, glow_color, background, alignment, line_width, see_through, view_range | — (displays) |
Song | — | title, author, length, speed (songs) |
Skin | — | texture, signature |
Canvas | — | width, height |
server | motd | tps, average_tps, mspt (TPS) |
request | — | method, path, query, body, params.* — api handlers only |
command "props" {
execute {
set sender.health to sender.max_health
set sender.location.y to sender.location.y + 10 // wither + one teleport
set sender.held_item.amount to 32
set sender.gamemode to "creative" // enum-validated string
}
}Values with immutable Java representations (Location, Item) are written copy-on-write: the runtime reads the deepest settable anchor (location, held_item, or a local variable), applies withers, and stores once. A stored Location is a snapshot — mutating a local copy never moves the player.
Statements reference
| Statement | Form |
|---|---|
| assign | set x to <expr> / set a.b.c to <expr> |
| send | send <expr> [to <player | list | all>] |
| broadcast | broadcast <expr> |
| teleport | teleport <player> to <player | location> |
| halt | halt — end this script task |
| cancel | cancel event — sync event handlers only |
| if | if <cond> { } [else if <cond> { }] [else { }] |
| loop | loop <n> times [as i] { } |
| foreach | loop [first <n> of] <list> as x { } |
| while | while <cond> { } |
| call | f(args) / call f(args) |
| return | return [<expr>] |
| wait | wait <expr> ticks|seconds|millis — async only |
| spawn | spawn f(args) — fire-and-forget task |
| async block | async { ... } |
| GUI nav | open gui "g" to <p> [with { k: v }], replace gui "g" to <p>, close gui for <p>, go back for <p> |
| scoreboard | show scoreboard "s" to <p>, hide scoreboard from <p>, update scoreboard for <p> |
| tablist | show tablist "t" to <p>, hide tablist from <p>, set tablist header|footer to <expr> for <p> |
| bossbar | show bossbar "b" to <p>, hide bossbar "b" from <p>, set bossbar "b" progress|text to <expr> for <p> |
| title | title <expr> [subtitle <expr>] to <p> [fade in <dur>] [stay <dur>] [fade out <dur>], clear title for <p> |
| actionbar | actionbar <expr> to <p> [for <dur>] |
| belowname | belowname <expr> for <p>, set belowname score to <expr> for <p>, clear belowname for <p> |
| items | give item "id" to <p> [amount <n>] |
| mobs | spawn mob "id" at <loc> [as <var>], despawn <mob> |
| viewers | show <entity> to <p|all>, hide <entity> from <p|all>, set name of <entity> to <expr> for <p>; viewers of <entity> (expr → list<Player>) |
| nametags | set nametag [prefix|suffix|color] of <p> to <expr> [for <viewer|all>], reset nametag of <p> [for ...] |
| packets | send packet "Name" { field: expr, ... } to <target>, cancel packet |
| displays | show|hide display <d> to|from <target>, mount display <d> on <entity>, teleport display <d> to <loc>, destroy display <d> |
| worlds | create|load|unload|save|delete world ..., clone world "a" to "b" with <loader>, import anvil world ... |
| blocks | set block at <loc> to "STONE", place <block> at <loc>, remove block at <loc>, fill blocks from <loc> to <loc> with "X" |
| http | reply [code <n>] with <expr> — api handlers only |
| songs | play|pause|resume|stop song ..., broadcast song <file>, set song volume ..., fade song ... |
| sounds | play sound <key> to <target> [at <loc>] [volume <v>] [pitch <p>], stop sound [<key>] for <target> |
| particles | spawn particle <name> at <loc> [count <n>] [offset <x>,<y>,<z>] [speed <s>] [to <viewer|all>] |
| toasts | show toast <title> [description <s>] [icon <mat>] [frame task|goal|challenge] to <target> |
| maps | draw pixel|rect|text on <canvas> ..., give map of <canvas> to <p> |
| motd | set server motd to <expr> |
| schedulers | cancel schedule <handle> (creation is the schedule expression) |
Targets accept a Player, a list<Player>, or the keyword all (broadcast).
Durations
Declaration positions (update:, refresh:, cooldown:, title fades, actionbar ... for, every <dur>, schedule after/every <dur>, fade song ... over <dur>) take an integer literal plus a unit and are converted to ticks at compile time:
| Written | Ticks |
|---|---|
10 ticks / 1 tick | as-is |
3 seconds / 1 second | × 20 |
250 millis | ÷ 50 (minimum 1) |
The wait statement is more general — its amount is any numeric expression, and the unit is kept for the runtime (seconds/millis sleep the task's virtual thread; ticks aligns to tick end).
Async
async function countdown(target: Player, from: Integer) {
loop from times as i {
send "<yellow>${from - i + 1}..." to target
wait 1 seconds
}
send "<lime>Go!" to target
}
async function double_it(x: Integer) {
wait 1 ticks
return x * 2
}
command "start" {
execute {
spawn countdown(sender, 3) // fire-and-forget from sync code
async { // anonymous fire-and-forget block
wait 10 ticks
send "ready" to sender
}
send "started" to sender // runs immediately, no blocking
}
}
command "race" {
execute async { // whole handler is async
wait 5 ticks
set doubled to double_it(21) // direct async call: sequential
send "result ${doubled}" to sender
}
}Coloring rules the compiler enforces:
waitonly in async color (async function,execute async,async { }).- Direct calls to async functions only from async color; from sync code use
spawn. spawnandasync { }are legal in both colors and never return a value.cancel eventis banned in async regions — cancel in the sync prologue, then detach.- Spawned tasks get a shallow snapshot of variables: reassignments don't leak back, object mutations are shared.
e_wait.sw:3:9: error: 'wait' is only allowed in async functions, 'execute async', or 'async { }' blocksPersistence
storage {
backend: files "data/swoftlang" // or: sqlite "path.db" | mysql { ... } | mongodb "uri"
flush: every 30 seconds // write-behind cadence (default 30s)
}
persistent total_joins: Integer = 0 // global scalar, default required
persistent kills for Player: Integer = 0 // keyed by subject
event PlayerJoin {
execute {
set total_joins to total_joins + 1
}
}Persistent types are limited to String | Integer | Double | Boolean. Defaults are mandatory, so reads are total — never optional. Keyed access without for is a compile error (kills is keyed by Player — use kills for <player>). See the server config page for backend configuration.
Modules
import "music" // stdlib/addon by name (addon path)
// import "./lib/util.sw" — relative form
var greeting_count = 0 // module-level var: private, shared state
export function greet(target: Player) { // 'export' makes it importable
set greeting_count to greeting_count + 1
send "hello (#${greeting_count})" to target
}
function helper() return 1 // un-exported = private to this fileexport legally prefixes function, item, mob, gui, scoreboard, tablist, and bossbar. Commands, events, api routes, schedulers, and packet listeners in an imported module always register — they're effects, not symbols. Full rules, collision errors, and the addon search path live in the Libraries section.
What the compiler catches
A non-exhaustive sampler of compile errors (each shown with real swoftc output on its reference page): unknown/read-only properties, typos with did-you-mean suggestions, optional misuse, wrong builtin arity or argument types, wait in sync code, cancel event on non-cancellable events, sidebar line and tablist column caps, invalid enum values (gamemode, bossbar color/style, click filters, tablist skins, rarities, stats, attributes, entity types, nametag and glow colors, toast frames, HTTP methods), GUI slot ranges, duplicate server blocks, unknown item/mob ids in give item / spawn mob / drops, reply outside api handlers, cancel packet outside packet listeners, blocking builtins in sync code, import cycles, calls to un-exported module functions, and use of never-assigned variables.
Named runtime objects are checked against their declarations, too. Referring to a scoreboard, tablist, bossbar, gui, hologram, npc, or named schedule that no script declares is a compile error naming the declaration you're missing:
unknown_gui.sw:3:9: error: unknown gui 'nope'; declare it with 'gui "nope" { }'And the target of a directed statement is type-checked: show, send, title, subtitle, actionbar, and friends want a Player, a list of players, or all — handing one a String (or any other type) is caught before it ships:
send_to_string.sw:4:22: error: cannot send a message to a String; expected a Player, a list of players, or 'all'