|
| 1 | +<!-- WURST_LANGUAGE_AGENT_DOC_VERSION: 2026-08-08 --> |
| 2 | +# WurstScript language digest |
| 3 | + |
| 4 | +This is the compact, agent-oriented language reference shipped with the WurstScript compiler. It covers language semantics and compiler-facing syntax; standard-library APIs, dependency conventions, UI rules, and object-editor policies belong to the project or dependency documentation. |
| 5 | + |
| 6 | +## File and block structure |
| 7 | + |
| 8 | +Every Wurst source file is inside a package. Blocks are indentation-based; use tabs or four spaces consistently and never mix indentation styles. |
| 9 | + |
| 10 | +```wurst |
| 11 | +package Example |
| 12 | +
|
| 13 | +init |
| 14 | + print("loaded") |
| 15 | +``` |
| 16 | + |
| 17 | +Statements normally end at a newline. A newline can continue after `(`, `[`, or an operator, and before `.`, `..`, `)`, `]`, or `begin`. |
| 18 | + |
| 19 | +## Declarations and expressions |
| 20 | + |
| 21 | +Use `let` for immutable locals and `var` when mutation is required. Type inference is preferred when the type is clear. Explicit types remain useful at public boundaries and for lambda target types. |
| 22 | + |
| 23 | +```wurst |
| 24 | +let immutable = 5 |
| 25 | +var mutable = 10 |
| 26 | +constant int SOME_ID = 'A000' |
| 27 | +int array values = [1, 2, 3] |
| 28 | +
|
| 29 | +function max(int a, int b) returns int |
| 30 | + if a > b |
| 31 | + return a |
| 32 | + return b |
| 33 | +``` |
| 34 | + |
| 35 | +Primitive types include `boolean`, `int`, `real`, and `string`; Warcraft values usually use nullable handle types such as `unit`, `group`, `effect`, and `player`. |
| 36 | + |
| 37 | +Operators include arithmetic (`+`, `-`, `*`, `/`), integer division (`div`), modulo (`%`, `mod`), boolean operators (`and`, `or`, `not`), comparisons, and the conditional expression `condition ? ifTrue : ifFalse`. |
| 38 | + |
| 39 | +`/` is real division even when both operands are integers. Use `div` for integer division. WC3 integers are signed 32-bit and overflow silently, so convert before a large multiplication: `worth.toReal() * count`, never `(worth * count).toReal()`. |
| 40 | + |
| 41 | +Control flow uses `if`/`else if`/`else`, `switch`/`case`/`default`, `while`, and `for`: |
| 42 | + |
| 43 | +```wurst |
| 44 | +for i = 0 to 10 |
| 45 | + ... |
| 46 | +for i = 10 downto 0 |
| 47 | + ... |
| 48 | +for unit u in group |
| 49 | + ... |
| 50 | +``` |
| 51 | + |
| 52 | +`continue` skips the current loop iteration. `skip` is a no-op statement. |
| 53 | + |
| 54 | +## Null-safe access |
| 55 | + |
| 56 | +`?.` accesses a member only when its receiver is non-null. The receiver is evaluated once, and method arguments are not evaluated when it is null. |
| 57 | + |
| 58 | +```wurst |
| 59 | +target?.kill() |
| 60 | +let owner = target?.getOwner() |
| 61 | +if node?.next?.next == null |
| 62 | + ... |
| 63 | +``` |
| 64 | + |
| 65 | +The receiver must have a nullable type; `int`, `real`, and `boolean` cannot use `?.`. If the accessed member returns a non-nullable value such as `int`, a null-safe call can only be used as a standalone statement. `?.` is not an assignment target. |
| 66 | + |
| 67 | +## Modern Wurst best practices |
| 68 | + |
| 69 | +- Prefer `?.` when the null case is simply a no-op: `target?.kill()`. Use an explicit `if target != null` when the null case needs different handling or when you need to consume a non-nullable return value. |
| 70 | +- Use `readonly` for package, class, and module variables that callers or consumers may read but only the declaring owner may write. This is an encapsulation boundary, not a replacement for `let`/`constant` immutability. |
| 71 | + |
| 72 | + ```wurst |
| 73 | + package Score |
| 74 | + public readonly int value |
| 75 | +
|
| 76 | + public function setValue(int next) |
| 77 | + value = next |
| 78 | + ``` |
| 79 | + |
| 80 | +- String concatenation automatically infers `.toString()` for a non-string operand. Write `"score: " + score`, not `"score: " + score.toString()`. Keep an explicit call only when you intentionally need a standalone string or a particular overload; redundant calls produce a compiler warning. |
| 81 | +- Enums are non-nullable value types. Do not compare an enum with `null`; use an enum member such as `Unknown`/`None` when the domain needs a sentinel, or keep a separate boolean for “has a value”. |
| 82 | + |
| 83 | +## Functions, packages, and imports |
| 84 | + |
| 85 | +Functions omit Jass-style `takes` and `returns nothing`: |
| 86 | + |
| 87 | +```wurst |
| 88 | +function printMax(int a, int b) |
| 89 | + print(max(a, b).toString()) |
| 90 | +``` |
| 91 | + |
| 92 | +Package members are private by default; use `public` for exports. Class members are public by default; use `private` or `protected` to restrict them. Every package implicitly imports `Wurst` unless it imports `NoWurst`. |
| 93 | + |
| 94 | +`import` makes names available locally. `import public` also re-exports those names. Package initialization runs top-to-bottom, with imported packages initialized before their importers. Avoid `initlater` except to break an unavoidable initialization cycle. |
| 95 | + |
| 96 | +Use `UpperCamelCase` for packages and classes, `lowerCamelCase` for functions, members, locals, and tuples, and `UPPER_SNAKE_CASE` for top-level constants. |
| 97 | + |
| 98 | +## Cascade and extension syntax |
| 99 | + |
| 100 | +The cascade operator calls methods on the same receiver and returns that receiver, which is useful for setup: |
| 101 | + |
| 102 | +```wurst |
| 103 | +CreateTrigger() |
| 104 | + ..registerAnyUnitEvent(EVENT_PLAYER_UNIT_ISSUED_ORDER) |
| 105 | + ..addCondition(Condition(function condition)) |
| 106 | + ..addAction(function action) |
| 107 | +``` |
| 108 | + |
| 109 | +Extension functions use `this` as their receiver: |
| 110 | + |
| 111 | +```wurst |
| 112 | +public function unit.getX2() returns real |
| 113 | + return GetUnitX(this) |
| 114 | +``` |
| 115 | + |
| 116 | +Prefer extension APIs and value tuples such as `vec2` over raw handle plumbing when the standard library provides them. Avoid unchecked `castTo`; prefer interfaces, modules, or explicit data modeling. |
| 117 | + |
| 118 | +## Lambdas and closures |
| 119 | + |
| 120 | +Every lambda needs a target type; standalone lambda expressions cannot infer one: |
| 121 | + |
| 122 | +```wurst |
| 123 | +Predicate<int> even = x -> x mod 2 == 0 |
| 124 | +
|
| 125 | +doAfter(1.) -> |
| 126 | + print("later") |
| 127 | +``` |
| 128 | + |
| 129 | +Locals captured by a closure are captured by value. Assigning to a captured local inside a callback does not update the outer local afterwards. Keep dependent work inside the callback that creates the value, store shared mutable state in an owning class, or use `reference(value)` deliberately and destroy the reference when finished. |
| 130 | + |
| 131 | +Lambdas used as the Jass `code` type cannot accept parameters or capture locals. |
| 132 | + |
| 133 | +## Classes, interfaces, modules, and tuples |
| 134 | + |
| 135 | +Objects created with `new` generally need `destroy`; tuples are value types and must not be destroyed. Destructors (`ondestroy`) remain explicit for Lua output—Lua garbage collection does not replace Wurst ownership and cleanup. |
| 136 | + |
| 137 | +```wurst |
| 138 | +class Missile |
| 139 | + function onCollide(unit target) |
| 140 | +
|
| 141 | +class Fireball extends Missile |
| 142 | + override function onCollide(unit target) |
| 143 | + ... |
| 144 | +``` |
| 145 | + |
| 146 | +`super(...)` must be the first constructor statement. Overridden methods require `override`. Interfaces declare required methods; modules (`use`) inject reusable members. |
| 147 | + |
| 148 | +Prefer `T:` generics for performance-sensitive or instance-heavy containers: |
| 149 | + |
| 150 | +```wurst |
| 151 | +class Box<T:> |
| 152 | + T value |
| 153 | +``` |
| 154 | + |
| 155 | +The older unconstrained `T` form erases through integer casts and can share storage in surprising ways. |
| 156 | + |
| 157 | +## Lua and Jass targets |
| 158 | + |
| 159 | +The target is selected by the project `wurst.build` `scriptMode` field. `wc3Patch` separately selects the compatible core Jass and standard-library era. |
| 160 | + |
| 161 | +Lua has no practical Jass operation limit; do not add `execute()` as a workaround. Use timers only for actual asynchronous delay. Jass has an operation limit per thread; `execute()` starts a new thread and heavy work may need chunking across ticks. |
| 162 | + |
| 163 | +## Compiletime |
| 164 | + |
| 165 | +Compiletime functions run while building the map and can generate object-editor data or constants: |
| 166 | + |
| 167 | +```wurst |
| 168 | +let value = compiletime(factorial(5)) |
| 169 | +
|
| 170 | +@compiletime function createSpell() |
| 171 | + new AbilityDefinitionMountainKingThunderBolt(SPELL_ID) |
| 172 | + ..setName("Wurst Bolt") |
| 173 | +``` |
| 174 | + |
| 175 | +Use stable ID helpers and wrappers. Generated object definitions should use real melee objects as bases, not other custom generated objects; inherited object fields must be audited by the project’s object-data guidance. |
| 176 | + |
| 177 | +## Formatting and diagnostics |
| 178 | + |
| 179 | +Use spaces around binary operators, no space before call parentheses, and no spaces around `.`, `..`, or `?.`. Put doc comments (`/** ... */`) on public APIs when they should appear in autocomplete. Prefix intentionally unused variables with `_`. |
| 180 | + |
| 181 | +When unsure, search the compiler’s tests and nearby working code. A successful parse is not proof of correct Wurst semantics: check ownership, closure capture, target mode, and the generated behavior as well. |
0 commit comments