Skip to content

Commit 1099559

Browse files
committed
doc fixes
1 parent b165d0d commit 1099559

3 files changed

Lines changed: 156 additions & 6 deletions

File tree

_doc/manual.md

Lines changed: 122 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,14 @@ string r = "yes" // OK
4646
string s = i // Error
4747
```
4848

49+
Beyond these primitives, most of the values you work with in a map have *handle types* such as
50+
`unit`, `group`, `timer`, `effect` or `player`. These come from the Warcraft III API (defined in
51+
`common.j`) rather than from Wurst itself, and the standard library provides convenient wrappers
52+
and extension functions for them. You can browse the wrapped native API in the
53+
[standard library reference](https://wurstlang.org/stdlib) (see the *Handle Wrappers* section).
54+
There is also the Jass `code` type for function references, which is covered in
55+
[Interfacing with Jass](#interfacing-with-jass-code-boolexpr-and-filters-a-mini-jass-101).
56+
4957
### Syntax
5058

5159
WurstScript uses indentation based syntax to define Blocks. You can use either spaces or tabs for indentation, but mixing both will throw a warning.
@@ -1058,7 +1066,7 @@ Or expressed differently: A and B are in the same partition if and only if their
10581066

10591067
### Abstract Classes
10601068

1061-
An _abstract_ class is a class, that is declared abstract — it may or may not
1069+
An _abstract_ class is a class, that is declared abstract. It may or may not
10621070
include abstract functions. You cannot create an instance of an abstract class,
10631071
but you can create subclasses for it which are not abstract.
10641072

@@ -1628,6 +1636,117 @@ The current implementation creates a specialized function with the right number
16281636
Since Jass allows at most 31 parameters, function calls must not use more than 31 arguments in total.
16291637

16301638

1639+
### Interfacing with Jass: `code`, `boolexpr` and filters (a mini Jass 101)
1640+
1641+
Wurst compiles down to Jass, and the Warcraft III API (`common.j`) is written in Jass.
1642+
Most of the time you never see this, because the standard library wraps it for you.
1643+
But when you read `common.j`, port an old map, or stare at a native signature that wants a
1644+
`code` or `boolexpr`, it helps to understand a few Jass concepts. These types are fully
1645+
available in Wurst, but for writing your own logic they are superseded by closures. This
1646+
section is a short primer, followed by what to do *instead* in idiomatic Wurst.
1647+
1648+
#### The `code` type
1649+
1650+
Jass has no real first-class functions. The closest it offers is the `code` type: a bare
1651+
reference to a top-level function that takes **no parameters and returns nothing**. You create
1652+
one with the `function` keyword followed by a function name:
1653+
1654+
```wurst
1655+
function onExpire()
1656+
print("timer done")
1657+
1658+
let t = CreateTimer()
1659+
TimerStart(t, 1.0, false, function onExpire)
1660+
```
1661+
1662+
A value of type `code` is extremely limited. You can only:
1663+
1664+
- pass it to a native, and
1665+
- have the engine call it later.
1666+
1667+
You **cannot**:
1668+
1669+
- give it parameters,
1670+
- make it return a value,
1671+
- store it in an array (`code` arrays are not allowed in Jass),
1672+
- capture local variables from around it.
1673+
1674+
Because of these limitations, callbacks that need context have to fetch it from the engine
1675+
through global "getter" natives at call time, e.g. `GetExpiredTimer()` inside a timer callback,
1676+
or `GetEnumUnit()` / `GetFilterUnit()` inside an enumeration.
1677+
1678+
#### `boolexpr`, `filterfunc` and `conditionfunc`
1679+
1680+
Many natives that loop over things (groups, forces, events, regions) take a *filter* so they
1681+
only act on units you care about. In Jass this filter is a `boolexpr` ("boolean expression").
1682+
1683+
You don't build a `boolexpr` directly. You write a `code` function that returns a `boolean`, then
1684+
wrap it with one of two natives:
1685+
1686+
- `Filter(code)` returns a `filterfunc`
1687+
- `Condition(code)` returns a `conditionfunc`
1688+
1689+
Both `filterfunc` and `conditionfunc` are subtypes of `boolexpr`, so either can be passed where
1690+
a `boolexpr` is expected. Inside the filter function you read the current unit with
1691+
`GetFilterUnit()`:
1692+
1693+
```wurst
1694+
function isAliveFilter() returns boolean
1695+
return GetFilterUnit().isAlive()
1696+
1697+
let g = CreateGroup()
1698+
g.enumUnitsInRange(pos, 500.0, Filter(function isAliveFilter))
1699+
```
1700+
1701+
This is exactly the pattern behind the Jass the new user was porting:
1702+
1703+
```wurst
1704+
// Jass-style: a global filter function plus a manual group enum
1705+
function filterEnemy() returns boolean
1706+
return GetFilterUnit().isEnemyOf(enumeratingPlayer)
1707+
1708+
g.enumUnitsInRange(pos, radius, Filter(function filterEnemy))
1709+
```
1710+
1711+
Note how the filter has to read its "argument" (which player are we filtering for?) from a
1712+
global, because `code` cannot capture or take parameters. That global juggling is the whole
1713+
reason this style is awkward.
1714+
1715+
#### Now that you know this, don't write it this way
1716+
1717+
Understanding `code` and `boolexpr` is useful for *reading* Jass and `common.j`, but writing new
1718+
Wurst this way means fighting the language. Wurst's whole paradigm is to write high-level,
1719+
maintainable code and let the compiler do the tedious low-level work. The standard library
1720+
therefore provides **closure wrappers** for these Jass primitives, and closures can do
1721+
everything `code` cannot: take parameters, return values, be stored in lists and arrays, and
1722+
capture local variables.
1723+
1724+
So the idiomatic version of the group enum above is simply:
1725+
1726+
```wurst
1727+
import ClosureForGroups
1728+
1729+
forUnitsInRange(pos, radius) u ->
1730+
if u.isEnemyOf(caster.getOwner()) // `caster` is captured directly, no global needed
1731+
caster.damageTarget(u, 50.0)
1732+
```
1733+
1734+
A few rules of thumb:
1735+
1736+
- Prefer the closure wrappers (`ClosureForGroups`, `ClosureTimers`, `ClosureEvents`, …) over
1737+
raw `code`/`boolexpr` natives. See [Lambdas and Closures](#lambdas-and-closures) below and the
1738+
[Mastering Closures](https://wurstlang.org/tutorials/getclose.html) tutorial.
1739+
- You almost never need a hand-written `boolexpr`. The enum natives have overloads without a
1740+
filter, and the closure wrappers let you filter inline with normal `if`.
1741+
- Don't pre-optimize by reaching for the lowest-level construct. The compiler inlines and
1742+
optimizes high-level code; readability and correctness come first.
1743+
- If you genuinely do need a `code` value (e.g. a native with no Wurst wrapper), remember a
1744+
parameterless, non-capturing lambda is also a valid `code` value. See
1745+
[Lambda expressions as code-type](#lambda-expressions-as-code-type).
1746+
1747+
In short: `code` and `boolexpr` remain available in Wurst so it can talk to the engine, but when
1748+
you write your own logic, use closures.
1749+
16311750
### Lambdas and Closures
16321751

16331752
A lambda expression (also called anonymous function) is a lightweight way to provide an implementation
@@ -1710,8 +1829,8 @@ doAfter(10.0, () -> begin
17101829
doMoreStuff()
17111830
end)
17121831
```
1713-
It is also possible to have a return statement inside a begin-end expression
1714-
and `return` can now be used anywhere inside the closure statement block.
1832+
A `return` statement can be used anywhere inside a closure body, including inside
1833+
a begin-end expression.
17151834

