Your browser does not support HTML5 canvas. Fork me on GitHub

Manual

by peq & Frotty

WurstScript (short Wurst) is a programming language named after the German word for sausage.

The sausage is a symbol for encapsulation (Peel/Pelle), compactness (sausage meat/Brät) and modularization (cut it into slices!). And because you normally know whats inside a sausage the project is also open source and easy to use (cook).

Remember: WurstScript and its related tools are in a probably unstable state and under heavy development, so you may encounter errors and bugs we don’t know about. Please report any problems with our issue tracker at GitHub.

Note: WurstScript is written in Java and should therefore be usable on Windows, OS/X and most Linux Distributions. This applies only to the compiler & plugins, because the Wurstpack is based on the Jass New Gen Pack (and therefore windows-only).

loading TOC ...

Philosophy

WurstScript aims at a fast and easy workflow with comfort- and safety features. The execution speed is not the highest priority (even though it is pretty fast, an optimizer is included in the compiler), but instead ease of use and stress-free map-development. It should be easy to use and learn (especially with knowledge of (v)Jass) to be beginner-friendly and also understandable to non-Jass users.

While we know that WurstScript won’t replace vJass in the WC3 mapping scene (also because of the tons of vJass scripts that can’t be simply ported) we still hope it will be a very good alternative, in particular for users that are trying to learn Jass.

Need help?

If you have any questions regarding Wurst-related tools or the language itself, feel free to write us a message.

You can contact us on Hive:

Or visit the IRC channel we usually hang out.

Note that this manual is not a beginner’s tutorial and expects the reader to have prior knowledge in programming.

Syntax

The WurstScript Syntax uses indention to define Blocks, instead of using curly brackets (like Java) or keywords like ‘endif’ (like Jass). You can use either spaces or tabs for indentation, but mixing both will throw a warning. In the following we use the word “tab” to refer to the tab character or to 4 space characters.

// Some language using curly brackets (e.g. Java or Zinc)
if condition
{
    ifStatements
}
nextStatements
// Some language using keywords (e.g. Jass)
if condition
    ifStatements
endif
nextStatements
// Some language using indention (e.g. WurstScript or phyton)
if condition
    ifStatements
nextStatements

A block has to be indented by one or more tabs. Using spaces for indentation is not permitted.

In general newlines come at the end of a statement, with some exceptions:

You can use this to break longer expressions or long parameter lists over several lines, or to chain method invocations:

    someFunc(param1, param2,
        param3, param4)

    someUnit..setX(...)
        ..setY(...)
        ..setName(...)

In general WurstScript tries to avoid using symbols as much as possible to provide a clear and readable look. At the same time it is less verbose than Jass.

Basics

Wurst code is organized into packages. All your wurst code has to be inside a package. Packages can also import other packages in order to use variables, functions, classes, etc. from the imported package. Packages can have an init block that is executed when the map is loaded.

package HelloWurst
// to use resources from other packages you have to import them at the top
import PrintingHelper

// the init block of each package is called when the map starts
init
    /* calling the print function from the PrintingHelper package */
    print("Hello Wurst!")

For more information about packages, refer to the packages section. You can still use normal Jass syntax/code outside of packages (when using WurstWE, those will be parsed by PJass), but inside packages only valid wurst/jurst code is expected.

Naming Conventions

Wurst encourages several naming conventions to create a common way of writing code and to provide general readability rules:

Since Wurst comes with optimizing tools build-in, you should always choose descriptive names.

Functions

A function definition consists of a name, a list of formal parameters and a return type. The return type is declared after the formal parameters using the returns keyword. If the function does not return a value this part is omitted.

// this function returns the maximum of two integers
function max(int a, int b) returns int
    if a > b
        return a
    else
        return b

// this function prints the maximum of two integers
function printMax(int a, int b)
    print(max(a,b).toString())

function foo() // parentheses instead of "takes", "returns nothing" obsolete.
    ...

function foo2(unit u) // parameters
    RemoveUnit(u)

function bar(int i) returns int // "returns" [type]
    return i + 4

function blub() returns int // without parameters
    return someArray[5]

function foobar()
    int i // local variable
    i = i + 1 // variable assignment
    int i2 = i // support for locals anywhere inside a function

Variables

Global (local) variables can be declared anywhere in a package (function). A constant value may be declared using the constant or let keyword. Mutable variables are declared by using the var keyword or by writing the type of the variable before its name.

// declaring a constant - the type is inferred from the initial expression
constant x = 5
// The same but with explicit type
constant real x = 5
// declaring a variable - the inferring works the same as 'let', but the value can be changed
var y = 5
// declaring a variable with explicit type
int z = 7
// declaring an array
int array a

// inside a function
function getUnitInfo( unit u )
    player p = u.getOwner()
    var name = u.getName()
    print( name )
    real x = u.getX()
    real y = u.getY()
    let sum = x + y

With these basic concepts you should be able to do anything you already know for Jass. The syntax is a little bit different of course, but this is covered in the next chapter.

Expressions

Semi-Formal syntax:

Expr ::=
      Expr + Expr
    | Expr - Expr
    | Expr / Expr       // real division
    | Expr div Expr     // integer division
    | Expr % Expr       // real modulo
    | Expr mod Expr     // integer modulo
    | Expr and Expr
    | Expr or Expr
    | Expr < Expr
    | Expr <= Expr
    | Expr > Expr
    | Expr >= Expr
    | Expr == Expr
    | Expr != Expr
    | - Expr
    | not Expr
    | IDENTIFIER                // variable access
    | IDENTIFIER(Expr, Expr, ...)       // function call
    | Expr . IDENTIFIER             // member variable
    | Expr . IDENTIFIER(Expr, Expr, ...)    // member function
    | Expr .. IDENTIFIER(Expr, Expr, ...)   // member function, same as single dot
                                            // but returns the receiver type
    | new IDENTIFIER(Expr, Expr, ...)       // constructor call
    | destroy Expr                  // destroy object
    | Expr castTo Type              // casting
    | Expr instanceof Type          // instance checking
    | begin
        Statements
      end // statement expr
    | (param1, param2, ...) -> Expr  // anonymous function
    | (Expr)                        // parantheses

An IDENTIFIER is a name of a variable or function. It may start with letters and may contain letters, numbers and underscores.

Note: The definition above does not show calls to generic functions. These will be handled in a separate chapter about generics.

Cascade operator (dot-dot-operator)

Use the cascade operator .. is similar to the normal . operator and can be used for calling methods, but instead of returning the result of the called method, the cascade operator returns the receiver. This makes it possible to perform a number of operations on the same object. Here is a small example

