Skip to content

Commit abb080a

Browse files
authored
Ship compiler language digest for agents (#1220)
1 parent 06f6889 commit abb080a

1 file changed

Lines changed: 181 additions & 0 deletions

File tree

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
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

Comments
 (0)