17161835
Inside a closure body, `it` refers to the closure object itself. This can be used for self-references, for example to destroy the closure from within its own body without passing it as a lambda parameter.
17171836
```wurst

_doc/wurstbeginner.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,21 @@ The functions inside the `Printing` package then reference the original warcraft
128128

129129
You can use the global vscode search or file search (`ctrl+p`) to find packages you're looking for.
130130

131+
## Printing and Debugging
132+
133+
The `print` function is your most basic debugging tool. It takes a `string` and displays it on screen.
134+
135+
Unlike some scripting languages, Wurst does not implicitly convert other types to text. To print a number or any non-string value, convert it explicitly with `.toString()` and build the message with the `+` operator:
136+
137+
```wurst
138+
let u = GetTriggerUnit()
139+
print("Unit " + u.getName() + " has " + u.getHP().toString() + " hp")
140+
```
141+
142+
Most standard library types provide a `.toString()` method, and it is conventional to add one to your own classes for readable debug output (use a separate `display()`-style method if the text is meant for players rather than debugging).
143+
144+
For longer-lived or filtered logging, the standard library also offers the `ErrorHandling` and `Execute` utilities, but `print` plus `.toString()` covers the vast majority of debugging needs.
145+
131146
## Coding Conventions
132147

133148
In Wurst, it is generally not recommended to call most natives directly. Instead, an extension function should be used to make the code more concise, readable, consistent and documented. Extension functions can also be easier found via autocomplete.

_tutorials/getclose.md

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -115,11 +115,11 @@ init
115115
print(i) // This will print "1"
116116
```
117117

118-
Here, the second print uses the value of `i` as it was initially. Setting it inside the closure only has an effect in the closure's context. To share changes, you can use a `Reference<T>` to wrap the variable:
118+
Here, the second print uses the value of `i` as it was initially. Setting it inside the closure only has an effect in the closure's context. To share changes, you can wrap the variable in a `Reference<T>`, created with the `reference()` helper:
119119

120120
```wurst
121121
init
122-
var i = new Reference(1)
122+
let i = reference(1)
123123
execute() ->
124124
i.val = 2
125125
print(i.val) // This will print "2"
@@ -128,7 +128,23 @@ init
128128
destroy i
129129
```
130130

131-
By using `Reference<T>`, both the closure and the rest of your code can work with the same underlying value.
131+
Because the reference is a normal object, the closure captures the same instance the outer code holds, so both work on the same underlying value. Remember to `destroy` it when you are done (or use `i.into()`, which returns the contained value and destroys the reference in one step).
132+
133+
---
134+
135+
## Early return inside a closure
136+
137+
`return` works inside a closure body just like it does in a normal function: it exits the closure.
138+
139+
This is useful for flattening with guard clauses. Instead of wrapping the rest of the body in an `if`, you return early on the cases you want to handle separately and keep the main logic unindented:
140+
141+
```wurst
142+
EventListener.onTargetCast(myUnit, MY_SPELL_ID) (caster, target) ->
143+
if not target.isAlive()
144+
return
145+
// main logic stays unindented
146+
caster.damageTarget(target, 100.)
147+
```
132148

133149
---
134150

0 commit comments

Comments
 (0)