CreateTrigger()
    ..registerAnyUnitEvent(EVENT_PLAYER_UNIT_ISSUED_ORDER)
    ..addCondition(Condition(function cond))
    ..addAction(function action)

The above code is basically equivalent to:

let temp = CreateTrigger()
temp.registerAnyUnitEvent(EVENT_PLAYER_UNIT_ISSUED_ORDER)
temp.addCondition(Condition(function cond))
temp.addAction(function action)

Statements

Skip

The simplest statement is the skip statement. It has no effect and can be used to create empty blocks.

If

if x > y
    ... // the content inside the block simply has to be indent
else if x < y // closing the if, opening the elseif-block
    ...
else
    ...
// Closing the if by indenting back

if x > y or x <= z and "blub" != "blah"
    print("if is true")
print("if done.")

if GetSpellAbilityId() == 'A000'
    AddSpecialEffect( GetSpellTargetX(), GetSpellTargetY(), FX_PATH )

Switch

// i is of type int
switch i
    case 1
        print("1")
    case 3
        print("3")
    case 88
        print("88")
    default
        print("not implemented")

As you see in the example, a switch statement is basically a nicer syntax for nesting ifs and else ifs, with the special default case.

Loops

while a > b // while-loop with input condition
    ...

for int i = 0 to 10 // for-loop
    ...

for int i = 0 to 10 step 2 // for-loop with step 2
    ...

for int i = 10 downto 0 // wurst can also count downwards
    ...

for unit u in someGroup // loop over all units in a group
    ...

for unit u from someGroup // loop over all units in group and remove the units from the group
    ...

for int i in myList
    ...

In for loops you can also omit the type of the loop variable.

for i = 0 to 10
for u in someGroup
...

For-in/from Loops

The for-in loop lets you iterate over any object which provides an iterator. A for-in loop can be transformed into an equivalent while-loop very easily:

for A a in b
    Statements

// is equivalent to:
let iterator = b.iterator()
while iterator.hasNext()
    A a = iterator.next()
    Statements*
iterator.close()

Note that iterator.close() will also be called before any return statement inside the body of the while loop.

If you already have an iterator or want to access further functions of the iterator you can use the for-from loop. The translation is very similar:

let iterator = myList.iterator()
for segment from iterator
    //Statements
iterator.close()

// is equivalent to:
let iterator = myList.iterator()
while iterator.hasNext()
    segment = iterator.next()
    //Statements
iterator.close()

Note that you have to close the iterator yourself.

Iterators

You can provide Wurst with an iterator for your desired type by providing a set of functions. By providing an iterator, the type can be used in for-loops: - function hasNext() returns boolean (return if there is another object left) - function next() returns TYPE (return the next element for your type)

With this two functions you get an iterator which can be used in for-from loops.

To make a type usable in for-in loops you have to provide

for your type, that returns the object for the iteration. This can either be a handle, like in the group-iterator or an object like the List-iterator. Your iterator should also provide a close function which clears all resources allocated by it. Most often the iterator just destroys itself in the close function.

Look at the 2 examples from the standard library:

Group-Iterator

public function group.iterator() returns group
    // return a copy of the group:
    bj_groupAddGroupDest = CreateGroup()
    ForGroup(this, function GroupAddGroupEnum)
    return bj_groupAddGroupDest

public function group.hasNext() returns boolean
    return FirstOfGroup(this) != null

public function group.next() returns unit
    let u = FirstOfGroup(this)
    GroupRemoveUnit(this, u)
    return u

public function group.close()
    DestroyGroup(this)

As you can see, the iterator is a group, therefore the group needs to provide the functions mentioned above. This is done via extension functions.

LinkedList-Iterator

public class LinkedList<T>
    ...

    // get an iterator for this list
    function iterator() returns LLIterator<T>
        return new LLIterator(dummy)

class LLIterator<Q>
    LLEntry<Q> dummy
    LLEntry<Q> current

    construct(LLEntry<Q> dummy)
        this.dummy = dummy
        this.current = dummy

    function hasNext() returns boolean
        return current.next != dummy

    function next() returns Q
        current = current.next
        return current.elem

    function close()
        destroy this

The LinkedList Iterator is a little different, because it’s a class. Still it provides the needed functions and is therefore viable as iterator. It also contains some members to help iterating. A new instance if LLIterator is returned from the .iterator() function of the LinkedList.

Assignment Shorthands

WurstScript supports the following shorthands for assignments:

i++         // i = i + 1
i--         // i = i - 1
x += y      // x = x + y
x -= y      // x = x - y
x *= y      // x = x * y
x /= y      // x = x / y

Because these shorthands simply get translated into their equivalents, they can be used with overloaded operators, too.

Packages

As mentioned above every code-segment written in Wurst has to be inside a package, packages define the code organization and separate name-spaces. Packages can also have global variables - every variable that is not inside another block (function/class/module) is declared global for that package.

When working in WurstWE, packages have to end with the endpackage keyword and the code inside has to be indent.

In WurstEclipse however, the endpackage can be omitted when the code inside is not indented.

package SomeWurstWePackage
    // Only for legacy WurstWE
    ...
    (code)
endpackage

package SomeWursteclipsePackage
...
(code)

Imports

Packages can import other packages to access classes, functions, variables, etc. that are defined public.

// declaring
package SomePackage

// importing
package Blub
import SomePackage
import AnotherPackge // importing more than 1 package
import MyExternalWurstFile // Import a scriptfile from the eclipseProject
import public PackageX // public import (see below)

The import directive searches for packages in the wurst folder of your project and all linked projects from your wurst.dependencies file.

import public

By default imported names are not exported by the package. For example the following will not compile:

package A
public constant x = 5

package B
import A

package C
import B
constant z = x

The variable x is usable in package B but it is not exported from B. So in package C we cannot use the variable x. We could fix this by simply importing A into C but sometimes you want to avoid those imports. Using public imports solves this problem because they will export everything that is imported. Thus the following code will work:

package A
public constant x = 5

package B
import public A

package C
import B
constant z = x

The special Wurst package

By default, every package imports a package named Wurst.wurst which is defined in the standard library. This package exports some packages which are used very often.

If you do not want this standard import you can disable it by importing a package NoWurst. This directive is mainly used to resolve some cyclic dependencies in the standard library.

Public declarations

All members of a package are private by default. If you want to make them visible inside packages that import that package you have to add the keyword “public”.

Constants

You can declare a variable constant to prohibit changes after initialization. This has no impact on the generated code but throws an error when trying to compile.

Examples

package First
int i // (private by default) Visible inside the package
public int j // public, gets exported

package Second
import First

int k = i // Error
int m = j // Works, because j is public


