wren

a classy little scripting language

Expressions

Wren's syntax is based on C so if you're familiar with that (or any of the plethora of other languages based on it) you should be right at home. Since Wren is heavily object-oriented, you'll notice that most of the different expression forms are just different ways of invoking methods.

Literals #

Literals produce objects of built-in types. The primitive value types—numbers, strings and friends—have literal forms as do the built in collections: lists and maps.

Functions do not have standalone a literal form. Instead, they are created by passing a block argument to a method.

Identifiers #

Names in expressions come in a few flavors. A name that starts with an underscore denotes a field, a piece of data stored in an instance of a class. All other names refer to variables.

Method calls #

Wren is object-oriented, so most code consists of method calls. Most of them look like so:

System.print("hello")
items.add("another")
items.insert(1, "value")

You have a receiver expression followed by a ., then a name and an argument list in parentheses. Arguments are separated by commas. Methods that do not take any arguments can omit the ():

text.length

These are special "getters" or "accessors" in other languages. In Wren, they're just method calls. You can also define methods that take an empty argument list:

list.clear()

An empty argument list is not the same as omitting the parentheses completely. Wren lets you overload methods by their call signature. This mainly means arity—number of parameters—but also distinguishes between "empty parentheses" and "no parentheses at all".

You can have a class that defines both foo and foo() as separate methods. Think of it like the parentheses and commas between arguments are part of the method's name.

If the last (or only) argument to a method call is a function, it may be passed as a block argument:

blondie.callMeAt(867, 5309) {
  System.print("This is the body!")
}

Semantically, all method calls work like so:

  1. Evaluate the receiver and arguments.
  2. Look up the method on the receiver's class.
  3. Invoke it, passing in the arguments.

This #

The special this keyword works sort of like a variable, but has special behavior. It always refers to the instance whose method is currently being executed. This lets you invoke methods on "yourself".

It's an error to refer to this outside of a method. However, it's perfectly fine to use it inside a function contained in a method. When you do, this still refers to the instance whose method is being called.

This is unlike Lua and JavaScript which can "forget" this when you create a callback inside a method. Wren does what you want here and retains the reference to the original object. (In technical terms, a function's closure includes this.)

Super #

Sometimes you want to invoke a method on yourself, but only methods defined in one of your superclasses. You typically do this in an overridden method when you want to access the original method being overridden.

To do that, you can use the special super keyword as the receiver in a method call:

class Base {
  method {
    System.print("base method")
  }
}

class Derived is Base {
  method {
    super.method // Prints "base method".
  }
}

You can also use super without a method name inside a constructor to invoke a base class constructor:

class Base {
  this new(arg) {
    System.print("base constructor got " + arg)
  }
}

class Derived is Base {
  this new() {
    super("value") // Prints "base constructor got value".
  }
}

TODO: constructors

Operators #

Wren has most of the same operators you know and love with the same precedence and associativity. Wren has three prefix operators:

! ~ -

They are just method calls on their operand without any other arguments. An expression like !possible means "call the ! method on possible".

We have a few other operators to play with. The remaining ones are infix—they have operators on either side. They are:

== != < > <= >= .. ... | & + - * / %

Like prefix operators, they are all funny ways of writing method calls. The left operand is the receiver, and the right operand gets passed to it. So a + b is semantically interpreted as "invoke the + method on a, passing it b".

Most of these are probably familiar already. The .. and ... operators are "range" operators. The number type implements those to create a range object, but they are just regular operators.

Since operators are just method calls, this means Wren supports "operator overloading" (though "operator over-riding" is more accurate). This can be really useful when the operator is natural for what a class represents, but can lead to mind-crushingly unreadable code when used recklessly. There's a reason punctuation represents profanity in comic strips.

Assignment #

The = operator is used to assign or store a value somewhere. The right-hand side can be any expression. If the left-hand side is an identifier, then the value of the right operand is stored in the referenced variable or field.

The left-hand side may also be a method call, like:

point.x = 123

In this case, the entire expression is a single "setter" method call. The above example invokes the x= setter on the point object, and passing in 123. Sort of like point.x=(123).

Since these are just regular method calls, you can define whatever setters you like in your classes. However, you cannot change the behavior of simple assignment. If the left-hand side is a variable name or field, an assignment expression will always just store the value there.

Subscript operators #

Most languages use square brackets ([]) for working with collection-like objects. For example:

list[0]    // Gets the first item in a list.
map["key"] // Gets the value associated with "key".

You know the refrain by now. In Wren, these are just method calls that a class may define. Subscript operators may take multiple arguments, which is useful for things like multi-dimensional arrays:

matrix[3, 5]

Subscripts may also be used on the left-hand side of an assignment:

list[0] = "item"
map["key"] = "value"

Again, these are just method calls. The last example is equivalent to invoking the []= method on map, passing in "key" and "value".

Logical operators #

The && and || operators are not like the other infix operators. They work more like control flow structures than operators because they conditionally execute some code—they short-circuit. Depending on the value of the left-hand side, the right-hand operand expression may or may not be evaluated. Because of this, they cannot be overloaded and their behavior is fixed.

A && ("logical and") expression evaluates the left-hand argument. If it's false, it returns that value. Otherwise it evaluates and returns the right-hand argument.

System.print(false && 1)  // false
System.print(1 && 2)      // 2

An || ("logical or") expression is reversed. If the left-hand argument is true, it's returned, otherwise the right-hand argument is evaluated and returned:

System.print(false || 1)  // 1
System.print(1 || 2)      // 1

The conditional operator ?: #

Also known as the "ternary" operator since it takes three arguments, Wren has the little "if statement in the form of an expression" you know and love from C and its brethren.

System.print(1 != 2 ? "math is sane" : "math is not sane!")

It takes a condition expression, followed by ?, followed by a then expression, a :, then an else expression. Just like if, it evaluates the condition. If true, it evaluates (and returns) the then expression. Otherwise it does the else expression.

The is operator #

Wren has one last expression form. You can use the is keyword like an infix operator. It performs a type test. The left operand is an object and the right operand is a class. It evaluates to true if the object is an instance of the class (or one of its subclasses).

123 is Num     // true
"s" is Num     // false
null is String // false
[] is List     // true
[] is Sequence // true

Precedence #

When you mix these all together, you need to worry about precedence—which operators bind more tightly than others—and associativity—how a series of the same operator is ordered. Wren mostly follows C, except that it fixes the bitwise operator mistake. The full precedence table, from highest to lowest, is:

Prec Operator Description Assoc
1 () [] . Grouping, Subscript, Method call Left
2 - ! ~ Negate, Not, Complement Right
3 * / % Multiply, Divide, Modulo Left
4 + - Add, Subtract Left
5 .. ... Inclusive range, Exclusive range Left
6 << >> Left shift, Right shift Left
7 < <= > >= Comparison Left
8 == Equals Left
8 != Not equal Left
9 & Bitwise and Left
10 ^ Bitwise xor Left
11 | Bitwise or Left
12 is Type test Left
13 && Logical and Left
14 || Logical or Left
15 ?: Conditional Right
16 = Assign Right