website is under construction
Language

Variables

Variables are named slots for storing values. You define a new variable in Ghost using the = operator, like so:

a = 1 + 2

This creates a new variable a in the current scope and initializes it with the result of the expression following =. Once a variable has been defined, it can be accessed by name as you would expect.

technology = "Micromachines"

console.log(technology) // >> Micromachines

Reading a name that has never been defined is an error, and Ghost reports it as soon as it reaches that line:

console.log(mystery) // name error: `mystery` is not defined

Destructuring

One statement can bind several names at once. A list pattern binds by position:

[first, second] = [10, 20]

console.log(first)  // >> 10
console.log(second) // >> 20

Asking for more names than the list has is not an error — the extras are null, the same leniency indexing out of range already has.

A map pattern binds by key. Naming a key on its own binds it to a variable of that name:

{name, version} = {name: "Ghost", version: "1.0.0-beta.3"}

console.log(name)    // >> Ghost
console.log(version) // >> 1.0.0-beta.3

key: local binds that key to a different name instead:

{name: title} = {name: "Ghost"}

console.log(title) // >> Ghost

Destructuring is a statement, not an expression — it cannot be used as a call argument. The patterns bind plain names only: no nesting, and no obj.x or list[0] on the left. The right-hand side has to already be a list for a [...] pattern, and a map for a {...} one, or it is a type error.

A statement that opens with [ can be read as a continuation of the line above it, since Ghost has no significant newlines. If the previous line ends in something indexable, end it with a ; — see Statements.

Scope

Functions introduce a scope. Blocks do not. A variable first assigned inside an if, while, or for body belongs to the scope that surrounds the loop or branch, and is still there after it ends:

if (true) {
  message = "assigned inside the block"
}

console.log(message) // >> assigned inside the block

A function body is different. Everything assigned inside it lives and dies with the call:

function foobar() {
  a = 123

  console.log(a) // >> 123
}

foobar()

console.log(a) // name error: `a` is not defined

Variables defined at the top level of a script are top-level, or global. All other variables are local.

Assignment inside a function is always local

This is the rule that catches people out. = inside a function creates or updates a variable in that function; it never reaches back out to reassign one from an enclosing scope.

count = 0

function bump() {
  count = count + 1 // reads the outer `count`, writes a new local one
}

bump()

console.log(count) // >> 0

A function can read a variable from an enclosing scope, so the right-hand side above sees 0. The assignment then defines a local count that disappears when the call returns.

To have a function change state that outlives it, put the state on a map or a class instance and assign through a property. Property assignment mutates the object itself, and every reference to it sees the change:

state = { count: 0 }

function bump() {
  state.count = state.count + 1
}

bump()
bump()

console.log(state.count) // >> 2

Shadowing

Declaring a variable in an inner scope with the same name as an outer one is called shadowing and is not an error.

a = "outer"

function foobar() {
  a = "inner"

  console.log(a) // >> inner
}

foobar()

console.log(a) // >> outer

Loop variables

The counter of a for loop, and the key and value of a for ... in loop, belong to the loop. When the loop finishes, they are removed again — and if the name was already in use, its previous value is put back.

for (i = 0; i < 3; i++) {
  last = i
}

console.log(last) // >> 2
console.log(i)    // name error: `i` is not defined