package Foo
constant int myImportantNumber = 21364 // has to be initialized with declaration

function blub()
    myImportantNumber = 123 // Error

public constant int myPrivateNumber2 = 123 // Correct keyword order

Init blocks

Another package feature are init blocks. Every package can have one or multiple init blocks anywhere inside it. All operations inside the init block of a package are executed at mapstart.

At the beginning of an init block you can assume that all global variables inside the current package are initialized.

package MyPackage
init
    print("this is the initblock")

Note: Since wc3 has a micro op limitation, too many operations inside init-blocks may stop it from fully executing. In order to avoid this you should only place map-init Stuff inside the init blocks and use timers and own inits for the other stuff.

Initialization and initlater

The initialization rules for Wurst are simple:

  1. Inside a package initialization is done from top to bottom. The initializer of a package is the union of all global variable static initializers (including static class variables) and all init blocks.
  2. If package A imports package B and the import is not a initlater import, then the initializer of package B is run before A’s. Cyclic imports without initlater are not allowed.

If you get a Cyclic init dependency between packages, you have to manually define which package can be initialized later. This is done by adding the keyword initlater to the import of the package:

package A
import initlater B
import public initlater C
import D

Here only package D is guaranteed to be initialized before package A. Packages B and C are allowed to be initialized later.

Configuring Packages

Global variables and functions can be configured. Configuration is done via configuration packages. Each package has at most one configuration package and every configuration package configures exactly one package. The relation between a package and its configuration package is expressed via the following naming convention: For a package named Blub the configuration package must be named Blub_config.

Inside a configuration global variables can be annotated with the @config annotation. This has the effect that the variable with the same name in the original package will be overridden with the variable in the configuration package. In the original package, the variable should be annotated with @configurable to signal that it is safe to configure. Here is an example:

package Example
@configurable int x = 5

package Example_config
@config int x = 42

Configuring functions works basically the same:

package Example
@configurable public function math(int x, int y) returns int
    return x + y


package Example_config
@config public function math(int x, int y) returns int
    return x*y

Classes

Classes are easy, powerful and very helpful constructs. A class defines data and related functions working with this data. Take a look at this small example:

Caster dummyCaster = new Caster(200.0, 400.0)
dummyCaster.castFlameStrike(500.0, 30.0)
destroy dummyCaster

In this example we created a Caster named “dummyCaster” at the location(200, 400). Then we ordered dummyCaster to cast a flame strike at another position and finally we destroyed dummyCaster.

This example shows you how to create a new object (line 1), invoke a function on an object (line 2) and how to destroy an object (line 3). But how can you define a new object type like “Caster”? This is where classes come in. A class defines a new kind of object. A class defines variables and functions, which every instance of this class should understand. A class can also define how a new object is constructed (construct) and what should happen, when it is destroyed (ondestroy).

Defining a caster-class might look like this:

class Caster // opening the class-block. "Caster" is the name of the class
    unit u // class variables can be defined anywhere inside a class

    construct(real x, real y)
        u = CreateUnit(...)

    function castFlameStrike(real x, real y)
        UnitIssueOrder(u, ...)

    ondestroy
        KillUnit(u)

Constructors

WurstScript allows you to define your own constructors for each class. A constructor is a function to construct a new instance of a class. The constructor is called when creating the class via the new keyword and allows operations being done to the class-instance before returning it.

class Pair
    int a
    int b

    construct( int pA, int pB )
            a = pA
            b = pB

    function add() returns int
            return a + b


function test()
    Pair p = new Pair(2,4)
    int sum = p.add()
    print(sum)

In this example the constructor takes two integers a and b as parameters and sets the class variables to those. You can define more than one constructor as long as the parameters differ.

class Pair
    int a
    int b

    construct( int pA, int pB )
            a = pA
            b = pB

    construct( int pA, int pB, int pC )
            a = pA
            b = pB
        a += pC
        b += pC

function test()
    Pair p = new Pair(2,4)
    Pair p2 = new Pair(3,4,5)

In this example the class pair has two constructors - one taking 2 and the second one taking three parameters. Depending on parameter-type and -count Wurst automatically decides which constructor to take when using “new”.

This

The this keyword refers to the current instance of the class on which the function was called. This also allows us to name the parameters the same as the class variables. However it can be left out in classfunctions, as seen above.

class Pair
    int a
    int b

    // With the this keyword we can access the classmembers
    construct( int a, int b )
            this.a = a
            this.b = b

ondestroy

Each class can have one ondestroy block. This block is executed before the instance is destroyed. Ondestroy blocks are defined as previously shown

class UnitWrapper
    unit u

    ...

    ondestroy
        RemoveUnit(u)

Static Elements

You can declare variables and functions as static by writing the keyword “static” in front of the declaration. Static variables and functions belong to the class whereas other elements belong to instances of the class. So you can call static functions without having an instance of the class.

class Terrain
    static real someVal = 12.

    static int array someArr

    static function getZ( real x, real y ) returns real
        ...

function foo()
    real z = Terrain.getZ( 0, 0 ) // call with $Classname$.$StaticFunctionName$()
    real r = Terrain.someVal // Same with members
    real s = Terrain.someArr[0]

Dynamic, Sized Array-Members

Wurstscript supports sized arrays as classmembers by translating it to SIZE times arrays and then resolve the array in a get/set function via binary search.

Example Usage:

class Rectangle
    Point array[4] points

    function getPoint(int index)
        return points[index]

    function setPoint(int index, Point nP)
        points[index] = nP

Visibility Rules

By default class elements are visible everywhere. You can add the modifiers private or protected in front of a variable or function definition to restrict its visibility. Private elements can only be seen from within the class. Protected elements can be seen within the enclosing package and in subclasses.

Subclassing

A class can extend an other class. The class then inherits all the non-private functions and variables from that class and can be used anywhere where the super class can be used.

Constructors of the class have to specify how the super class should be constructed. This is done using a super call, which defines the arguments for the super constructor. There can not be any statement before this call.

If a constructor does not provide a super call, the compiler tries to insert a super call with no arguments.

Functions inherited from super classes can be overridden in the subclass. Such functions have to be annotated with override.

Example

class Missile
    construct(string fx, real x, real y)
        // ...

    function onCollide(unit u)
        skip

    // ...

// a fireball missile is a special kind of missile
class FireballMissile extends Missile
    // we construct it using a fireball fx
    construct(real x, real y)
        super("Abilities\\Weapons\\RedDragonBreath\\RedDragonMissile.mdl", x, y)
    // a fireball does some special things when it collides
    // with a unit, so we override the onCollide method
    override function onCollide(unit u)
        // create a big explosion here ;)
        //...

