Core Language
All parsers must support the core language, which defines a minimal set of concepts, features, and value types. This chapter introduces these essential elements. Here, you’ll learn about fundamental topics such as file encoding, comments, sections, names, and values in a concise format.
File Encoding and Line Breaks
Let’s begin with the basics. All ELCL configuration files must be encoded in UTF-8. ELCL supports nearly all valid UTF-8 Unicode characters, except for certain control characters.
ELCL is a line-based configuration language, meaning that line breaks are syntactically significant. Line breaks terminate comments, separate statements, and influence overall structure. The following line-ending styles are supported, and can be mixed within the same document:
A single newline character (
\n
).A Windows-style carriage return followed by a newline (
\r
and\n
).
Document Structure
Every configuration document in ELCL is made up of a sequence of lines, separated by line breaks. Lines that are empty or contain only comments are ignored by the parser. Configuration data is organized into one or more sections, with each section containing any number of name-value pairs. Each pair assigns a value to a given name within its section, effectively configuring the settings for that section.
# A comment
[first_section]
Name1: "A text value" # Comment
Name2: 1'000
last_name: 1 # Another comment.
[another_section.subsection] # Comment
Name: "more keys"
Names
In ELCL, names are used to identify both sections and name-value pairs. Names are always case-insensitive, meaning variations like example
, EXAMPLE
, and eXaMpLe
are treated as the same name.
Name Rules
Names in ELCL follow specific rules to ensure consistency and readability. They can include letters (a
– z
), digits (0
– 9
), underscores (_
), and spaces (
). However, there are a few important constraints:
Names must start with a letter: Names cannot begin with a digit or an underscore. For example,
100days
or_example
are invalid names.Spaces and underscores are interchangeable: Spaces in names are automatically converted to underscores. This means that
Example Name
is treated asexample_name
.No consecutive underscores: Names cannot contain consecutive underscores or multiple spaces in a row. This prevents names like
this__name
orthis name
from being valid.No trailing underscores: Names must not end with an underscore. For instance,
example_
is not considered valid.
These rules ensure that names in ELCL are consistent, readable, and unambiguous.
[ Example Section ] # Interpreted as: example_section
DNS Host: "127.0.0.1" # Interpreted as: dns_host
Sections
In ELCL, a section begins with a name enclosed in square brackets [
and ]
. After the section name, you can define any number of name-value pairs. Empty sections—those without any name-value pairs—are also valid.
[section]
Name1: "Value"
Name2: "Value"
[empty_section] # Empty sections are allowed.
A section must start at the beginning of a line, with no spacing before the opening square bracket [
. However, spacing around the section name inside the brackets is allowed.
Additionally, sections can be visually separated using any number of minus -
characters surrounding the section name. These characters are ignored by the parser but can help with visual organization in configuration files.
[ section1 ] # Spacing around section names is allowed.
Name: "Value"
-----------------[ section2 ]-------------------- # Surrounding minus signs are allowed.
Name: "Value"
[ section3 ]-------------------------------------
Name: "Value"
-------------------------------------[ section4 ]
Name: "Value"
Subsections
In ELCL, subsections are created by joining two or more section names with a .
character, forming a name path. This allows you to define nested configurations within a document’s structure.
[section.subsection.one]
Name: "Value"
[ section . subsection . another_one ] # Spacing around the '.' is also allowed.
Absolute and Relative Sections
If a section name begins with a .
character, it is considered a relative section. Otherwise, it is treated as an absolute section.
An absolute section starts from the document root. Each additional name separated by a dot represents a deeper level in the hierarchy.
A relative section refers to a subsection of the most recently defined absolute section. Each relative section resets the hierarchy relative to the last absolute section, rather than nesting further under previous subsections. An ELCL document must never begin with a relative section.
[root] # => root (absolute)
[.section1.sub] # => root.section1.sub (relative to root)
[.section2] # => root.section2 (relative to root)
[.section3.x] # => root.section3.x (relative to root)
[another.sub] # => another.sub (absolute)
[.section1.sub] # => another.sub.section1.sub (relative to another.sub)
[.section2] # => another.sub.section2 (relative to another.sub)
[.section3.x] # => another.sub.section3.x (relative to another.sub)
Name-Value Pairs
Name-value pairs in ELCL configure individual settings within a section. Each pair must begin with a name at the start of a line, followed by a value separator, which can be either a colon (:
) or an equal sign (=
). After the separator comes the actual value, which is assigned to the name.
[server]
dns name: "ecl.example.com" # Assigns "ecl.example.com" to `server.dns_name`
port: 9080 # Assigns 9080 to `server.port`
Spacing and Formatting
You are free to add spaces around the value separator for readability:
[server]
dns name : "ecl.example.com" # Spacing around the name and value is allowed.
port : # The value can begin on the next line,
9080 # but indentation is required.
If the value starts on the next line, it must be indented with at least one space or tab. Also, there should be no empty line between the name and the start of the value.
Values
A value must follow a name and the value separator. You can write the value either on the same line or start it on the following line. There are two key rules to remember:
A value must not start or continue at the beginning of a line.
There must be no empty line between the name and its value.
The example below shows two valid placements of values. The first value is placed on the same line as the name, while the second value begins on the next line. If you place the value on a new line, you must indent it with at least one space (
) or tab (\t
) character.
[value_placement]
same_line: 7000
next_line:
7000
Core Value Types
The core language defines three primary value types, which are fundamental to configuring settings in ELCL:
Integer: Represents whole numbers, such as
42
or-1000
. Integers are commonly used for numeric settings like port numbers or limits.Boolean: Represents logical values. Common literals include
true
andfalse
, but ELCL supports multiple synonyms, such asyes
andno
, oron
andoff
. Booleans are ideal for toggling features on or off, such as enabling or disabling a module.Text: Represents strings of characters enclosed in quotes, such as
"example text"
. Text values are used for settings that require descriptive information, like file paths or descriptions.
While these are the core value types, ELCL also supports additional types that extend its capabilities beyond the core language. These extended types will be discussed in detail in later sections.
Value Formats
Each value type supports various formats, offering flexibility in how values can be represented in configuration files. For example, an integer can be written as a decimal, hexadecimal, or even using digit grouping, depending on the allowed formats.
These formats provide multiple ways to represent the same value type, making ELCL adaptable to different scenarios and user preferences.
Integer Values
An integer value in ELCL represents a whole positive or negative number. It can be written in decimal, hexadecimal, or binary format. Consider the following examples:
[integer_values]
decimal : -12'000'000
hexadecimal : 0xAC12'08CD
binary : 0b10100100'00010101
Decimal Format
Numbers without any prefix are interpreted as decimal values. For example, 120
is a regular decimal number. Note that ELCL does not support an octal number format, so avoid leading zeros (e.g., 0120
) to prevent confusion, as they might be misinterpreted as octal in other contexts.
[decimal_numbers]
value a: 0 # Zero value
value b: 12'000 # Positive decimal value with separators
value c: -987654321 # Negative decimal value
Hexadecimal Format
Numbers prefixed with 0x are treated as hexadecimal values. In this format, you can use both lower-case and upper-case letters a
– f
interchangeably, as ELCL is case-insensitive. Leading zeros are allowed, but the total number of digits must not exceed the parser’s limit, typically 16 digits.
[hexadecimal_numbers]
value a: 0x0 # Zero value
value b: 0x2ee0 # Positive hexadecimal value
value c: -0x3ADE'68B1 # Negative hexadecimal value with separators
Binary Format
Numbers prefixed with 0b are interpreted as binary values, consisting only of the digits 0
and 1
. Leading zeros are also permitted. Binary numbers are right-aligned, meaning the last digit always represents the least significant bit.
[binary_numbers]
value a: 0b0 # Zero value
value b: 0b00101110'11100000 # Positive binary value with separators
# ↓ Negative binary value with separators
value c: -0b111010'11011110'01101000'10110001
Negative and Positive Numbers
Any of these formats can represent negative numbers by prefixing the value with a minus sign (-
). For example, -0x1A
and -42
are valid negative hexadecimal and decimal values, respectively. Note that setting the most significant bit (the leftmost bit) in binary format results in a negative value when interpreted as a signed integer. Also, it is possible to explicitly prefix any number with a plus sign (+
).
The four values in the following example represent the same negative integer using different formats:
[negative_numbers]
value a: -987'654'321 # Decimal format
value b: -0x3ADE68B1 # Hexadecimal format
value c: -0b111010'11011110'01101000'10110001 # Binary format
# ↓ Long binary format (negative 64-bit value)
value d: 0b11111111'11111111'11111111'11111111'11000101'00100001'10010111'01001111
Digit Separators
To enhance readability, you can use a digit separator '
between digits in any number format. For instance, 12'345'678
or 0xAC12'08CD
are valid uses. However, there are some restrictions:
Separators must be placed between digits only, not at the beginning or end of a number.
Consecutive separators (e.g.,
12''345
) are not allowed.
By following these rules, you can format numbers in a way that is both flexible and clear, making your configurations easier to read and understand.
Boolean Values
Boolean values represent two possible states, typically true
or false
. They are ideal for toggling features, enabling or disabling functionalities, or providing affirmative or negative responses to configuration options. To improve readability, ELCL offers several synonyms for “true” and “false” that can be used interchangeably.
ELCL is also case-insensitive, meaning there is no difference between true
, True
, TRUE
, or even tRuE
—all are interpreted as the same value.
[boolean_values]
ecl_is_nice: true # True using lowercase
light: Off # False using a synonym
motor: ENABLED # True using uppercase synonym
stop_now: yes # True using another synonym
Boolean Synonyms
The table below lists all the boolean literals recognized by ELCL. You can use any of these for a true or false value, based on your preference.
True |
False |
---|---|
Yes |
No |
On |
Off |
Enabled |
Disabled |
Text Values
The third core value type in ELCL is text. Text values are enclosed in double quotes ("
). They are used for storing strings of characters, which can include almost any Unicode character, making them ideal for representing descriptive or freeform information.
[text_values]
value a: "" # An empty text
value b: "Text can be anything" # A basic text value
value c: " Spaces are preserved " # Text with preserved spaces
The core language supports only single-line text values, as shown above. Multi-line text values are also available in ELCL and will be covered in a later section.
Text Content Restrictions
You can use almost any Unicode character in text values, except for the following:
Control characters and line breaks (
\r
and\n
).The double quote (
"
).The backslash (
\
).
To include these characters in your text values, you must use escape sequences.
Text Escape Sequences
Escape sequences allow you to include special characters in text values by using a backslash (\
) followed by a specific sequence of characters. The following table provides an overview of all supported escape sequences in ELCL:
Sequence |
Description |
---|---|
\\ |
Inserts a backslash ( |
\" |
Inserts a double quote ( |
\$ |
Inserts a dollar sign ( |
\n |
Inserts a newline control code ( |
\r |
Inserts a carriage-return control code ( |
\t |
Inserts a tab character ( |
\uXXXX |
Inserts a Unicode character represented by the four-digit hexadecimal code |
\u{YYYY} |
Inserts a Unicode character represented by the hexadecimal code |
The escape sequence \uXXXX
requires a four-digit hexadecimal code, while \u{YYYY}
provides more flexibility, allowing one to eight digits. This flexibility helps when you need to represent characters beyond the Basic Multilingual Plane or when a fixed digit length is inconvenient.
ELCL intentionally supports only a limited set of escape sequences to keep the language simple and easy to read. For instance, rarely used escape sequences like “form feed” or “vertical tab” are not included.
Note
The escape sequence for the dollar sign ($
) is included in the core language to establish a foundation for a unified placeholder system or text interpolation. Although the specification includes a recommended placeholder syntax, support for text replacements during configuration parsing is an optional feature. Full-featured parsers must implement this functionality, allowing dynamic substitution of placeholder values as the configuration is processed.
All three text values in the example below are equivalent, despite being represented with different escape sequences:
[escape_sequences]
text a: "ψ\"ありがとう\"😄" # Direct Unicode characters, except for the double quotes
text b: "\u{3c8}\u{22}\u3042\u308a\u304c\u3068\u3046\u{22}\u{1f604}" # Lowercase escape sequences
text c: "\U{3C8}\U{22}\U3042\U308A\U304C\U3068\U3046\U{22}\U{1F604}" # Uppercase escape sequences
Meta Values
Meta values are special values that are part of the configuration language itself and its parser, not the actual configuration data. These values have a name that is starting with the “at”-character @
and they must always be defined at the beginning of a document, in the document root.
The core language requires all meta values to be placed before the first section in the document, and like name-value pairs, each meta value cannot be defined more than once. There are also meta commands, like @include
, which will be discussed later. Meta commands can be defined later in the document, between section blocks.
The core language supports the following three meta values:
@signature: "..."
@version: "1.0"
@features: "regex, timedelta"
It’s important to note that all meta values are completely optional.
The Version Value
The version meta value specifies the version of ELCL for which the configuration was written. This value takes a text with the version number in the format <major>.<minor>
. Currently, ELCL is at version 1.0
, which is the only valid value at this time.
@version: "1.0"
[first_section]
name: "value"
When a parser reads the version meta value, it must compare it to its own supported version of the language. For example, if a future version “1.2” of ELCL introduces new features, and a parser that only supports version “1.0” encounters @version: "1.2"
, it must stop parsing. This is to prevent the parser from misinterpreting the configuration due to unsupported language features.
This value also helps ensure backward compatibility. If a future version “2.0” introduces breaking changes, a parser reading @version: "1.0"
can switch to a legacy mode to handle the older syntax correctly.
The Features Value
The features meta value specifies a list of features that the document requires. This value takes a space-separated list of feature identifiers as text. If a parser does not support a specified feature, it must stop parsing the configuration.
@features: "regex timedelta"
[first_section]
name: "value"
This meta value ensures that a configuration document is compatible with different parsers. If a parser encounters an unsupported feature, it can provide a clear error message, preventing confusion that may arise from encountering unexpected character sequences in the document.
The Signature Value
The signature value, if present, must appear in the first line of the document. This value is used to verify the integrity of the document. If a parser or application does not support signature verification, it must stop with an error when it encounters this value.
@signature: "..."
[first_section]
name: "value"
This meta value is part of the core language as a security measure. If a document has been signed, the signature must be validated to ensure the document’s integrity. If a parser cannot verify the signature, it should not ignore it. Instead, it should require the signature to be manually removed before processing the document. This prevents potentially unauthorized changes to the configuration from going unnoticed.
Parser Meta Values
Parser implementations are allowed to provide additional meta values, that must start with @parser_...
. Like meta values of the ELCL, parser meta values must be optional.
Name Paths and Name Conflicts
ELCL is designed to keep configurations simple and easy to understand. To maintain this simplicity, each value can be defined only once. Overwriting existing values is not allowed, and ELCL does not support “inheritance” or “templates.” These features are often sources of unintended complexity and can lead to configuration errors, particularly in large and complex systems.
The rules are intentionally straightforward: every section and value is assigned a unique name path starting from the document root. Once a name path has been assigned through a written definition in the configuration, it cannot be redefined or overwritten later in the document.
Multiple Values with the Same Name
In a single configuration, two values cannot share the same name. Attempting to define a value more than once results in an error.
[block.one] # Creates the value map "block.one"
name: "value a" # Assigns the value "value a" to "block.one.name"
name: "value b" # Error! "block.one.name" is already defined.
Multiple Sections with the Same Name
The same rule applies to sections. Two sections in the same configuration cannot share the same name.
[block.one] # Creates the section "block.one"
name: "value a"
[block.one] # Error! The section "block.one" is already defined.
name: "value b"
This rule also prevents sections from being “continued” later in the document.
[block one]
value a: 123
value b: 123
value c: 123
[another block]
# ... other definitions ...
[block one] # Error! The section "block one" was already defined.
value d: 123
value e: 123
value f: 123
Conflicts between Value and Section Names
A section defines a unique instance with its name, behaving similarly to any other value. Consequently, a section cannot share the same name path with an existing value.
[block] # Creates the section "block"
name: "value a" # Assigns the value "value a" to "block.name"
[block.name] # Error! A value already exists for "block.name".
Likewise, no value can share the same name path as a section.
[block.name] # Creates the section "block.name"
[block]
name: "value b" # Error! The name path "block.name" is already in use.
The order in which sections or values are defined does not matter—the conflict will occur regardless. The only difference is whether the error is raised for the value or the section, depending on the order of definition.
No Conflicts with Intermediate Sections
Intermediate sections are implicitly created when defining a name path. These sections are not considered “defined” until explicitly declared, allowing them to be defined later in the same document.
[one.two.three] # Creates the intermediate sections "one" and "one.two".
value: 123
[one.two] # This is allowed! Defines the section "one.two".
value: 123
[one] # This is allowed! Defines the section "one".
value: 123
This exception from the rule is only valid for intermediate sections being defined, the name path for intermediate sections cannot be used by values later in the same document.
[one.two.three] # Creates the intermediate sections "one" and "one.two".
[one]
two: 123 # Error! The name "one.two" is already in use.
Core Language: Key Takeaways and Beyond
In this chapter, we explored the essential components of the ELCL core language. We discussed how to structure configuration documents using sections, subsections, and name-value pairs, and how to effectively utilize comments for clarity. We also covered the fundamental value types—integers, booleans, and text—along with their formats, rules, and best practices.
Mastering these core concepts is crucial for working with ELCL, as they provide the foundational building blocks for all configurations. Whether you are defining simple settings or creating more complex structures, the core language ensures consistency, readability, and flexibility in your configurations.
While the core language is sufficient for basic configurations and essential for lightweight micro-parsers on microcontrollers, real-world scenarios on desktop applications often require more than just the basics. This is where ELCL’s Standard Features come into play. These features significantly extend the capabilities of the core language, enabling more sophisticated configurations.
With additional value types like floating-point numbers, dates, times, and byte-data, as well as powerful tools such as multi-line text and code blocks, value and section lists, text names, and the “include” meta command, ELCL provides the flexibility needed for complex and robust configurations.
In the next chapter, we’ll explore these standard features in detail, learning how they can help you tackle advanced configuration challenges with ease. Let’s move forward and discover the possibilities that await with ELCL’s extended capabilities.
Comments
In ELCL, a comment begins with the
#
character and continues until the end of the line. Comments are ignored entirely during parsing and can contain any text, except for some control characters that are disallowed in ELCL documents.Comments can only appear outside of a value. For example, a
#
character within a text or code value does not initiate a comment.