website is under construction
Language

Syntax

Ghost's syntax is designed to be simple and predictable: curly braces for blocks, parentheses around conditions, and as few special cases as the language can get away with.

Scripts are stored in plain text files with a .gs file extension. Ghost does not compile ahead of time: programs are run directly from source, from top to bottom like any other scripting language.

Comments

Single line comments start with // and continue to the end of the line.

// This is a single line comment

Block comments are enclosed within /* ... */ and can span as many lines as necessary.

/*
This is a block comment.
*/

Comments behave like whitespace and are discarded during execution. While single line comments are the norm, block comments are useful within an expression or to disable large swaths of code.

Statements

A statement simply ends where its grammar says it ends. Ghost has no significant-newline rule and no automatic semicolon insertion to reason about; a trailing ; is optional, and accepted after every kind of statement.

name = "Ghost"
console.log(name)

name = "Ghost";      // also fine

; is required between the three clauses of a for loop header.

When a line break is not a separator

Because a newline means nothing to the parser, two statements on separate lines can still be read as one when the second line opens with a token the first can continue into[, (, ., ++, --, or a binary operator:

x = 1
[10, 20, 30]

That is not an assignment followed by a list. The [ after 1 reads as an index, so Ghost tries to parse x = 1[10, 20, 30] as a single statement and reports a syntax error on the first comma. When the continuation happens to be valid, you get no error at all — just a statement that quietly did something other than what the layout suggests.

Ending the first line with ; settles it:

x = 1;
[10, 20, 30]

This comes up most with destructuring, which begins with [ or {. It is worth the semicolon whenever the next line starts with one of those tokens. Ghost keeps the rule rather than making newlines significant because a statement that can span lines is what lets a method chain break across them:

result = values
  .filter(isReady)
  .map(toLabel)

Strings

Strings are written in single or double quotes, and may span several lines literally.

name = "Ghost"
other = 'Ghost'

Backtick strings are template literals, and interpolate any expression written inside ${}:

count = 3

console.log(`there are ${count} item${count == 1 ? "" : "s"}`)
// >> there are 3 items

Each interpolated value is converted using its own string form, so nothing needs an explicit toString() to appear cleanly. This is the fluent way to build a string out of mixed types — + still requires both sides to be the same type. See Strings.

Reserved Words

Ghost has a small subset of reserved words used as predefined identifiers. None of the identifiers listed here should be used as identifiers in any of your scripts.

and       as        break     case      class     continue
default   else      extends   false     for       from
function  if        import    in        new       null
or        return    super     switch    this      trait
true      use       while

There are 27 of them, and they are all lowercase and case-sensitive.

The two names Ghost makes available without an import — console and type — are not reserved words either, and neither is the name of a module you have imported. A variable that shares a name with one shadows it for the rest of the scope, which matters most for a name like font or image inside a file that imported the module of that name.

Identifiers

Naming rules are similar to other programming languages. Identifiers must start with a letter or underscore and may then contain letters, digits, and underscores. Case is sensitive.

hello
camelCase
PascalCase
_under_score
abc123
ALL_CAPS

Blocks

Ghost uses curly braces to define blocks. A block is the body of a control flow statement, a function, a class, or a trait — it is always attached to one of those, never written on its own.

pressure = 3.6

if (pressure > 3.4) {
  console.log("Pressure is above critical levels.")
}

Braces standing alone are a map literal, not a block, so there is no bare-block form for grouping statements or introducing a scope.

Blocks group statements; they do not introduce a new scope. A variable first assigned inside an if, while, or for body belongs to the scope around it and outlives the block. See Variables for what does introduce a scope.