Typecasting

In order to typecast, you use the castTo operator

You need typecasting for several reasons.

One being to save class instances and for example attaching them onto a timer, like done in TimerUtils.wurst This process can also be reversed (casting from int to a classtype).

class Test
    int val

init
    Test t = new Test()
    int i = t castTo int

Typecasting is sometimes useful when using subtyping. If you have an object of static type A but know that the dynamic type of the object is B, you can cast the object to B to change the static type.

class A

class B extends A
    function special()
        ...

init
    A a = new B()
    // we know that a is actually of type B, so we can safely cast it to B:
    B b = a castTo B
    // now we can call functions from class B
    b.special()

Note: You should avoid castTo whenever possible. Casts are not checked at runtime so they can go horribly wrong. Casts can be avoided by using high level libraries and object oriented programming.

Dynamic dispatch

Wurst features dynamic dispatching when accessing classinstances. What this means is simple: If you have a classinstance of a subclass B, casted into a variable of the superclass A, calling a function with that reference will automatically call the overridden function from the original type. It is easier to understand with an example:

Example 1

class A
    function printOut()
        print("I'm A")

class B extends A
    override function printOut()
        print("I'm B")

init
    A a = new B()
    a.printOut()
    // this will print "I'm B", even though it's a type A variable

Example 2

class A
    string name

    construct(string name)
        this.name = name

    function printName()
        print("Instance of A named: " + name )


class B extends A

    construct(string name)
        super(name)

    override function printName()
        print("Instance of B named: " + name )

init
    A a = new B("first") // This works because B extends A
    a.printName() // This will print "Instance of B named: first", because a is an Instance of B.

This is especially useful when iterating through ClassInstances of the same supertype, meaning you don’t have to cast the instance to it’s proper subtype.

super calls

When you override a method you can still call to the original implementation by using the super keyword. This is useful since subclasses often just add some functionality to its base classes.

As an example consider a fireball spell for which we want to create a more powerful version:

class FireBall
    ...
    function hitUnit(unit u, real damage)
        ...

class PowerFireBall extends FireBall
    ...
    // here we override the original behavior of the hitUnit function
    override function hitUnit(unit u, real damage)
        // first we create a big explosion effect
        createSomeBigExplosionEffect(u)
        // then we call the original hitUnit function but with doubled damage
        super.hitUnit(u, damage*2)

The super keyword also should be used when defining custom constructors. The Constructor of the superclass has to be called in the constructor of the subclass. When no super constructor call is given, the compiler will try to call a constructor with no arguments. If no such constructor exists, then you will get an error like: “Incorrect call to super constructor: Missing parameters.”

class Missile
    ...
    construct(vec2 position)
        ...

class TimedMissile extends Missile
    ...
    construct(vec2 position, real duration)
        // Here the constructor of the superclass is called
        // The super statement has to be the first statement in the constructor of the subclass
        super(position)
        ...

instanceof

In Wurst you can check the type of a classinstance with the instanceof keyword.

Note: You should avoid instanceof checks whenever possible and prefer object oriented programming.

The instanceof expression “o instanceof C” returns true, if object o is a subtype of type C.

It is not possible to use instanceof with types from different type partitions, as instanceof is based on typeIds (see following chapter). This is also the reason why you cannot use instanceof to check the type of an integer.

The compiler will try to reject instanceof expressions, which will always yield true or always yield false.

###Example

class A

class B extends A

init
    A a = new B()

    if a instanceof B
        print("It's a B")

typeId

NOTE: typeIds are an experimental feature. Try to avoid using them.

Sometimes it is necessary to check the exact type of an object. To do this you can write “a.typeId” if a is an object or “A.typeId” if A is a class.

    // check if a is of class A
    if a.typeId == A.typeId
        print("It's an A")

The typeId is an integer which is unique for each class inside a type partition.

type partitions

The type partition of A is the smallest set containing A such that for all classes or interfaces T1 and T2 it holds that: If T1 is in the set and T1 is a subtype or supertype of T2, then T2 is also in the set. Or expressed differently: A and B are in the same partition if and only if their type hierarchies are somehow connected.

Abstract Classes

An abstract class is a class, that is declared abstract — it may or may not include abstract functions. You cannot create an instance of an abstract class, but you can create subclasses for it which are not abstract.

An abstract function is declared with the keyword ‘abstract’ and by leaving out an implementation.

abstract function onHit()

Abstract classes are similar to interfaces, but they can have own, implemented functions and variables like normal classes.

The advantage of an abstract class is, that you can reference and call the abstract functions inside the abstract class, without having a vlid implementation.

Take Collision-Responses as example. You have several Bodies in your map, and you want each of them to behave differently. You could do this by centralizing the updating function, or by using abstract classes like so:

abstract class CollidableObject

    abstract function onHit()

    function checkCollision(CollidableObject o)
        if this.inRange(o)
          onHit()
          o.onHit()

class Ball extends CollidableObject

    override function onHit()
        print("I'm a ball")

class Rect extends CollidableObject

    override function onHit()
        print("I'm a Rect")

Because CollidableObject requires it’s subclasses to implement the function onHit; it can be called within the abstract class and without knowing it’s type.

If a subclass of an abstract class does not implement all abstract functions from its superclass, it has to be abstract, too.

Interfaces

interface Listener
    function onClick()

    function onAttack( real x, real y ) returns boolean


class ExpertListener implements Listener
    function onClick()
        print("clicked")


    function onAttack( real x, real y ) returns boolean
        AddSpecialEffect(EFFECT_STRING, x ,y)

An interface is a group of related functions with empty bodies. If a class implements a certain interface it has to replace all the empty functions from the interface. A class can implement multiple interfaces, meaning that it complies to more interface requirements at the same time.

class ExampleClass implements Interface1, Interface2, ...
        // all functions from the implemented interfaces

With interfaces (and modules if implicit) you can now up- and downcast any Class that implements it. This is especially useful for saving all instances from classes that inherit 1 interface in only 1 List/Array

Defender methods

NOTE: Defender methods are a very experimental feature and will probably be removed from the language.

An interface can have functions with an implementation. This implementation is used, when a class implementing the interface does not provide an implementation of the method itself. Usually this is not needed but in some cases it might be necessary in order to evolve an interface without breaking its implementing classes.

Generics

Generics make it possible to abstract from specific types and only program with placeholders for types. This is especially useful for container types (e.g. Lists) where we do not want to code a ListOfA, ListOfB, ListOfC for every class A, B, C which we need a list for. Think of it as creating a general List with all it’s functionality, but for an unknown type, that gets defined when creating the instance.

