Statements

The Joe language provides the following statements. Statements are terminated by a semicolon or (sometimes) a block, as in Java.

Variable Declarations

All variables must be declared before use using the var statement. The var statement can assign an initial value; if the initial value is omitted, the variable is initialized to null.

var x = 5;
var y;      // y == null

Joe is lexically scoped; undeclared variables in function or method bodies are presumed to be declared in an enclosing scope. It is an error if they are not.

In addition to declaring and initializing individual variables, the var statement can use a pattern to perform a destructuring bind: that is, to bind one or more variables to values within a data structure. For example, suppose that the function f() returns a two-item list, and the caller wants to assign the list items to the variables x and y.

One could do this:

var result = f();
var x = result[0];
var y = result[1];

Or, one could do a destructing bind:

var [x, y] = f();

Here, var matches the list returned by f() against the pattern [x, y] and binds variables x and y to the matched values.

When the pattern doesn't match: If var's pattern doesn't match the target value, e.g., if f() didn't return a two-item list in the example shown above, then the pattern match will fail and Joe will throw a runtime error. Therefore, var should only be used when the shape of the target value is known ahead of time. Use the ~ operator or the match statement to test whether a value matches a particular pattern.

See Pattern Matching for more on pattern matching and destructuring binds, including Joe's full pattern syntax.

Function Declarations

Functions are declared with the function statement. See Functions for more details.

Class Declarations

Classes are declared with the class statement. See Classes for more details.

Expression Statements

Any expression becomes a statement when followed by a semicolon.

x + y;
doSomething();

To execute an expression and see its value in joe repl, enter the expression as an expression statement.

Blocks

A block is a sequence of statements enclosed in curly brackets, as in Java. Each block defines a lexical scope.

var x = 5;
{
    var x = 6;
    println(x); // Prints "6"
}
println(x);     // Prints "5"

Return

The return statement is used to return from functions and methods. As in Java, it takes an optional expression to return a value.

When used at script level, return terminates the script, optionally returning a value. This value is displayed in the REPL and is accessible via Joe's embedding API, but is not displayed by joe run.

If Statements

if statements are defined as in Java.

if (x == 5) {
    ...
} else if (x == 15) {
    ...
} else {
    ...
}

While Loops

while loops are defined as in Java.

var x = 0;
while (x < 10) {
    ...
    x = x + 1;
}

For Loops

for loops are defined as in Java, except that for (var item : list) is not supported. See Foreach Loops, below.

for (var i = 0; i < 10; i = i + 1) {
    ...
}

Foreach Loops

foreach loops allow iteration over the members of a collection, e.g., a Joe List.

var list = ["a", "b", "c"];

// Prints "a", "b", and "c" on successive lines.
foreach (item : list) {
    println(item);
} 

In addition, foreach can use a pattern to do a destructuring bind on each list item:

var list = [[#a, 1], [#b, 2], #whoops, [#c, 3]];

// Prints #a, #b, and #c on successive lines
foreach ([x, _] : list) {
    println(x);
}

foreach will silently ignore any list items that don't match the pattern, making it a useful tool for extracting data from homogeneous lists.

See Pattern Matching for more on pattern matching and destructuring binds, including Joe's full pattern syntax.

Break and Continue

The break and continue statements break out of the enclosing loop or continue with the next iteration of the enclosing loop, just as in Java.

There is no support for labeled breaks or continues in order to jump through nested loops.

Switch Statements

Joe's switch statement uses Java's enhanced switch syntax rather than the classic C-like syntax:

switch (x) {
    case 1, 2, 3 -> return "abc";
    case 4, 5, 6 -> return "def";
    case 7, 8, 9 -> return "ghi";
    default -> return "???";
}
  • The switch value and the case values can be any Joe value or expression.
  • There must be at least one case clause.
  • Each case can have one or more values to match.
  • Each case's body can be a single statement or a block.
  • If present, the default clause must follow all the case clauses.

The implementation is quite simple: each case value is checked in turn, from the top to the bottom, until a match is found; then the case's body is executed.

Match Statements

The match statement is similar to a switch statement, but matches patterns against a target value instead of checking for equality. It is especially useful for processing a heterogeneous list of values.

match (value) {
    case [a, b] -> 
        println("Two item list of " a + " and " + b + ".");
    case Person(name, age) -> 
        println("Person " + name + " is " + age + " years old.");
    default -> println("no match");
}

Every match statement requires at least one case; the default case is optional. (Note that matching on _, a simple wildcard pattern, is equivalent to the default case:

match (value) {
    case [a, b] -> 
        println("Two item list of " a + " and " + b + ".");
    case Person(name, age) -> 
        println("Person " + name + " is " + age + " years old.");
    case _ -> println("no match");
}

Each case in a match can include an optional guard clause that adds a boolean guard condition on top of the pattern match. In the following example the case matches any Person record, and then requires that the person be at least 10 years old.

match (value) {
    ...
    case Person(name, age) if age >= 10 -> 
        println("Person " + name + " is at least 10 years old.");
    ...
}

See Pattern Matching for more on pattern matching and destructuring binds, including Joe's full pattern syntax.

Throw

The throw statement is used to explicitly throw error exceptions in a Joe script.

if (x <= 0) {
    throw Error("Expected a positive number.");
}

For convenience the message string can be thrown directly; the following code is equivalent to that above:

if (x <= 0) {
    throw "Expected a positive number.";
}

Thrown errors can be caught using the catch() function; and once caught can be rethrown.

Assert

The assert statement checks a condition and throws an error if it is unmet. Assertions are always checked; they are not disabled in production as in Java.

assert x > 0;

The programmer may provide an optional message.

assert x > 0, "x must be positive!";

The various checkers and assertion functions defined in joe test's Test API all work in terms of the assert statement.