hedl-core 2.0.0

Core parser and data model for HEDL (Hierarchical Entity Data Language)
Documentation

hedl-core

The parsing engine for HEDL (Hierarchical Entity Data Language).

Why HEDL exists

Consider a dataset with 50,000 user records. In JSON, each object repeats the same keys: {"id": ..., "name": ..., "email": ...}. The parser processes those keys fifty thousand times, and roughly 40% of your file is redundant key names.

HEDL separates structure from data. You declare the schema once in the header, then write rows of values. The parser knows the column names upfront, which means less parsing work and smaller files. For entity-heavy datasets (users, products, events), this matters.

What this crate does

hedl-core parses HEDL text into typed Rust values. It handles the complete syntax: headers, matrix rows, nested objects, entity references, and tensor literals.

use hedl_core::{parse, Document};

let hedl = r#"
%S:User:[id, name, email]
---
users: @User
 | alice, Alice Smith, alice@example.com
 | bob, Bob Jones, bob@example.com
"#;

let doc = parse(hedl.as_bytes())?;

Values come out typed. A reference like @User:alice parses to Value::Reference, not a string. A list like [1, 2, 3] parses to Value::Tensor. Pattern matching gives you compile-time guarantees about what you're handling.

Schema-aware matrices

HEDL's core feature is typed tabular data. You define schemas in the header, then use them to structure your data:

let hedl = r#"
%S:Post:[id, author, title]
%S:Comment:[id, post, author, text]
---
posts: @Post
 | p1, @User:alice, First Post
 | p2, @User:bob, Second Post

comments: @Comment
 | c1, @Post:p1, @User:bob, Great post!
 | c2, @Post:p1, @User:alice, Thanks!
"#;

let doc = parse(hedl.as_bytes())?;

// Each row maps to the schema columns
// References like @Post:p1 are parsed as Reference values, not strings

The schema association (posts: @Post) tells the parser that each row follows the Post schema. Fields are accessed by column name, not by parsing key-value pairs repeatedly.

Entity references

References are first-class values. When you write @User:alice, the parser produces a Reference with the entity type ("User") and identifier ("alice") already separated. Building graphs or resolving relationships doesn't require string parsing at runtime.

Tensor literals

Arrays parse directly to Value::Tensor:

let hedl = r#"
---
metrics: @Metric[id, values, percentiles]
 | m1, 1250, [850, 1100, 1400, 2100, 3500]
 | m2, 890, [620, 780, 920, 1200, 1800]
"#;

let doc = parse(hedl.as_bytes())?;
// The [850, 1100, ...] values are Tensor types, ready for numerical work

Data model

After parsing:

Document holds the header metadata and a map of named entries.

Item represents a body entry: a scalar value, a nested object, or a matrix list.

MatrixList contains schema information and rows of data.

Node represents one row with typed fields and optional child nodes.

Value is a tagged enum: Null, Bool, Int, Float, String, Tensor, Reference, Expression.

Reference holds an entity type and identifier, parsed from @Type:id syntax.

Error handling

Parse errors include location and context:

use hedl_core::{parse, HedlError, HedlErrorKind};

match parse(data) {
    Ok(doc) => { /* use the document */ },
    Err(e) => {
        eprintln!("Error at line {}: {}", e.line, e.message);
        match e.kind {
            HedlErrorKind::Reference => { /* unresolved reference */ },
            HedlErrorKind::Schema => { /* schema mismatch */ },
            HedlErrorKind::Syntax => { /* malformed input */ },
            _ => { /* other issues */ }
        }
    }
}

The parser never panics on invalid input. All failures return structured errors.

When to use this crate

For most tasks, use higher-level tools: the hedl facade crate for a convenient API, hedl-cli for command-line operations, or hedl-lsp for editor integration.

Use hedl-core directly when building custom tooling (linters, converters, analyzers), embedding HEDL parsing in a larger application, or when you need direct access to the parsed AST without abstraction layers.

Safety

The crate uses minimal unsafe code in two internal modules: string interning and arena allocation. The public API is safe Rust. All errors return Result values with diagnostics.

Report security issues privately before public disclosure.

License

Apache-2.0