With generics we can instead write only one implementation for lists and use it with all types. Generic type parameter and arguments are written in angled bracket s (< an d >) after the identifier.

// a generic interface for Sets with type parameter T
interface Set<T>
    // adds an element to the set
    function add(T t)

    // removes an element from the set
    function remove(T t)

    // returns the number of elements in the set
    function size() returns int

    // checks whether a certain element is in the set
    function contains(T t) returns boolean

class SetImpl<T> implements Set<T>
    // [...] implementation of the interface

If we have a class defined like this, we can then use it with a concrete type (e.g. Missile):

// create a set of missiles
Set<Missile> missiles = new SetImpl<Missile>();
// add a missile m
missiles.add(m);

Generic parameters in Wurst can be bound to integers, class types and interface types directly. In order to bind generic parameters to primitive-, handle- and tuple types you have to provide the functions

function [TYPE]ToIndex([TYPE] t) returns int

function [TYPE]FromIndex(int index) returns [TYPE]
    return ...

The typecasting functions for primitive- and handle types are provided in Typecasting.wurst using the fogstate bug.

function unitToIndex(unit u) returns int
    return u.getHandleId()

function unitFromIndex(int index) returns unit
    data.saveFogState(0,ConvertFogState(index))
    return data.loadUnit(0)

Generic Functions

Functions can use generic types. The type parameter is written after the name of the function. In the following example the function forall tests if a predicate is true for all elements in a list. The function has to be generic, because it has to work on all kinds of lists.

function forall<T>(LinkedList<T> l, LinkedListPredicate<T> pred) returns boolean
    for x in l
        if not pred.isTrueFor(x)
            return false
    return true

// usage:
    LinkedList<int> l = ...
    // check if all elements in the list are even
    if forall<int>(l, (int x) -> x mod 2 == 0)
        print("true")

When calling a generic function, the type arguments can be omitted if they can be inferred from the arguments to the function:

...
if forall(l, (int x) -> x mod 2 == 0)
    ...

Extension functions can also be generic, as shown by the following example:

function LinkedList<T>.forall<T>(LinkedListPredicate<T> pred) returns boolean
    for x in this
        if not pred.isTrueFor(x)
            return false
    return true

// usage:
    ...
    if l.forall((int x) -> x mod 2 == 0)
        ...

Modules

NOTE: It is likely that the concept of modules will change in later versions of the language. If you want to be on the safe side you should not create modules which use other modules themselves. Plain, flat modules will most likely stay in the language.

A module is a small package which provides some functionality for classes. Classes can use modules to inherit the functionality of the module.

You can use the functions from the used module as if they were declared in the class. You can also override functions defined in a module to adjust its behavior.

If you know object oriented languages like Java or C#: Modules are like abstract classes and using a module is like inheriting from an abstract class but without the sub-typing. (WurstScript takes a different approach to enable polymorphism, but this is not implemented yet)

Example 1

In this example we just have a class which uses a module A. The resulting program behaves as if the code from module A would be pasted into Class C.

module A
    public function foo()
        ...


class C
    use A

Example 2

Modules are more than just a mechanism to copy code. Classes and modules can override functions defined in used modules:

// a module to store an integer variable
module IntContainer
    int x

    public function getX() returns int
        return int

    public function setX(int x)
        this.x = x

// a container which only stores positive ints
module PositiveIntContainer
    use IntContainer

    // override the setter to only store positive integers
    override function setX(int x)
        if x >= 0
            IntContainer.setX(x)

Visibility & Usage Rules

Overriding Functions

Abstract Functions

Modules can declare abstract functions: Functions without a given implementation. Abstract functions have to be implemented by the underlying classes.

Thistype

You can use thistype inside a module to refer to the type of the class which uses the module. This can be useful if you need to cast the class to an integer and back.

Enums

In Wurst, Enums can be used to set up collections of named (int) constants. These Constants can then be accessed via the Enum’s name:

enum State
    FLYING
    GROUND
    WATER

init
    State s = State.GROUND

You can also use enums inside of classes

class C
    State currentState

    construct( State state )
        currentState = state

To check the current value of an enum, you can use the switch statement. Note that all Enummembers have to be checked (or a defaut).

switch currentState
    case State.FLYING
        print("flying")
    case State.GROUND
        print("ground")
    case State.WATER
        print("water")

In switch statements and variable assignments the qualifier can be ommited so you can also write:

switch currentState
    case FLYING
        print("flying")
    case GROUND
        print("ground")
    case WATER
        print("water")

Tuple Types

With tuple types you can group several variables into one bundle. This can be used to return more than one value from a function, to create custom types and of course for better readability.

Note that tuples are not like classes. There are some important differences: - You do not destroy tuple values. - When you assign a tuple to a different variable or pass it to a function you create a copy of the tuple. Changes to this copy will not affect the original tuple. - Tuple types cannot be bound to type parameters, so you can not have a List{vec} if vec is a tuple type. - As tuple types are not created on the heap you have no performance overhead compared to using single variables.

    // Example 1:

    // define a tuple
    tuple vec(real x, real y, real z)

    init
        // create a new tuple value
        vec v = vec(1,2,3)
        // change parts of the tuple
        v.x = 4
        // create a copy of v and call it u
        vec u = v
        u.y = 5
        if v.x == 4 and v.y == 2 and u.y == 5
            testSuccess()


    // Example 2:

    tuple pair(real x, real y)
    init
        pair p = pair(1,2)
        // swap the values of p.x and p.y
        p = pair(p.y, p.x)
        if p.x == 2 and p.y == 1
            testSuccess()

Because tuples don’t have any functions themselves, you can add extension functions to an existing tuple type in order to achieve class-like functionality. Remember that you can’t modify the value of a tuple in it’s extension function - so you have to return a new tuple every time if you wan’t to change something. Look at the Vector package in the Standard Library for some tuple usage examples. (Math/Vectors.wurst)

Extension Functions

Extension functions enable you to “add” functions to existing types without creating a new derived type, recompiling, or otherwise modifying the original type. Extension functions are a special kind of static function, but they are called as if they were instance functions of the extended type.

Declaration

public function TYPE.EXTFUNCTIONNAME(PARAMETERS) returns ...
    BODY
    // The keyword "this" inside the body refers to the instance of the extended type

Examples

// Declaration
public function unit.getX() returns real
    return GetUnitX(this)

// Works with any type
public function real.half() returns real
    return this/2

// Parameters
public function int.add( int value )
    return this + value

// Usage
unit u = CreateUnit(...)
...
print( u.getX().half() )

// Also classes, e.g. setter and getter for private vars
public function BlubClass.getPrivateMember() returns real
    return this.privateMember

