Skip to main content

Crate adam_lang

Crate adam_lang 

Source
Expand description

§adam-lang

A DSL parser for property models. Parses an adam-lang source string and produces a live ParsedSheet (sheet plus cell names in declaration order).

§Grammar

sheet              = "sheet" identifier "{" { sheet_item } "}".
sheet_item         = [ doc_comment ] (cell_decl | relationship_decl | conditional_decl | out_decl
                       | source_decl).
cell_decl          = "cell" identifier cell_type_init [ cell_filter ] [ require_block ] ";".
cell_filter        = "filter" expression.
cell_type_init     = (":" type_expr ["=" expression]) | ("=" expression).
source_decl        = "source" identifier cell_type_init [ cell_filter ] [ require_block ] ";".
type_expr          = identifier [ "(" type_expr { "," type_expr } ")" ]
                   | "[" type_expr "]"
                   | "(" [ type_expr ["," [ type_expr { "," type_expr } ]] ] ")".
relationship_decl  = "relationship" "{" { binding } "}".
binding            = binding_target ":=" expression ";".
binding_target     = identifier | "(" identifier { "," identifier } [ "," ] ")".
conditional_decl   = "conditional" expression "{" { conditional_branch } "}".
conditional_branch = (literal_pattern | "_") "=>" "{" { relationship_decl } "}" [ "," ].
out_decl           = "out" identifier [":" type_expr] ":=" expression
                       [ cell_filter ] [ require_block ] ";".
require_block      = "require" "{" { requirement } "}".
requirement        = [ "@" identifier ] expression ";".

The design spec for cell_decl also calls for an optional trailing ":=" expression clause, not shown above because this crate does not yet implement it — see docs/superpowers/specs/2026-08-19-adam-lang-syntax-design.md’s “Explicitly out of scope” section; it’s deferred pending a forward-reference/hoisting decision. Only cell_decl’s "=" expression one-time initializer and its optional cell_filter/require_block clauses are implemented today.

expression and its descendants (literal, identifier, and the rest of the CEL expression grammar), as well as literal_pattern (a bare literal, optionally negated by a leading -, matching Rust’s own LiteralPattern rule — CEL has no constant-expression syntax in pattern position), are defined by cel_parser — see that crate’s own # Grammar section. That embedded CEL grammar now includes postfix array type ascriptions such as [left, right]: [Custom] and []: [[i32]]. The ascription names the complete array type, resolves leaf names through the active TypeRegistry, and reuses recursive tuple/array/callable type syntax. Persistent cells support arbitrary compositions of these shapes and registered generic leaves. Arrays default to empty, tuples default recursively, and callables require an initializer. Every stored cell requires cloning and value equality: CEL permits callable storage, but Adam rejects a callable cell without equality rather than inventing function equality. Registered concrete leaves keep Sheet’s Rust read/write access; compound owners use ParsedSheet::read_compound, ParsedSheet::write_compound, and ParsedSheet::source_compound with canonical representation witnesses. Relationships, filters, outputs, conditionals, and requirements validate complete frames at construction and run the existing raw CEL engine without recursively checking types on propagation. The private CellValue owns one complete compound value, not individual erased array elements. Checked host writes reject mismatched recursive witnesses before mutation. Reads clone independent owners; source reads preserve aspirations. Formatting is a separate capability: prepare_compound_cell_formatter binds it without adding equality or clone requirements.

use adam_lang::{AdamParser, TypeRegistry};
use cel_parser::OpLookup;
use cel_runtime::{Array, ArrayOf, Cons, IntoCStackList, Leaf, Nil};
type Rows = ArrayOf<Cons<Leaf<i32>, Cons<Leaf<bool>, Nil>>>;
let mut parsed = AdamParser::new(TypeRegistry::new(), OpLookup::new())
    .parse_str("sheet s { source rows: [(i32, bool)]; out copy := rows; }").unwrap();
let id = parsed.cell_names["rows"].0;
parsed.write_compound::<Rows>(id, Array::from_vec(vec![(7, true).into_c_stack_list()])).unwrap();
parsed.propagate().unwrap();
assert_eq!(parsed.read_compound::<Rows>(parsed.output_names["copy"]).unwrap().len(), 1);
assert_eq!(parsed.source_compound::<Rows>(id).unwrap().as_slice()[0].1, 7);

A cell_filter’s expression names no explicit parameter list: _ always refers to the candidate value being conformed (of the filtered cell’s own declared type), and every other identifier that names an already-declared cell is a dependency, deduced exactly as a relationship binding’s/out declaration’s/conditional’s own expression deduces its inputs — see ast::CellFilter. _ is reserved inside a filter expression only.

§Example

AdamParser::new takes an OpLookup instance. See OpLookup::push_library_scope for how to install one (e.g. cel-std) before parsing.

use adam_lang::{AdamParser, TypeRegistry};
use cel_parser::OpLookup;

