The role of Eucalypt's syntax

Eucalypt has a native syntax which emphasises the mappings-and-lists nature of its underlying data model but adds enhancements for functions and expressions. Eucalypt is written in .eu files.

While eu happily processes YAML inputs with embedded expressions, many features are not yet available in the YAML embedding and the embedded expressions are themselves in Eucalypt syntax, so it is necessary to have an overview of how the syntax works to do anything interesting with Eucalypt.

A few aspects are unorthodox and experimental.

Overview

Eucalypt syntax comes about by the an overlapping of two sub-languages.

  • the block DSL is how you write blocks and their declarations
  • the expression DSL is how you write expressions

They are entwined in a fairly typical way: block literals (from the block DSL) can be used in expressions (from the expression DSL) and expressions (from the expression DSL) appear in declarations (from the block DSL).

Comments can be interspersed throughout. Eucalypt only has line level comments.

foo: bar # Line comments start with '#' and run till the end of the line

Note

If you feel you need a block comment, you can use an actual block or a string property within a block and mark it with annotation metadata :suppress to ensure it doesn't appear in output.

Eucalypt has two types of names:

  • normal names, which are largely alphanumeric (e.g. f, blah, some-thing!, ) and are used to name properties and functions
  • operator names, which are largely symbolic (e.g. &&&, , -+-|, ) and are used to name operators

See Operators and Identifiers for more.

The block DSL

A block is surrounded by curly braces:

... { ... }

...and contains declarations...

... {
  a: 1
  b: 2
  c: 3
}

...which may themselves have blocks as values...

... {
  foo: {
    bar: {
      baz: "hello world"
    }
  }
}

The top-level block in a file (a unit) does not have braces:

a: 1
b: 2
c: 3

So far all these declarations have been property declarations which contains a name and an expression, separated by a colon.

Commas are entirely optional for delimiting declarations. Line endings are not significant. The following is a top-level block of three property declarations.

a: 1 b: 2 c: 3

There are other types of declarations. By specifying a parameter list, you get a function declaration:

# A function declaration
f(x, y): x + y

two: f(1, 1)

...and using some brackets and suitable names, you can define operators too, either binary:

# A binary operator declaration
(x ^|^ y): "{x} v {y}"

...or prefix or postfix unary operators:

# A prefix operator declaration
(¬ x): not(x)

# A postfix operator declaration
(x ******): "maybe {x}"

Eucalypt should handle unicode gracefully and any unicode characters in the symbol or punctuation classes are fine for operators.

To control the precedence and associativity of user defined operators, you need metadata annotations.

declaration annotations allow us to specify arbitrary metadata against declarations. These can be used for documentation and similar.

To attach an annotation to a declaration, squeeze it between a leading backtick and the declaration itself:

` { doc: "This is a"}
a: 1

` { doc: "This is b"}
b: 2

Some metadata activate special handling, such as the associates and precedence keys you can put on operator declarations:

` { doc: "`(f ∘ g)` - return composition of `f` and `g`"
    associates: :right
    precedence: 88 }
(f ∘ g): compose(f,g)

Look out for other uses like :target, :suppress, :main.

Finally, you can specify metadata at a unit level. If the first item in a unit is an expression, rather than a declaration, it is treated as metadata that is applied to the whole unit.

{ :doc "This is just an example unit" }
a: 1 b: 2 c: 3

The expression DSL

Everything that can appear to the right of the colon in a declaration is an expression and defined by the expression DSL.

Primitives

First there are primitives.

...numbers...

123
-123
123.333

...double quoted strings...

"a string"

...symbols, prefixed by a colon...

:key

...which are currently very like strings, but used in circumstances where their internal structure is generally not significant (i.e. keys in a block's internal representation).

Finally, booleans (true and false) are pre-defined constants. As is (null) which is a value which renders as YAML or JSON's version of null but is not used by Eucalypt itself.

Block literals

Block literals (in braces, as defined in the block DSL) are expressions and can be the values of declarations or passed as function arguments or operands in any of the contexts below:

foo: { a: 1 b: 2 c: 3}

List literals

List literals are enclosed in square brackets and contain a comma separated sequence of expressions:

list: [1, 2, :a, "boo"]

Names

Then there are names, which refer to the surrounding context. They might refer to properties:

x: 22
y: x

...or functions:

add-one(x): 1 + x
three: add-one(2)

...or operators:

(x &&& y): [x, x, x, y]
z: "da" &&& "dum"

Calling functions

Functions can be applied by suffixing a argument list in parens, with no intervening whitespace:

f(x, y): x + y
result: f(2, 2) # no whitespace

In the special case of applying a single argument, "catenation" can be used:

add-one(x): 1 + x
result: 2 add-one

...which allows succinct expressions of pipelines of operations.

In addition, functions are curried so can be partially applied:

add(x, y): x + y
incremement: add(1)
result: 2 increment

...and placeholder underscores (or expression anaphora) can be used to define simple functions without the song and dance of a function declaration:

f: if(tuesday?, (_ * 32 / 12), (99 / _))
result: f(3)

In fact, in many cases the underscores can be omitted, leading to a construct very similar to Haskell's sections only even brackets aren't necessary.

Note

Eucalypt uses its knowledge of the fixity and associativity of each operator to find "gaps" and fills them with the unwritten underscores. This is great for simple cases but worth avoiding for complicated expressions.

increment: + 1
result: 2 increment (126 /)

Both styles of function application together with partial application and sectioning can all be applied together:

result: [1, 2, 3] map(+1) filter(odd?) //=> [3]

(//=> is an assertion operator which causes a panic if the left and right hand expressions aren't found to be equal at run time, but returns that value if they are.

Note

There are no explicit lambda expressions in Eucalypt right now. For simple cases, expression or string anaphora should do the job. For more involved cases, you should use a named function declaration. See Anaphora and Lambdas for more.