// And tuples as mentioned above
public function vec2.lengthSquared returns real
    return this.x*this.x+this.y*this.y

Lambda expressions and Closures

A lambda expression (also called anonymous function) is a lightweight way to provide an implementation of a functional interface or abstract class (To keep the text simple, the following explanations are all referring to interfaces, but abstract classes can be used in the same way).

A functional interface is an interface which has only one method. Here is an example:

// the functional interface:
interface Predicate<T>
    function isTrueFor(T t) returns bool

// a simple implementation
class IsEven implements Predicate<int>
    function isTrueFor(int x) returns bool
        return x mod 2 == 0

// and then we can use it like so:
let x = 3
Predicate<int> pred = new IsEven()
if pred.isTrueFor(x)
    print("x is even")
else
    print("x is odd")
destroy pred

When using lambda expressions, it is not necessary to define a new class implementing the functional interface. Instead the only function of the functional interface can be implemented where it is used, like so:

let x = 3
// Predicate is defined here:
Predicate<int> pred = (int x) -> x mod 2 == 0
if pred.isTrueFor(x)
    print("x is even")
else
    print("x is odd")
destroy pred

The important part is:

(int x) -> x mod 2 == 0

This is a lambda expression. It consists of two parts and an arrow symbol -> between the two parts. The left hand side of the arrow is a list of formal parameters, as you know them from function definitions. On the right hand side there is an expression, which is the implementation. The implementation consists only of a single expressions, because lambda expressions are typically small and used in one line. But if one expression is not enough there is the begin-end expression.

Remember that, because closures are just like normal objects, you also have to destroy them like normal objects. And you can do all the other stuff you can do with other objects like putting them in a list or into a table.

begin-end expression

Sometimes one expression is not enough for a closure. In this case, the begin-end expression can be used. It allows to have statements inside an expression. The begin keyword has to be followed by a newline and an increase in indentation. The rule that newlines are ignored inside parenthesis is ignored for the begin-end expression, so that it is possible to have multiple lines of statements within:

doLater(10.0, () -> begin
    KillUnit(u)
    createNiceExplosion()
    doMoreStuff()
end)

It is also possible to have a return statement inside a begin-end expression but only the very last statement can be a return.

Capturing of Variables

The really cool feature with lambda expressions is, that they create a closure. This means that they can close over local variables outside their scope and capture them. Here is a very simple example:

let min = 10
let max = 50
// remove all elements not between min and max:
myList.removeWhen((int x) ->  x < min or x > max)

In this example the lambda expression captured the local variables min and max.

It is important to know, that variables are captured by value. When a closure is created the value is copied into the closure and the closure only works on that copy. The variable can still be changed in the environment or in the closure, but this will have no effect on the respective other copy of the variable.

This can be observed when a variable is changed after the closure is created:

var s = "Hello!"
CallbackFunc f = () -> begin
    print(s)
    s = s + "!"
end
s = "Bye!"
f.run()  // will print "Hello!"
f.run()  // will print "Hello!!"
print(s) // will print "Bye!"

Behind the scenes

The compiler will just create a new class for every lambda expression in your code. This class implements the interface which is given by the context in which the lambda expression is used. The generated class has fields for all local variables which are captured. Whenever the lambda expression is evaluated, a new object of the class is created and the fields are set.

So the “Hello!” example above is roughly equivalent to the following code:

// (the interface was not shown in the above code, but it is the same):
interface CallbackFunc
    function run()

// compiler creates this closure class implementing the interface:
class Closure implements CallbackFunc
    // a field for each captured variable:
    string s

    function run()
        // body of the lambda expression == body of the function
        print(s)
        s = s + "!"

var s = "Hello!"
CallbackFunc f = new Closure()
// captured fields are set
f.s = s
s = "Bye!"
f.run()  // will print "Hello!"
f.run()  // will print "Hello!!"
print(s) // will print "Bye!"

Function types

A lambda expression has a special type which captures the type of the parameter and the return type. This type is called a function type. Here are some examples with their type:

() -> 1
    // type: () -> integer

(real r) -> 2*r
    // type: (real) -> real

(int x, string s) -> s + I2S(x)
    // type: (int,string) -> string

While function types are part of the type system, Wurst has no way to write down a function type. There are no variables of type “(int,string) -> string”. Because of this, a lambda expression can only be used in places where a concrete interface or class type is known. This can be an assignment where the type of the variable is given.

Predicate<int> pred = (int x) -> x mod 2 == 0

However it is not possible to use lambda expressions if the type of the variable is only inferred:

// will not compile, error "Could not get super class for closure"
let pred = (int x) -> x mod 2 == 0

Lambda expressions as code-type

Lambda expressions can also be used where an expression of type code is expected. The prerequisite for this is, that the lambda expression does not have any parameters and does not capture any variables. For example the following code is not valid, because the local variable x is captured.

let t = getTimer()
let x = 3
t.start(3.0, () -> doSomething(x)) // error: Cannot capture local variable 'x'

This can be fixed by attaching the data to the timer manually:

let t = getTimer()
let x = 3
t.setData(x)
t.start(3.0, () -> doSomething(GetExpiredTimer().getData()))

If a lambda expression is used as code, there is no new object created and thus there is no object which has to be destroyed. The lambda expression will just be translated to a normal Jass function, so there is no performance overhead when using lambda expressions in this way.

Advanced Concepts

Function Overloading

Function overloading allows you to have several functions with the same name. The compiler will then decide which function to call based on the static type of the arguments.

Wurst uses a very simple form of overloading. If there is exactly one function in scope which is feasible for the given arguments, then this function will be used. If there is more than one feasible function the compiler will give an error.

Note that this is different to many other languages like Java, where the function with the most specific feasible type is chosen instead of giving an error.

function unit.setPosition(vec2 pos)
    ...

function unit.setPosition(vec3 pos)
    ...

function real.add(real r)
    ...

function real.add(real r1, real r2)
    ...

This works because the parameters are of different types or have a different amount of paramaters and the correct function can therefore be determined at compiletime.

function real.add(real r1)
    ...

function real.add(real r1) returns real

This does not work because only the returntype is different and the correct function cannot be determined.

class A
class B extends A

function foo(A c)
    ...

function foo(B c)
    ...

// somewhere else:
    foo(new B)

This does not work either, because B is a subtype of A. If you would call the function foo with a value of type B, both functions would be viable. Other languages just take the “most specific type” but Wurst does not allow this. If A and B are incomparable types, the overloading is allowed.

Operator Overloading

Operator Overloading allows you to change the behavior of internal operators +, -, * and / for custom arguments. A quick example from the standard library (Vectors.wurst):

