# Syntax

Bruijn has an arguably weird syntax, although it's not strictly meant as
an esoteric programming language. Most notably the usage of lambda
calculus logic, combinators, and De Bruijn indices can be confusing at
first -- it's definitely possible to get used to them though!

Bruijn uses a [variation of lambda calculus](lambda-calculus.md).

## Definitions

Bruijn works by defining named substitution rules, where each usage of
the identifier will get substituted respectively.

Since there's no other major syntactic block, bruijn omits the equal
symbol for definitions.

For example:

``` bruijn
# define `id` as [0]
id [0]

# [0] [0] ⤳ [0]
still-id id id

# `main` is now [0]
main still-id
```

Note that this does *not* actually extend the functionality of lambda
calculus. These identifiers are static constants and can't be
dynamically changed.

In fact, bruijn's interpreter works by producing *one* final huge lambda
calculus expression for any given program, that then gets reduced by a
normal lambda calculus reducer.

## Open terms

If you use De Bruijn indices that reach out of their environment, you
have created an *open term*. Depending on the context, these terms are
typically seen as invalid if standing by themself.

``` bruijn
# open terms
open0 0
open1 [[2]]
open2 [[0 1] 1]

# closed terms
closed0 [0 [1]]
closed1 [[[2 0] 1]]
```

Bruijn does not give warnings for open terms and reduces them as normal.
In some cases it's actually encouraged to use open terms as sub-terms
for better readability (see [coding style
suggestions](../coding/style.md)).

## Imports

Files can either be imported into a namespace (capital word) or the
current environment (.):

``` bruijn
:import std/Pair P
:import std/Logic .

main [P.pair true false]
```

All paths get the `.bruijn` extension appended automatically.

Only top-level definitions get imported using `:import`{.bruijn}. If you
also want to import second-level definitions (for example imported
definitions from the imported file), you can use `:input`{.bruijn}.

``` bruijn
:input std/Math
```

## Tests

Tests compare the *normal form* of two expressions. Note that the
parentheses around the two terms are always mandatory.

``` bruijn
:test ([0] [[1]]) ([[1]] [[1]] [0])
```

Execution succeeds silently. Example of failing test:

``` bruijn
:test ([0] [[1]]) ([[1]] [0])
```

After running:

    ERROR test failed: ([0] [[1]]) = ([[1]] [0])
          reduced to [[1]] = [[0]]

Tests are always run for the executed file and any files it contains. If
they take too long, you can enable the *YOLO* mode by using bruijn's
`-y` argument.

## Scoping

Indented lines (by tab) act as a `where` statement for the less indented
lines.

``` bruijn
foo [[0]]

bar [0] foo
    foo bar
        bar [[1]]

# foo is still `[[0]]`
# bar is `[0] [[1]]`
main [[0 foo bar]]
```

Also note that bruijn does *not* support recursion -- you wouldn't be
able to use `bar`{.bruijn} in `foo`{.bruijn} without its sub definition.
See [recursion](../coding/recursion.md) to learn how to use recursion
anyway.

## Syntactic sugar

Some typical data encodings are provided as syntactic sugar. You can
learn more about the internal specifics in [data
structures](../coding/data-structures.md).

-   *Numbers*: `(SXB)`{.bruijn} where `S` is `+`/`-`, `X` is a number
    and `B` is the *base* of the encoded number (or `t` by default)
    -   `u` for unary base (postive, Church): `(+42u)`
    -   `b` for binary base (positive): `(+42b)`
    -   `t` for balanced ternary (positive/negative): `(-42t)`
-   *Characters*: `'C'`{.bruijn} where `C` is any ASCII character
-   *Strings*: `"X1..XN"`{.bruijn} where `X1...XN` are any ASCII
    characters
-   *Quotes*: `` `T ``{.briujn} where `T` is any lambda term ([meta
    programming](../coding/meta-programming.md))

## Types

TODO.