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::AdamAstParseras an alternative tocrate::AdamParser’s directadam_rs::Sheetconstruction. Method bodies and cell initializers reference [cel_parser::Expr]/[cel_parser::lex_lexer::Literal] directly. Carries no resolved types, noTypeRegistrylookups, 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-Sheetphase, mirroring [cel_parser::AstContext]’s design. - type_
registry - Type registry for adam-lang cell declarations.
Structs§
- Adam
AstParser - Parses adam-lang source strings into
ast::Sheettrees, instead of executing into a liveadam_rs::Sheet(seecrate::AdamParserfor that path). - Adam
Parser - Parses adam-lang source strings into live
ParsedSheets (sheet + cell names). - Parse
Error - A parse error carrying the original
proc_macro2::Spanof the offending token or expression start, plus an optional end span for range errors. - Parsed
Sheet - The result of
AdamParser::parse_str: a live [Sheet] plus the declared cell names, in source declaration order.
Enums§
- Format
Source Error - Error returned by
format_sourcewhensourcecan’t be safely formatted.
Functions§
- attach_
trivia - Recovers comments/blank-lines from every gap in
sheet— a leading comment before thesheetkeyword itself, its own top-level items, and every nestedrelationship/conditionalbody — attaching each to the nearest following node. - check_
sheet - Checks
sheetagainst registered types and operation/record metadata, returning diagnostics. Never fails — an absent Adam annotation, an unresolved identifier, an Adamcell/source/outannotationregistrydoesn’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 uselookup’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
sheetback to adam-lang source text — see the module doc for the printing rules. - format_
source - Parses and formats
sourceas a complete adam-lang sheet, matchingadam-lsp’stextDocument/formattinghandler exactly. - prepare_
compound_ cell_ formatter - Prepares canonical compound-cell formatting from the cell owner’s debug capability.