// Defining the "+" operator for the tupletype vec3
public function vec3.op_plus( vec3 v ) returns vec3
    return vec3(this.x + v.x, this.y + v.y, this.z + v.z)

// Usage example
vec3 a = vec3(1.,1.,1.)
vec3 b = vec3(1.,1.,1.)
// Without Operator Overloading (the add function was replaced by it)
vec3 c = a.add( b )
// With operator Overloading
vec3 c = a + b

You can overload operators for existing types via Extension-Functions or via class-functions for the specific classtype. In order to define an overloading function it has to be named as following:

+  "op_plus"
-  "op_minus"
*  "op_mult"
/  "op_divReal"

Object Editing

Creating Object-Editor Objects via Wurst code.

NOTE: Object Editing hardly works at the moment, so you should only use it for fun but not for profit. Do not use it for a real project yet!

Compiletime Functions

Compiletime Functions are functions, that are executed when compiling your script/map. They mainly offer the possibility to create Object-Editor Objects via code.

A compiletime function is just a normal Wurst function annotated with @compiletime.

@compiltetime function foo()

Compiltetime functions have no parameters and no return value.

In order to run compiletime functions you have to enable the checkbox in the Wurstpack Menu. When you use compiletime functions to generate objects, Wurst will generate the object files next to your map and you can import them into your map using the object editors normal import function. Compared to ObjectMerger this has the advantage, that you can directly see your new objects in the object editor. You can also enable an option to directly inject the objects into the map file, though the changes will not be visible in the object-editor directly.

You can use the same code during runtime and compiletime. The special constant compiletime can be used to distinguish the two. The constant is true when the function was called at compiletime and false otherwise. The following example shows how this could be useful:

init
    doInit()

@compiletime
function doInit()
    for i = 1 to 100
        if compiletime
            // create item object
        else
            // place item on map

Object Editing Natives

The standard library provides some functions to edit objects in compiletime functions. You can find the corresponding natives and higher level libraries in the objediting folder of the standard library.

The package ObjEditingNatives contains natives to create and manipulate objects. If you are familiar with the object format of Wc3 and know similar tools like Lua Object Generation or the ObjectMerger from JNGP, you should have no problems in using them. If you run Wurst with compiletime functions enabled, it will generate the object creation code for all the objects in your map. This code is saved in files named similar to “WurstExportedObjects_w3a.wurst.txt” and can be found right next to your map file. You can use this code as a starting point if you want to use the natives.

Wurst also provides a higher level of abstraction. For example the package AbilityObjEditing provides many classes for the different base abilities of Wc3 with readable method names. That way you do not have to look up the IDs.

The following example creates a new spell based on “Thunder Bolt”. The created spell has the ID “A005”. In the next line the name of the spell is changed to “Test Spell”. Level specific properties are changed inside the loop.

package Objects
import AbilityObjEditing

@compiletime function myThunderBolt()
    // create new spell based on thunder bolt from mountain king
    let a = new AbilityDefinitionMountainKingThunderBolt("A005")
    // change the name
    a.setName("Wurst Bolt")
    a.setTooltipLearn("The Wurstinator throws a Salami at the target.")
    for i=1 to 3
        // 400 damage, increase by 100 every level
        a.setDamage(i, 400. + i*100)
        // 10 seconds cooldown
        a.setCooldown(i, 10.)
        // 0 mana, because no magic is needed to master Wurst
        a.setManaCost(i, 0)
        // ... and so on

NOTE There are also packages for other object types, but those packages are even more WIP.

Automated Unit Tests

You can add the annotation @test to a function. Then when you type “tests” into the Wurst Console all functions annotated with @test are executed.

You have to import the Wurstunit package to use functions like assertEquals.

Example:

    package Test
    import Wurstunit

    @test public function a()
        12 .assertEquals (3*4)

    @test public function b()
        12 .assertEquals (5+8)

If you run this, you get the following output:

    > 1+1
    res = 2     // integer
    > tests
    1 tests OK, 1 tests failed
    function b (Test.wurst, line 8)
        test failed: expected 13 but was 12
        at stmtreturn  (.../lib/primitives/Integer.wurst, line 25)
    >

The first line is just to check whether the console is working ;)

You can search the standard library for “@test” to get some more examples.

Other Stuff

Stacktraces

You can enable stacktraces in the the menu of WurstPack or with the commandline switch -stacktraces. Each error will then be displayed with a stacktrace showing the line number and filename of each function-call leading to the error. Errors must be generated with the magic function function error(string msg).

Standard Library

Wurst comes with a library of some useful standard functions You can find the generated HotDoc pages here: The Wurstscript Standardlibrary However, the best way to learn about the library is still to look at the source code.

Wurst Style

In this section we describe some style guidelines which you should follow when programming in Wurst.

Rule 1: Write for readability

You should always write your code so that it can be read easily. Ideally your code should be readable like normal English text. Use suitable names for functions and variables to make your code sound like normal text.

Example:

// not so readable:
if GetUnitState(h, UNIT_STATE_LIFE) <= 0.405
    let t = NewTimer()
    t.setData(...)
    t.start(...)
    ...

// better:
if hero.isDead()
    hero.reviveAt(town, REVIVE_TIME)

// that timer stuff is in a different small function

Rule 1.1 Document your Code

### Rule 1.2 Keep functions short ### Rule 1.3 The Golden Path

Rule 2: Write checkable code

Your code should be checkable by the compiler. The Wurst compiler provides you with some powerful typechecking tools. Use them wisely to reduce the mistakes you can do in your program. You might also want to watch this awesome talk by Yaron Minsky who summarizes it pretty nicely: “make invalid state unrepresentable”. Of course Wurst is not as sophisticated as ML but there are still a lot of things you can do.

Rule 3: DRY: Don’t repeat yourself

When you find the same lines of code at several places of your project, try to put the common code into a function, class or module.

Rule 4: KISS: Keep it simple, stupid!

Always try to choose the most simple solution to your problem. Avoid premature optimization.

Rule 5: Use Object oriented programming

Avoid instanceof

Using instanceof is usually a sign for bad use of object orientation. Often instanceof checks can be replaced by using dynamic dispatch. Let’s look at an example:

if shape instanceof Circle
    Circle c = shape castTo Circle
    area = bj_PI * c.radius*c.radius
else if shape instanceof Rect
    Rect r = shape castTo Rect
    area = r.width * r.height

It would be better to have one area function in Shape and then implement it for Circle and Rect. That way you can just write:

area = shape.area()

The right area method will then be automatically selected based on the type of shape.

vJass vs Wurst

