website is under construction
Language

Control Flow

Control flow is used to determine which blocks of code are executed and how many times. Conditional statements and expressions decide whether or not to execute some code and looping ones execute something more than once.

Truthiness

Anywhere Ghost wants a condition — an if, a while, a ternary — it accepts any value and asks whether it is truthy.

ValueTruthy?
falseno
nullno
""no
trueyes
any other numberyes
any other stringyes
list, map, instance, functionyes

0 and [] are truthy. Test them explicitly (count == 0, list.length() == 0) rather than leaning on the condition.

Logical Operators

and and or combine two booleans. Both operands must be booleans — false and 1 is a type error, not false.

console.log(true and false) // >> false
console.log(true or false)  // >> true
Neither operator short-circuits. Ghost evaluates both sides before applying the operator, so a test on the left cannot protect the expression on the right.

This is the shape that goes wrong:

// `list[i]` is read even when `i` is out of range
if (i >= 0 and list[i].found) {
  //
}

Write it as nested if statements instead, so the second test only runs when the first passed:

if (i >= 0) {
  if (list[i].found) {
    //
  }
}

Reading a list index that doesn't exist gives null rather than an error, so the failure above shows up further along as an error about a property of null.

Conditional Statements

A conditional statement is a set of commands that executes if a specified condition is true.

If Statements

The simplest conditional statement, if lets you conditionally skip a chunk of code. It looks like this:

if (condition) {
  // Do something
}

This evaluates the parenthesized expression after if. If it's truthy, then the block after the condition is evaluated. Otherwise it is skipped.

You may also provide an else branch. It will be executed if the condition is falsy:

if (condition) {
  // Do something
} else {
  // Do something else
}

You can also chain if statements together with else if to check multiple conditions:

if (firstCondition) {
  // Do something
} else if (secondCondition) {
  // Do something else
} else if (thirdCondition) {
  // Do something else
} else {
  // Do something else
}

Ternary Expressions

When both branches produce a value, the ternary operator says the same thing in one line:

label = count == 1 ? "item" : "items"

Switch Statements

The switch statement evaluates an expression, matching the expression's value to a case clause, and executes statements associated with that case. They're similar to a series of if statements but are more concise and easier to read when evaluating multiple possible conditions for a single variable.

switch (value) {
  case 0 {
    // Handle case where value is 0
  }
  case 1 {
    // Handle case where value is 1
  }
  case 2 {
    // Handle case where value is 2
  }
}

// Is equivalent to:

if (value == 0) {
  // Handle case where value is 0
} else if (value == 1) {
  // Handle case where value is 1
} else if (value == 2) {
  // Handle case where value is 2
}

It's important to note that cases are not fall-through. That is, the switch statement will only execute the first case that matches. If you want to execute multiple cases, you can evaluate multiple cases in a single block:

switch (value) {
  case 0, 1, 2 {
    // Handle case where value is 0, 1, or 2
  }
  case 3, 4, 5 {
    // Handle case where value is 3, 4, or 5
  }
}

You can also provide a default case which will be executed if no other case matches:

switch (value) {
  case 0 {
    // Handle case where value is 0
  }
  case 1 {
    // Handle case where value is 1
  }
  case 2 {
    // Handle case where value is 2
  }
  default {
    // Handle all other cases
  }
}

Regardless of whether or not a default case is provided, execution will continue after the switch statement per normal. If a default case is not provided, and no other cases match, the switch statement will do nothing.

At most one default is allowed; a second is a parse error.

A case matches using the same rule == uses, so lists and maps match on their contents rather than on being the same value:

switch (point) {
  case [0, 0] {
    console.log("origin")
  }
}

And because matching is ordinary evaluation, an error in the subject or in a case value is reported where it happened rather than being swallowed.

Looping Statements

A looping statement offers a quick and easy way to do something repeatedly. In this section, we'll introduce the different looping statements available in Ghost.

While Statement

A while statement executes its block as long as a specified condition evaluates to true. A while statement looks like the following:

while (condition) {
  // Do something
}

The condition test occurs before the block is executed. If the condition returns true, the block is executed and the condition is tested again. If the condition returns false, execution stops, and control is passed to the statement following while.


The following while loop iterates as long as n is less than 3:

n = 0
x = 0

while (n < 3) {
  n = n + 1
  x += n
}

With each iteration, the loop increments n and adds that value to x. Therefore, x and n take on the following values:

  • After the first pass: n = 1 and x = 1
  • After the second pass: n = 2 and x = 3
  • After the third pass: n = 3 and x = 6

After completing the third pass, the condition n < 3 is no longer true, so the loop terminates.


Generally you'll want to avoid infinite loops (there are very few cases where an infinite loops are utilized, such as in game development). These are loops where the condition never evaluates to false. The following example will loop forever because the condition is always true:

while (true) {
  console.log("Hello, world!")
}

For Statement

A for loop repeats until a specified condition evaluates to false. The Ghost for loop is similar to the C loop for those familiar.

A for statement looks like the following:

for (initializer; condition; increment) {
  // Do something
}

When a for loop executes, the following occurs:

  1. The initializing expression is executed. This expression usually initializes one or more loop counters. This expression can declare variables.
  2. The condition expression is evaluated. If the value of condition is true, the loop block is executed. If the value of condition is false, the for loop terminates.
  3. The block executes.
  4. The increment expression is executed.
  5. Control returns to step 2 until the condition expression evaluates to false.
for (i = 0; i < 5; i++) {
  console.log(i)
}

The three clauses are separated by semicolons — the one place a semicolon is required rather than optional. The increment clause may be an assignment (i = i + 1), a compound assignment (i += 1), or an increment or decrement (i++, i--), and it can target anything an assignment can reach, not just a plain variable:

for (i = 0; i < items.length(); state.seen++) {
  i++
}

The counter belongs to the loop: once the loop ends, the name is gone again.

For ... In Statement

for ... in walks a list or a map without you having to manage an index.

for (name in ["Artemis", "Rabbit", "Orion"]) {
  console.log(name)
}

Give it two names and the first receives the key — the index for a list, the key for a map:

for (index, name in ["Artemis", "Rabbit", "Orion"]) {
  console.log(index.toString() + ": " + name)
}

for (key, value in { name: "Ghost", type: "language" }) {
  console.log(key + " = " + value)
}

Maps keep their insertion order, so the pairs come out in the order they were added — the same order keys() and values() report.

Combined with the range operator, for ... in also counts:

for (i in 1 .. 5) {
  console.log(i)
}

Lists and maps are the only things Ghost knows how to iterate. Anything else is a type error, with the help text "for ... in walks a list or a map."

Break Statement

The break statement terminates the current loop statement and transfers program control back to the state following the terminated statement.

A break statement cannot be used at the top level of a script, module, function, or class.

As an example, the following function has a break statement that terminates the while loop when i reaches 3, and then returns the value 3 * x from the function:

function testBreak(x) {
  i = 0

  while (i < 6) {
    if (i == 3) {
      break
    }

    i = i + 1
  }

  return i * x
}

Continue Statement

The continue statement terminates execution of the statements in the current iteration of the current loop, and continues execution of the loop with the next iteration.

Much like break, a continue statement cannot be used at the top level of a script, module, function, or class.

As an example, the following example shows a while loop that has a continue statement that executes when the value of i is 3. n therefore counts every pass except that one, and ends at 4.

i = 0
n = 0

while (i < 5) {
  i = i + 1

  if (i == 3) {
    continue
  }

  n = n + 1
}