// CON / convel surface syntax (PEG / Pest).
//
// Aligns with docs/orgmode/spec.org (CON v2–v3). This grammar defines the
// *syntactic* shape of one or more concatenated frames. Semantic rules
// (atom counts matching header N_i, Component index order, JSON metadata
// keys, fixed-mask values, validate=true geometry) are enforced by the
// reference implementation in src/parser.rs — not by this PEG alone.
//
// Hot-path I/O does not depend on Pest; this file is the formal grammar
// artifact shipped with the repo, validated in tests via the optional
// `grammar` feature / dev-dependencies.
//
// Section row arities (spec §sections):
// vector: velocities, forces, magmoms → v0 v1 v2 fixed [atom_id]
// scalar: energies, charges, spins → value fixed [atom_id]
/// One or more frames, no inter-frame marker (frames are concatenated).
file = { SOI ~ frame+ ~ EOI }
/// Header + per-type coordinate blocks + optional section blocks.
frame = { header ~ type_block+ ~ section* }
/// Nine-line header (lines 1–9 of the specification).
header = {
free_line ~ NL
~ free_line ~ NL
~ float3_line
~ float3_line
~ free_line ~ NL
~ free_line ~ NL
~ integer_line
~ integer_list_line
~ float_list_line
}
/// Free-form line (generator comment, JSON metadata, or reserved text).
/// Does not consume the trailing newline.
free_line = { (!NL ~ ANY)* }
float3_line = { ws* ~ float ~ ws+ ~ float ~ ws+ ~ float ~ ws* ~ NL }
integer_line = { ws* ~ integer ~ ws* ~ NL }
integer_list_line = { ws* ~ integer ~ (ws+ ~ integer)* ~ ws* ~ NL }
float_list_line = { ws* ~ float ~ (ws+ ~ float)* ~ ws* ~ NL }
/// One chemical-type block: symbol, "Coordinates of Component k", atom lines.
type_block = {
symbol_line ~ NL
~ coord_label ~ NL
~ vector_atom_line ~ (NL ~ vector_atom_line)*
~ NL?
}
symbol_line = { ws* ~ symbol ~ ws* }
/// Element / type symbol (letters, optional trailing digits/underscore).
symbol = @{ ASCII_ALPHA ~ (ASCII_ALPHANUMERIC | "_")* }
/// Wire labels may use legacy "component" casing (e.g. eOn sulfolene fixtures).
/// Pest ^string is case-insensitive ASCII.
coord_label = {
ws* ~ ^"Coordinates of Component" ~ ws+ ~ integer ~ ws*
}
/// Coordinate / 3-vector section row: v0 v1 v2 fixed_mask [atom_id]
vector_atom_line = {
ws* ~ float ~ ws+ ~ float ~ ws+ ~ float
~ ws+ ~ integer
~ (ws+ ~ integer)?
~ ws*
}
/// Scalar section row: value fixed_mask [atom_id]
scalar_atom_line = {
ws* ~ float
~ ws+ ~ integer
~ (ws+ ~ integer)?
~ ws*
}
/// Backward-compatible alias used by tests and older notes.
atom_line = { vector_atom_line }
/// Optional section: blank separator + homogeneous type blocks (vector or scalar).
/// After a type_block's trailing newline, one more newline is the blank line
/// required by the specification before a section.
section = {
ws* ~ NL
~ (section_type_block_vector+ | section_type_block_scalar+)
}
section_type_block_vector = {
symbol_line ~ NL
~ section_label_vector ~ NL
~ vector_atom_line ~ (NL ~ vector_atom_line)*
~ NL?
}
section_type_block_scalar = {
symbol_line ~ NL
~ section_label_scalar ~ NL
~ scalar_atom_line ~ (NL ~ scalar_atom_line)*
~ NL?
}
section_kind_vector = { ^"Velocities" | ^"Forces" | ^"Magmoms" }
section_kind_scalar = { ^"Energies" | ^"Charges" | ^"Spins" }
/// Union of all section kinds (docs / tests).
section_kind = { section_kind_vector | section_kind_scalar }
section_label_vector = {
ws* ~ section_kind_vector ~ ws+ ~ ^"of Component" ~ ws+ ~ integer ~ ws*
}
section_label_scalar = {
ws* ~ section_kind_scalar ~ ws+ ~ ^"of Component" ~ ws+ ~ integer ~ ws*
}
/// ASCII decimal float (optional sign, fraction, scientific exponent).
float = @{
"-"?
~ ASCII_DIGIT+
~ ("." ~ ASCII_DIGIT*)?
~ (("e" | "E") ~ ("+" | "-")? ~ ASCII_DIGIT+)?
}
integer = @{ "-"? ~ ASCII_DIGIT+ }
ws = _{ " " | "\t" }
NL = _{ "\r\n" | "\n" }