Blocks and Declarations
In this chapter you will learn:
- What blocks are and how they relate to structured data formats
- The three types of declarations: property, function, and operator
- How top-level files work as implicit blocks (units)
- How to annotate declarations with metadata
Blocks
A block is eucalypt's fundamental data structure. It corresponds to a JSON object, a YAML mapping, or a TOML table: an ordered collection of named values.
Blocks are written with curly braces:
person: {
name: "Alice"
age: 30
role: "engineer"
}
Running this file produces:
person:
name: Alice
age: 30
role: engineer
Blocks can be nested:
config: {
database: {
host: "localhost"
port: 5432
}
cache: {
host: "localhost"
port: 6379
}
}
Property Declarations
The simplest declaration is a property declaration: a name followed by a colon and an expression.
greeting: "Hello, World!"
count: 42
pi: 3.14159
active: true
nothing: null
These declare names bound to values. The values can be any expression:
numbers, strings, booleans, null, lists, blocks, or computed
expressions.
Commas are Optional
Declarations can be separated by commas or simply by whitespace. Line endings are not significant. All of these are equivalent:
a: { x: 1 y: 2 z: 3 }
b: { x: 1, y: 2, z: 3 }
c: { x: 1, y: 2, z: 3, }
eu -e '{ x: 1 y: 2 z: 3 }'
x: 1
y: 2
z: 3
Symbols
Symbols are written with a colon prefix and behave like interned strings. They are used as keys and as lightweight identifiers:
status: :active
tag: :important
status: active
tag: important
Function Declarations
Adding a parameter list creates a function declaration:
greet(name): "Hello, {name}!"
double(x): x * 2
message: greet("World")
result: double(21)
message: Hello, World!
result: 42
Functions are not rendered in the output -- only property values appear. Functions can take multiple parameters:
add(x, y): x + y
total: add(3, 4)
total: 7
Operator Declarations
You can define custom infix operators using symbolic names:
(x <+> y): [x, y]
pair: 1 <+> 2
pair:
- 1
- 2
Prefix and postfix unary operators are also possible:
(!! x): x * x
squared: !! 5
squared: 25
Operator precedence and associativity are controlled through metadata annotations (covered below). See the Operators chapter for full details.
Note: While function declarations are namespaced to their block, operators do not have a namespace and are available only where they are in scope.
Units: Top-Level Blocks
The top-level of a .eu file is itself a block, called a unit.
It does not need surrounding braces. So this file:
name: "Alice"
age: 30
...is equivalent to a block { name: "Alice" age: 30 } and produces:
name: Alice
age: 30
Comments
Comments start with # and continue to the end of the line:
# This is a comment
name: "Alice" # inline comment
Declaration Metadata
Metadata can be attached to any declaration by placing it between a leading backtick and the declaration:
` "A friendly greeting"
greeting: "Hello!"
` { doc: "Add two numbers" }
add(x, y): x + y
A bare string is shorthand for documentation metadata.
Some metadata keys activate special behaviour:
:suppress-- hides the declaration from output:target-- marks the declaration as an export target:main-- marks the default target
` :suppress
helper(x): x + 1
` { target: :my-output }
output: {
result: helper(41)
}
Running eu file.eu -t my-output renders only the output block.
Block and Unit Metadata
A single expression may precede the declarations in any block and is treated as metadata for that block. At the top level of a file (the unit), this means the first item, if it is an expression rather than a declaration, becomes metadata for the entire unit:
{ doc: "Configuration generator" }
host: "localhost"
port: 8080
Scope and Visibility
Names declared in a block are visible within that block and in any nested blocks:
x: 99
inner: {
y: x + 1 # x is visible here
}
x: 99
inner:
y: 100
Names in nested blocks can shadow outer names:
x: 1
inner: {
x: 2
y: x # refers to inner x
}
x: 1
inner:
x: 2
y: 2
Warning: Be careful with self-reference. Writing
name: nameinside a block creates an infinite recursion, because the declarationnamerefers to itself. This is true regardless of whethernameis defined in an outer scope.
Key Concepts
- Blocks are ordered collections of named values (like JSON objects or YAML mappings)
- Property declarations bind a name to a value
- Function declarations bind a name to a function (not rendered in output)
- Operator declarations define custom infix, prefix, or postfix operators
- Metadata annotations control export, documentation, and other special behaviour
- The top-level file is a unit: an implicit block without braces