website is under construction
Language

Operators

An operator is something that takes one or more values (or expressions) and yields another value (so that the construction itself becomes an expression)

Operators can be grouped according to the number of values they take. Unary operators take only one value, for example ! (the logical not operator). Binary operators take two values, such as the familiar arithmetical operators + (plus) and - (minus). The majority of Ghost's operators fall into this category.

Precedence and Associativity

The precedence of an operator specifies how "tightly" it binds two expressions together. For example, in the expression 1 + 5 * 3, the answer is 16 and not 18 because the multiplication (*) operator has a higher precedence than the addition (+) operator. Parentheses may be used to force precedence, if necessary. For example, (1 + 5) * 3 evaluates to 18.

When operators have equal precedence their associativity decides how the operators are grouped. - is left-associative, so 1 - 2 - 3 is grouped as (1 - 2) - 3 and evaluates to -4.

Use of parentheses, even when not strictly necessary, can often increase readability of the code by making grouping explicit rather than relying on the implicit operator precedence and associativity.

The following table summarizes the operator precedence in Ghost, from highest to lowest. Operators in the same box have the same precedence.

PrecedenceOperatorDescriptionAssociates
1. []Property access, SubscriptLeft
2()CallLeft
3- !Negate, Logical notRight
4%ModuloLeft
5* / *= /=Multiply, DivideLeft
6+ - += -=Add, SubtractLeft
7< <= > >=ComparisonLeft
8== !=Equals, Not equalLeft
9..RangeLeft
10? :Ternary
11andLogical andLeft
12orLogical orLeft
Two of these are worth committing to memory. % binds tighter than * and /, so 2 * 3 % 4 is 2 * (3 % 4) and evaluates to 6. And .. binds looser than the comparisons, so a range built from compared values needs parentheses. When in doubt, parenthesize.

Assignment (=) is not in the table: it is a statement rather than an expression, so a = b = c is not valid Ghost.

Arithmetic Operators

Remember basic arithmetic from school? These work just like those.

ExampleNameResult
-aNegationOpposite of a.
a + bAdditionSum of a and b.
a - bSubtractionDifference of a and b.
a * bMultiplicationProduct of a and b.
a / bDivisionQuotient of a and b.
a % bModuloRemainder of a divided by b.

Division always produces a decimal, even when both operands are whole: 10 / 4 is 2.5, not 2. Use math.floor() if you want the whole part.

The result of the modulo operator (%) has the same sign as the dividend - that is, the result of a % b will have the same sign as a. For example:

console.log(5 % 3) // >> 2
console.log(5 % -3) // >> 2
console.log(-5 % 3) // >> -2
console.log(-5 % -3) // >> -2

+ also concatenates two strings:

console.log("Hello, " + "world!") // >> Hello, world!

On lists every arithmetic operator is elementwise, and broadcasts: a number spreads across the list, and a shorter shape stretches across a longer one.

console.log([1, 2, 3] * 2)               // >> [2, 4, 6]
console.log([1, 2, 3] + [10, 20, 30])    // >> [11, 22, 33]
console.log([[1, 2], [3, 4]] + [10, 20]) // >> [[11, 22], [13, 24]]

Note that + does not join two lists the way it joins two strings — the arithmetic reading is the one that keeps all five operators agreeing with each other. Use concat() to join.

Both operands have to be the same type. Adding a number to a string is a type error — use a template literal, or call toString() on the number first.

Assignment Operator

The assignment operator is =. This declares and assigns the value of the expression on the right.

message = "Hello, world!"

console.log(message) // >> Hello, world!

Compound Assignment

The compound operators apply an arithmetic operation to a variable and assign the result back to it.

ExampleEquivalent to
a += ba = a + b
a -= ba = a - b
a *= ba = a * b
a /= ba = a / b

There is no %=; write a = a % b.

total = 10
total += 5

console.log(total) // >> 15

Increment and Decrement

++ and -- are postfix operators that add or subtract one. There is no prefix form — ++x is not valid Ghost. They work on a variable, a property, and an index alike:

counts = { hits: 0 }
list = [1, 2]

counts.hits++
list[0]++
count = 0

count++
count++
count--

console.log(count) // >> 1

They are most at home in the increment clause of a for loop.

Comparison Operators

Comparison operators, as their name implies, allow you to compare two values.

ExampleNameResult
a == bEqualtrue if a is equal to b.
a != bNot equaltrue if a is not equal to b.
a < bLess thantrue if a is less than b.
a > bGreater thantrue if a is greater than b.
a <= bLess than or equal totrue if a is less than or equal to b.
a >= bGreater than or equal totrue if a is greater than or equal to b.

Numbers, strings, and booleans compare by value. Comparing two values of different types is a type error rather than false — except against null, which any value may be compared to, and which is how you test whether something is set:

if (result == null) {
  // nothing came back
}

Every type answers == and !=. Which answer you get depends on the kind of value, and the split is between values defined by their contents and values defined by which one they are:

Both sides are== compares
number, string, booleanvalue
listcontents, to any depth
mapcontents, to any depth — same keys, each with an equal value
datethe instant, whatever time zone each is attached to
durationits six components
instanceidentity
function, class, trait, moduleidentity

Lists and maps compare by contents, so one you built compares equal to one you wrote out:

console.log([1, 2] + [1, 2] == [2, 4])       // >> true
console.log([[1], [2]] == [[1], [2]])        // >> true
console.log({name: "Ghost"} == {name: "Ghost"}) // >> true

Class instances compare by identity: two instances of the same class holding equal fields are still two different objects, and == reports false. Functions, classes, and traits work the same way — a value is equal to itself and to nothing else.

class Point {
  constructor(x) { this.x = x }
}

console.log(new Point(1) == new Point(1)) // >> false

p = new Point(1)
console.log(p == p)                       // >> true

Ordering two lists with < or > is not supported: neither an elementwise nor a lexicographic reading is obviously the right one, so Ghost refuses rather than picking. Two durations are the same — "which span is longer" has no answer without a reference date, since a month is a different number of days depending on which month it starts from.

Dates compare as instants, with ==, !=, and all four ordering operators. See Date.

Logical Operators

ExampleNameResult
!aLogical nottrue if a is false, and vice versa.
a and bLogical andtrue if both a and b are true.
a or bLogical ortrue if either a or b is true.
and and or do not short-circuit. Both sides are evaluated before the operator is applied, so a test on the left cannot guard the expression on the right. See Control Flow.

Range Operator

.. builds a list of the whole numbers from its left operand up to and including its right one.

console.log(1 .. 5) // >> [1, 2, 3, 4, 5]

It is mostly used to drive a for ... in loop:

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

If the start is greater than the end, the result is an empty list — ranges do not count backwards.

Ternary Operator

condition ? ifTrue : ifFalse evaluates to one of two expressions depending on a condition.

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

Ternary expressions cannot be nested, in either branch, and parentheses do not change that — width > 100 ? (width > 500 ? "huge" : "large") : "small" is a syntax error. Use an if statement, or an intermediate variable:

if (width > 500) {
  size = "huge"
} else if (width > 100) {
  size = "large"
} else {
  size = "small"
}

Subscript and Property Access

[] reads an element from a list, a character from a string, or a value from a map by key. . reads a property of a map or an instance, or calls a method.

list = [1, 2, 3]
map = { name: "Ghost" }

console.log(list[0])     // >> 1
console.log("Ghost"[0])  // >> G
console.log(map["name"]) // >> Ghost
console.log(map.name)    // >> Ghost

An index that is out of range, and a map key that is not present, both read as null rather than raising an error.