#![cfg(feature = "grammar")]
use pest::Parser;
use pest_derive::Parser;
#[derive(Parser)]
#[grammar = "../grammar/readcon.pest"]
pub struct ConGrammar;
pub fn parse_surface(input: &str) -> Result<(), pest::error::Error<Rule>> {
ConGrammar::parse(Rule::file, input).map(|_| ())
}
pub fn accepts(input: &str) -> bool {
parse_surface(input).is_ok()
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
use std::path::PathBuf;
fn fixture(name: &str) -> String {
let p = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("resources/test")
.join(name);
fs::read_to_string(&p).unwrap_or_else(|e| panic!("read {}: {e}", p.display()))
}
#[test]
fn grammar_accepts_core_fixtures() {
for name in [
"tiny_cuh2.con",
"cuh2.con",
"sulfolene.con",
"tiny_multi_cuh2.con",
"tiny_cuh2_vel_forces.con",
"tiny_cuh2_forces.con",
"tiny_cuh2.convel",
"tiny_multi_cuh2.convel",
"tiny_cuh2_charges_spins_magmoms.con",
] {
let text = fixture(name);
assert!(
accepts(&text),
"grammar rejected fixture {name}: {:?}",
parse_surface(&text).err()
);
}
}
#[test]
fn grammar_lockstep_hand_parser_accepts_same_fixtures() {
use crate::iterators::ConFrameIterator;
for name in [
"tiny_cuh2.con",
"tiny_cuh2_vel_forces.con",
"tiny_cuh2_charges_spins_magmoms.con",
"tiny_multi_cuh2.con",
] {
let text = fixture(name);
assert!(accepts(&text), "PEG reject {name}");
let frames: Result<Vec<_>, _> = ConFrameIterator::new(&text).collect();
assert!(
frames.as_ref().map(|f| !f.is_empty()).unwrap_or(false),
"hand parser failed {name}: {frames:?}"
);
}
}
#[test]
fn grammar_rejects_obvious_garbage() {
assert!(!accepts(""));
assert!(!accepts("not a con file\n"));
assert!(!accepts("only one line"));
}
#[test]
fn grammar_file_ships_six_section_kinds_and_scalar_rows() {
let p = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("grammar/readcon.pest");
assert!(p.is_file(), "expected shipped grammar at {}", p.display());
let body = fs::read_to_string(&p).unwrap();
assert!(body.contains("file = {"), "grammar should define file rule");
assert!(body.contains("Coordinates of Component"));
for kind in [
"Velocities",
"Forces",
"Energies",
"Charges",
"Spins",
"Magmoms",
] {
assert!(
body.contains(&format!("\"{kind}\"")),
"grammar missing section kind {kind}"
);
}
assert!(
body.contains("scalar_atom_line"),
"grammar must define scalar section rows"
);
assert!(
body.contains("vector_atom_line"),
"grammar must define vector section rows"
);
}
#[test]
fn grammar_accepts_scalar_and_vector_kind_snippets() {
let scalar = "\
Generated by test
{\"con_spec_version\":2,\"sections\":[\"charges\"]}
10.0 10.0 10.0
90.0 90.0 90.0
0 0
0 0
1
1
1.0
H
Coordinates of Component 1
0.0 0.0 0.0 0 0
H
Charges of Component 1
0.5 0 0
";
assert!(
accepts(scalar),
"scalar charges: {:?}",
parse_surface(scalar).err()
);
let vector = "\
Generated by test
{\"con_spec_version\":2,\"sections\":[\"magmoms\"]}
10.0 10.0 10.0
90.0 90.0 90.0
0 0
0 0
1
1
1.0
H
Coordinates of Component 1
0.0 0.0 0.0 0 0
H
Magmoms of Component 1
0.0 0.0 1.0 0 0
";
assert!(
accepts(vector),
"vector magmoms: {:?}",
parse_surface(vector).err()
);
}
}