If you plan to convert from using vJass to Wurst, it will probably be helpful to read this segment. Specific vJass syntax and features are directly compared to the equivalent WurstCode.

You might be wondering why Wurst is missing so many vJass features. The answer is simple: Wurst utilizes several better/smarter constructs to achieve similar functionality like in vJass and beyond.

Textmacros and member-arrays aside, most vJass features either got useless due to Wurst or got replaced.

Feature table

Feature vJass Wurst
code organization libraries packages
nested scopes yes -
classes structs classes
modules yes yes
function interfaces yes no
interfaces yes yes
textmacros yes -
keyword yes -
struct onInit yes -
stub methods yes overriding and abstract classes
Dynamic arrays yes -
Array members yes -
Delegate yes -
Array structs yes -
Keys yes -
Sized arrays yes -
2D arrays yes -
Colon yes -
Operator overloading for [] for +,-,*,/
hook yes -
inject yes -
Loading from SLK yes -
Script optimization only inlining yes
Function inlining limited yes
External tools yes -
Typechecker limited yes
Function sorting - yes
Extension functions - yes
generics - yes
tuple types - yes
closures - yes
compiletimefunctions - yes

Globals

Instead of a globals block, every variable outside a block inside a package is considered a global in that package.

*vJass:*

globals
    integer foo
endglobals

*Wurst:*

package test
    ...
    int foo
endpackage

Functions

*vJass:*

function foo takes nothing returns nothing

endfunction

function bar takes real r returns real

endfunction

*Wurst:*

function foo()
    ...

function bar(real r) returns real
    ...

Locals

locals can be declared and initialized anywhere inside a function.

*vJass:*

function foo takes nothing returns nothing
    local integer i = 0
    local integer j
    i = i + 1
    j = i
endfunction

*Wurst:*

function foo()
    int i = 0
    i++
    int j = i

Librarys/Scopes

Name-spaces/code organisation in wurst is handled by packages. All code in Wurst has to be inside a package.

*vJass:*

library foo

endlibrary

*Wurst:*

package foo

endpackage

importing

*vJass:*

library foo requires/needs/uses bar, blub, stuff

endlibrary

*Wurst:*

package foo
    import bar
    import blub
    import stuff

endpackage

init-blocks

*vJass:*

library foo initializer init

    private function init takes nothing returns nothing
        ...
    endfunction

endlibrary

*Wurst:*

package foo

    init
        ...
endpackage

Structs / Classes

*vJass:*

struct BlubStruct
    static method create takes nothing returns BlubStruct
        local thistype this = thistype.allocate()
        // constructor code
        return this
    endmethod

    method add takes integer a, integer b returns integer
        return a + b
    endmethod

    static method sub takes integer a, integer b returns integer
        return a - b
    endmethod

    method onDestroy takes nothing returns nothing
        // destructor code
    endmethod
endstruct

// create instance:
local BlubStruct blub = BlubStruct.create()
// call method
call blub.add( 1, 1 )
// call static method
call BlubStruct.sub( 2, 1 )
// destroy instance
call blub.destroy

*Wurst:*

class BlubClass
    construct()
        // constructor code

    function add(int a, int b) returns int
        return a + b

    static function sub(int a, int b) returns int
        return a - b

    ondestroy
        // destructor code

// create instance:
BlubClass blub = new BlubClass()
// call function
blub.add( 1, 1 )
// call static function
BlubClass.sub( 2, 1 )
// destroy instance
destroy blub

Modules

*vJass:*

module Bla
    ...
endmodule

struct C
    implement Bla
    ...
endstruct

*Wurst:*

module Bla
    ...


class C
    use Bla
    ...

Wurst-modules are also more powerful than vJass modules. They allow overriding and can declare abstract functions.

Eclipse Plugin

Installation: You can find information on how to install the plugin at the Wurst Homepage. Installation

Usage

  1. Go to File-New Project and select New Wurst Project. Choose a name and press Finish.
  2. Add a new file named wurst.dependencies. Each line in this file points to a folder on which your project depends. As every project depends on the standard library you have to add the path to Wurstpack/wurstscript/lib in your Wurstpack folder.
  3. Create a new file with the .wurst filename extension and an arbitrary name. You can now start writing your code.

Useful Shortcuts

The Wurst-Console

The Wurst-Console provides an Read-Eval-Print-Loop (REPL) for Wurst. The Console can be activated via Window -> Show View -> Console. The Console is always attached to the currently opened document. You can enter expressions into the console and the expression will be evaluated in the context of the currently opened document.

Wurst Console

By default the result of the expression will be stored in the variable res, but you can also use a let statement to store the result in a different variable.

The console supports other useful commands, which are not (yet) accessible. A list of commands can be displayed by typing help into the console. The most important commands are probably reset and tests. reset will reset the state of the console. This is sometimes necessary after switching to other files, or if the console is in an invalid state because of bugs. tests will run all unit tests (functions annotated with @test) in the current project and show the test results.

Optimizer

The Wurstcompiler has a set of build-in scriptoptimizing tools which will, when enabled, optimize the generated Jass code in various ways. Jass optimization got very important to provide playable framerates when using very enhanced and complex systems. On the one hand the optimizer cleans the code, making it smaller in size and removing useless stuff in order to reduce RAM-usage. On the other hand it also offers some optimizations to increase the speed of execution and performance of the code.

Cleaning

Stuff that is being removed, changed or not even printed

Name compression

Smaller names execute faster and take less space, so all names of functions and variables are compressed to the shortest name possible.

Inlining

Inlining is not an easy task, but brings great performance boosts to systems which use many different functions. It also makes coding easier and more readable, because you don’t have to care about the performance loss when splitting stuff into too many functions. Also blizzard.j functions, such as BJs and Swaps, can get inlined.

In the current implementation, a function can be inlined when it has only one exit-point. This is the case, when there is no return statement or when the only return statement is at the end of a function.

Whether a function actually gets inlined depends on some heuristics in the compiler. The heuristic tries to balance execution speed and size of the mapscript, as inlining usually makes the code longer but faster. A function is more likely to get inlined, when it is short. A function is less likely to get inlined, when it is called in many different places. It is best to not rely on more guarantees about the inliner, as the heuristics are changed from time to time.

Global variables that have a constant value get inlined as well as constant locals.

Constant Folding and Constant propagation

Expressions containing only constants are calculated at compiletime. Ifs with constant conditions are removed. Both mechanics work together to remove unneeded and unreachable code.

Errors and Warnings

Wurst provides some errors and warnings to help finding bugs early in the development process. In this chapter some of these errors and warnings are explained, as well as some ways to fix them.

Warnings

Some warnings are just there to ensure some common coding standards in Wurst: