use std::collections::BTreeSet;
use std::fs;
use std::path::Path;
fn main() {
generate_grammar_features();
}
fn generate_grammar_features() {
let out_dir = std::env::var("OUT_DIR").unwrap();
let dest_path = Path::new(&out_dir).join("grammar_features.rs");
let pest_path = Path::new("../shape-ast/src/shape.pest");
let rules = if pest_path.exists() {
extract_pest_rules(pest_path)
} else {
eprintln!("Warning: pest grammar not found at {:?}", pest_path);
BTreeSet::new()
};
let rules_array: String = rules
.iter()
.map(|r| format!(" \"{}\",", r))
.collect::<Vec<_>>()
.join("\n");
let generated = format!(
r#"// Auto-generated from shape.pest - DO NOT EDIT
// This file contains all grammar rule names extracted from the pest grammar.
// Generated by shape-vm/build.rs
/// All grammar rules extracted from shape.pest
pub const PEST_RULES: &[&str] = &[
{}
];
"#,
rules_array
);
fs::write(&dest_path, generated).expect("Failed to write grammar_features.rs");
println!("cargo:rerun-if-changed=../shape-ast/src/shape.pest");
}
fn extract_pest_rules(path: &Path) -> BTreeSet<String> {
let content = fs::read_to_string(path).expect("Failed to read pest grammar");
let mut rules = BTreeSet::new();
for line in content.lines() {
let line = line.trim();
if line.is_empty() || line.starts_with("//") {
continue;
}
if let Some(eq_pos) = line.find('=') {
let before_eq = line[..eq_pos].trim();
if is_valid_rule_name(before_eq) {
let after_eq = line[eq_pos + 1..].trim();
if after_eq.starts_with('{')
|| after_eq.starts_with("_{")
|| after_eq.starts_with("@{")
|| after_eq.starts_with("${")
|| after_eq.starts_with("!{")
{
rules.insert(before_eq.to_string());
}
}
}
}
rules
}
fn is_valid_rule_name(s: &str) -> bool {
!s.is_empty()
&& s.chars()
.next()
.is_some_and(|c| c.is_ascii_alphabetic() || c == '_')
&& s.chars().all(|c| c.is_ascii_alphanumeric() || c == '_')
}