use std::path::Path;
use panproto_schema::Schema;
use rustc_hash::FxHashMap;
use crate::error::ParseError;
use crate::theory_extract::ExtractedTheoryMeta;
pub trait AstParser: Send + Sync {
fn protocol_name(&self) -> &str;
fn parse(&self, source: &[u8], file_path: &str) -> Result<Schema, ParseError>;
fn emit(&self, schema: &Schema) -> Result<Vec<u8>, ParseError>;
fn supported_extensions(&self) -> &[&str];
fn theory_meta(&self) -> &ExtractedTheoryMeta;
}
pub struct ParserRegistry {
parsers: FxHashMap<String, Box<dyn AstParser>>,
extension_map: FxHashMap<String, String>,
}
impl ParserRegistry {
#[must_use]
pub fn new() -> Self {
let mut registry = Self {
parsers: FxHashMap::default(),
extension_map: FxHashMap::default(),
};
#[cfg(feature = "grammars")]
for grammar in panproto_grammars::grammars() {
let config = crate::languages::walker_configs::walker_config_for(grammar.name);
match crate::languages::common::LanguageParser::from_language(
grammar.name,
grammar.extensions.to_vec(),
grammar.language,
grammar.node_types,
config,
) {
Ok(p) => registry.register(Box::new(p)),
Err(err) => {
#[cfg(debug_assertions)]
eprintln!(
"warning: grammar '{}' theory extraction failed: {err}",
grammar.name
);
}
}
}
registry
}
pub fn register(&mut self, parser: Box<dyn AstParser>) {
let name = parser.protocol_name().to_owned();
for ext in parser.supported_extensions() {
self.extension_map.insert((*ext).to_owned(), name.clone());
}
self.parsers.insert(name, parser);
}
#[must_use]
pub fn detect_language(&self, path: &Path) -> Option<&str> {
path.extension()
.and_then(|ext| ext.to_str())
.and_then(|ext| self.extension_map.get(ext))
.map(String::as_str)
}
pub fn parse_file(&self, path: &Path, content: &[u8]) -> Result<Schema, ParseError> {
let protocol = self
.detect_language(path)
.ok_or_else(|| ParseError::UnknownLanguage {
extension: path
.extension()
.and_then(|e| e.to_str())
.unwrap_or("")
.to_owned(),
})?;
self.parse_with_protocol(protocol, content, &path.display().to_string())
}
pub fn parse_with_protocol(
&self,
protocol: &str,
content: &[u8],
file_path: &str,
) -> Result<Schema, ParseError> {
let parser = self
.parsers
.get(protocol)
.ok_or_else(|| ParseError::UnknownLanguage {
extension: protocol.to_owned(),
})?;
parser.parse(content, file_path)
}
pub fn emit_with_protocol(
&self,
protocol: &str,
schema: &Schema,
) -> Result<Vec<u8>, ParseError> {
let parser = self
.parsers
.get(protocol)
.ok_or_else(|| ParseError::UnknownLanguage {
extension: protocol.to_owned(),
})?;
parser.emit(schema)
}
#[must_use]
pub fn theory_meta(&self, protocol: &str) -> Option<&ExtractedTheoryMeta> {
self.parsers.get(protocol).map(|p| p.theory_meta())
}
pub fn protocol_names(&self) -> impl Iterator<Item = &str> {
self.parsers.keys().map(String::as_str)
}
#[must_use]
pub fn len(&self) -> usize {
self.parsers.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.parsers.is_empty()
}
}
impl Default for ParserRegistry {
fn default() -> Self {
Self::new()
}
}