let mut parser = AdamParser::new(TypeRegistry::new(), OpLookup::new());
let parsed = parser.parse_str(r#"
    sheet image_resize {
        cell width:  f64 = 1920.0;
        cell height: f64 = 1080.0;
        cell area:   f64;
    }
"#).unwrap();

Built-in and registered Adam types are both visible inside embedded CEL array ascriptions:

use adam_lang::{AdamParser, TypeRegistry};
use cel_parser::OpLookup;
use cel_runtime::{ArrayOf, Leaf, ValueRepr};

#[derive(Clone, Debug, PartialEq)]
struct Custom(i32);

#[derive(Clone)]
struct CountFn;

let mut lookup = OpLookup::new();
lookup.push_scope(|name, segment, arity, _span| match (name, arity) {
    ("left", 0) => {
        segment.op0(|| Custom(1));
        Ok(true)
    }
    ("right", 0) => {
        segment.op0(|| Custom(2));
        Ok(true)
    }
    ("count", 0) => {
        segment.op0(|| CountFn);
        Ok(true)
    }
    ("()", 2) => {
        let array = &segment.peek_stack_infos(1)[0].value_type;
        if array.same_shape(&ArrayOf::<Leaf<i32>>::value_type()) {
            segment.op2_repr::<Leaf<CountFn>, ArrayOf<Leaf<i32>>, Leaf<i32>, _>(
                |_callee, values| values.len() as i32)?;
        } else if array.same_shape(&ArrayOf::<Leaf<Custom>>::value_type()) {
            segment.op2_repr::<Leaf<CountFn>, ArrayOf<Leaf<Custom>>, Leaf<i32>, _>(
                |_callee, values| values.len() as i32)?;
        } else {
            segment.op2_repr::<Leaf<CountFn>, ArrayOf<ArrayOf<Leaf<Custom>>>, Leaf<i32>, _>(
                |_callee, values| values.len() as i32)?;
        }
        Ok(true)
    }
    _ => Ok(false),
});

let mut types = TypeRegistry::new();
types.register_no_default::<Custom>("Custom");
let mut parser = AdamParser::new(types, lookup);
let parsed = parser.parse_str(
    "sheet s { \
        cell scalar: i32 = count([1, 2]: [i32]); \
        cell values: i32 = count([left, right]: [Custom]); \
        cell empty: i32 = count([]: [Custom]); \
        cell nested: i32 = count([[]: [Custom]]: [[Custom]]); \
    }",
).unwrap();

let (scalar, _) = parsed.cell_names["scalar"];
assert_eq!(*parsed.read::<i32>(scalar).unwrap(), 2);
let (values, _) = parsed.cell_names["values"];
assert_eq!(*parsed.read::<i32>(values).unwrap(), 2);
let (empty, _) = parsed.cell_names["empty"];
assert_eq!(*parsed.read::<i32>(empty).unwrap(), 0);
let (nested, _) = parsed.cell_names["nested"];
assert_eq!(*parsed.read::<i32>(nested).unwrap(), 1);

Embedded CEL array diagnostics stay exact:

use adam_lang::{AdamParser, TypeRegistry};
use cel_parser::OpLookup;

let mut parser = AdamParser::new(TypeRegistry::new(), OpLookup::new());
let err = parser
    .parse_str("sheet s { cell values = []: [Missing]; }")
    .unwrap_err();
assert_eq!(err.message(), "unknown type `Missing`");
use adam_lang::{AdamAstParser, TypeRegistry, check_sheet};

let sheet = AdamAstParser::new()
    .parse_str("sheet s { out values := [0, 1]: [f64]; }")
    .unwrap();
let diagnostics = check_sheet(&sheet, &TypeRegistry::new(), &cel_parser::OpLookup::new());
assert_eq!(diagnostics.len(), 1);
assert_eq!(
    diagnostics[0].message(),
    "array elements must match the annotation exactly: expected `f64`, found `i32`"
);

Re-exports§

pub use type_registry::TypeRegistry;

Modules§

ast
A span-carrying adam-lang structural AST, built by crate::AdamAstParser as an alternative to crate::AdamParser’s direct adam_rs::Sheet construction. Method bodies and cell initializers reference [cel_parser::Expr]/[cel_parser::lex_lexer::Literal] directly. Carries no resolved types, no TypeRegistry lookups, and never fails on semantic grounds (unknown type name, literal/type mismatch, undeclared cell, arity mismatch) — those checks are deferred to a later, separate compile-to-Sheet phase, mirroring [cel_parser::AstContext]’s design.
type_registry
Type registry for adam-lang cell declarations.

Structs§

AdamAstParser
Parses adam-lang source strings into ast::Sheet trees, instead of executing into a live adam_rs::Sheet (see crate::AdamParser for that path).
AdamParser
Parses adam-lang source strings into live ParsedSheets (sheet + cell names).
ParseError
A parse error carrying the original proc_macro2::Span of the offending token or expression start, plus an optional end span for range errors.
ParsedSheet
The result of AdamParser::parse_str: a live [Sheet] plus the declared cell names, in source declaration order.

Enums§

FormatSourceError
Error returned by format_source when source can’t be safely formatted.

Functions§

attach_trivia
Recovers comments/blank-lines from every gap in sheet — a leading comment before the sheet keyword itself, its own top-level items, and every nested relationship/conditional body — attaching each to the nearest following node.
check_sheet
Checks sheet against registered types and operation/record metadata, returning diagnostics. Never fails — an absent Adam annotation, an unresolved identifier, an Adam cell/source/out annotation registry doesn’t resolve, or a custom call without metadata falls back to [cel_parser::Ty::Any] and are silently skipped, not reported. CEL array annotations inside expressions use the same registry-backed CEL resolver, so unknown leaf names such as []: [Missing] are still reported. Known custom annotations retain their complete leaf descriptors. Constructors and members use lookup’s schemas/signatures, not value names; unknown constructors and incompatible known fields/operands produce diagnostics.
debug_compound_cell
Formats a canonical compound cell at the checked host boundary.
format_sheet
Pretty-prints sheet back to adam-lang source text — see the module doc for the printing rules.
format_source
Parses and formats source as a complete adam-lang sheet, matching adam-lsp’s textDocument/formatting handler exactly.
prepare_compound_cell_formatter
Prepares canonical compound-cell formatting from the cell owner’s debug capability.