Skip to main content

Lexical structure

The building blocks of BT source text: files, comments, names, keywords and literals.

object Character { int health; int max_health; }

/// Heals `target` by `amount`, capped at its maximum.
infallible fn heal(Character target, int amount) -> int {
// Statements end with a semicolon; line breaks don't matter.
int healed = target.health + amount;
if (healed > target.max_health) { healed = target.max_health; }
target.health = healed;
return healed;
}

Source files​

  • BT source files use the extension .bt. Write them in UTF-8.
  • BT is case-sensitive: score and Score are different names.
  • Statements end with ;. Spaces, tabs and line breaks only separate tokens; LF and CRLF line endings are equivalent.
  • A #! line at the very start of a file is ignored, so a file can be run as a script on Linux. # anywhere else is an error.
  • These characters are an unexpected character error outside strings and comments: #, `, @, \, ^, and a single & or |.

Comments​

FormRule
// textRuns to the end of the line.
/* text */May span lines. Block comments nest: each /* needs its own */. An unclosed block comment is an error.
/// textA doc comment: editors show it when you hover over the declaration.

Doc comments​

Write /// lines directly above a function, tree, object, interface, field, method, query or access declaration. The text is Markdown.

/// Heals `target` by `amount`, capped at its maximum.
///
/// Fails when the target is already dead.
fn heal(Character target,
/// Hit points to restore.
int amount) -> int { ... }
  • The comment must be on the line right above the declaration. A blank line, an ordinary // comment or code in between detaches it, and it documents nothing.
  • A parameter can have its own doc comment if it starts its own line.
  • //// lines (four slashes, often used for banners) are not doc comments.
  • /// after code on the same line is an ordinary comment.

Names​

A name (identifier) starts with a letter or _, followed by letters, digits or _. Non-ASCII letters work too, so größe is a valid name. Names are compared byte for byte, so two visually identical names written in different Unicode forms are different names.

Reserved words​

These words are keywords and cannot be used as names:

include object interface fn infallible const tree
if else while repeat for in select
break continue return throw discard
branch join new typeof unless
true false null
enum switch is
  • NaN and Infinity are float literals, so they cannot be names either.
  • select can still be a method or function name (tabs.select(0), fn select(int i)).
  • discard is deprecated and gives a warning; just write the expression as a statement.

Some words are special only in one position and remain usable as ordinary names elsewhere:

WordWhere it is special
query, accessAt the start of a top-level declaration.
asInside a query body, and after an include path.
read, writeInside an access body.
void, bool, byte, int, int32, float, float32, string, error, typeAs built-in type names.
task, array, slice, map, channel, component, rows, view, borrowAs built-in type names with type arguments.
thisThe object inside a method. A parameter cannot be named this.
errThe error inside a !! handler.
_As a whole switch pattern it matches anything; as a type pattern's name (Circle _ =>) it binds nothing.

Words from other languages such as try, catch, var, let, auto, class and struct mean nothing in BT and are ordinary names.

Integer literals​

0 42 1000000 -5
  • Decimal digits only. There are no hexadecimal, octal or binary literals, digit separators or suffixes.
  • The type is int (signed 64-bit). A leading - is the negation operator, not part of the literal.
  • The largest literal is 9223372036854775807. A larger one is the compile error integer literal ... is out of range. The minimum int can be written as -9223372036854775808.

Float literals​

0.25 2e3 1.5e-2 6.02E+23 NaN Infinity
  • The type is float (64-bit double precision).
  • A decimal point needs digits on both sides: write 1.0, not 1., and 0.5, not .5.
  • An exponent needs digits: 1e is an error.
  • NaN is a quiet not-a-number and Infinity is positive infinity. Write negative infinity as -Infinity. Both are reserved and adapt to float32 like any float literal.
  • A literal too large for a double, such as 1e400, becomes infinity without a warning.

String literals​

"Hello" "line one\nline two" "she said \"hi\""
  • A string literal must close on the same line (unterminated string literal).

  • Escapes:

    EscapeMeaning
    \nnewline
    \rcarriage return
    \ttab
    \""
    \\\
    \$$ (so \${ is literal text, not an interpolation)

    A backslash before any other character is that character (\q is q). There are no numeric or Unicode (\u) escapes, and a string can never contain a NUL byte. UTF-8 text in the source is kept as written.

Interpolated strings​

"hp ${hp} of ${max}" "${name}: ${score * 2}" "cost: \$${price}"
  • ${ inside a string starts an embedded expression and the matching } ends it. A $ not followed by { is ordinary text.
  • The embedded expression is ordinary source: it may contain braces (map and object literals), other strings, and other interpolated strings. Only the string's literal text must stay on one line.
  • A ${ with no matching } is an error (unterminated '${' in string literal). A ; outside braces inside the expression also ends it with that error, since no expression contains one.
  • What can be embedded and how each value is printed is described under String interpolation.

Character literals​

'a' '\n' '\'' 'é' '\u{1F600}'
  • A character literal is one Unicode code point in single quotes. Its type is char.

  • UTF-8 text between the quotes counts as one character when it is one code point: 'é' and '😀' are valid, 'ab' is an error.

  • Escapes:

    EscapeMeaning
    \nnewline
    \rcarriage return
    \ttab
    \0the zero character, U+0000
    \''
    \""
    \\\
    \u{1F600}the code point with that hex value (1 to 6 digits)
  • Any other escape is an error, unlike in strings.

  • These are errors: an empty literal '', more than one code point, a missing closing quote, malformed UTF-8, a UTF-16 surrogate (\u{D800} to \u{DFFF}), and a value above \u{10FFFF}.

Other literals​

  • true and false are bool.
  • null fits only where a nullable type is expected (see Nullability).
  • Array, map and object literals ([1, 2, 3], {"hp": 10}, Point { x: 1.0, y: 2.0 }) are described under Construction.

Operators and punctuation​

( ) { } [ ] , ; : . ? ?? -> => ~
= + - * / % ++ -- ! !! == != < <= > >= && ||
+= -= *= /= %=
  • array<array<int>> needs no space between the closing >s.
  • ~ is not an operator. It marks a declaration or field as private to its package (see Private declarations).
  • && { and !! { start guard blocks (see Guard blocks).

Nesting limit​

Parentheses, blocks and operator chains can nest at most 256 levels deep. Deeper code is the error nesting is too deep (limit 256); split it into helper functions.

See also​