use crate::commands::schema::SchemaDef;
use crate::commands::{self, Command};
use crate::error::AamlError;
use crate::types::list::ListType;
use crate::types::{Type, resolve_builtin};
use std::collections::HashMap;
use std::fs;
use std::ops::{Add, AddAssign};
use std::path::Path;
use std::sync::Arc;
mod lookup;
pub mod parsing;
pub mod types_registry;
mod validation;
#[cfg(feature = "serde")]
pub mod serialize;
#[cfg(feature = "perf-hash")]
type Hasher = ahash::RandomState;
#[cfg(not(feature = "perf-hash"))]
type Hasher = std::collections::hash_map::RandomState;
type AamlString = Box<str>;
pub struct AAML {
map: HashMap<AamlString, AamlString, Hasher>,
commands: HashMap<String, Arc<dyn Command>>,
types: HashMap<String, Box<dyn Type>>,
schemas: HashMap<String, SchemaDef>,
}
impl std::fmt::Debug for AAML {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("AAML")
.field("map", &self.map)
.field("commands_count", &self.commands.len())
.finish()
}
}
impl AAML {
pub fn new() -> AAML {
let mut instance = AAML {
map: HashMap::with_hasher(Hasher::new()),
commands: HashMap::new(),
types: HashMap::new(),
schemas: HashMap::new(),
};
instance.register_default_commands();
instance
}
pub fn get_schemas(&self) -> &HashMap<String, SchemaDef> {
&self.schemas
}
pub fn with_capacity(capacity: usize) -> AAML {
let mut instance = AAML {
map: HashMap::with_capacity_and_hasher(capacity, Hasher::default()),
commands: HashMap::new(),
types: HashMap::new(),
schemas: HashMap::new(),
};
instance.register_default_commands();
instance
}
pub(crate) fn get_schemas_mut(&mut self) -> &mut HashMap<String, SchemaDef> {
&mut self.schemas
}
pub fn get_schema(&self, name: &str) -> Option<&SchemaDef> {
self.schemas.get(name)
}
pub(crate) fn get_map_mut(&mut self) -> &mut HashMap<AamlString, AamlString, Hasher> {
&mut self.map
}
pub fn register_command<C: Command + 'static>(&mut self, command: C) {
self.commands
.insert(command.name().to_string(), Arc::new(command));
}
pub fn register_type<T: Type + 'static>(&mut self, name: String, type_def: T) {
self.types.insert(name, Box::new(type_def));
}
pub fn get_type(&self, name: &str) -> Option<&dyn Type> {
self.types.get(name).map(|b| b.as_ref())
}
pub fn unregister_type(&mut self, name: &str) {
self.types.remove(name);
}
pub fn check_type(&self, type_name: &str, value: &str) -> Result<(), AamlError> {
self.types
.get(type_name)
.ok_or_else(|| AamlError::NotFound(type_name.to_string()))?
.validate(value, self)
}
pub fn validate_value(&self, type_name: &str, value: &str) -> Result<(), AamlError> {
let make_err = |e: AamlError| AamlError::InvalidType {
type_name: type_name.to_string(),
details: e.to_string(),
};
if let Some(type_def) = self.types.get(type_name) {
return type_def.validate(value, self).map_err(make_err);
}
resolve_builtin(type_name)
.map_err(|_| AamlError::NotFound(type_name.to_string()))?
.validate(value, self)
.map_err(make_err)
}
pub fn merge_content(&mut self, content: &str) -> Result<(), AamlError> {
self.map.reserve(content.len() / 40);
let mut pending: Option<(String, usize)> = None;
for (i, line) in content.lines().enumerate() {
let line_num = i + 1;
if let Some(result) = self.accumulate_or_process(line, line_num, &mut pending)? {
self.process_line(&result.0, result.1)?;
}
}
if let Some((buf, start)) = pending {
self.process_line(&buf, start)?;
}
Ok(())
}
pub(crate) fn get_types_mut(&mut self) -> &mut HashMap<String, Box<dyn Type>> {
&mut self.types
}
fn accumulate_or_process(
&mut self,
line: &str,
line_num: usize,
pending: &mut Option<(String, usize)>,
) -> Result<Option<(String, usize)>, AamlError> {
if let Some((buf, start)) = pending {
buf.push(' ');
buf.push_str(parsing::strip_comment(line).trim());
if parsing::block_is_complete(buf) {
let complete = buf.clone();
let start_line = *start;
*pending = None;
return Ok(Some((complete, start_line)));
}
return Ok(None);
}
let stripped = parsing::strip_comment(line).trim();
if parsing::needs_accumulation(stripped) {
*pending = Some((stripped.to_string(), line_num));
return Ok(None);
}
self.process_stripped_line(stripped, line_num)?;
Ok(None)
}
pub fn merge_file<P: AsRef<Path>>(&mut self, file_path: P) -> Result<(), AamlError> {
let content = fs::read_to_string(file_path)?;
self.merge_content(&content)
}
pub fn parse(content: &str) -> Result<Self, AamlError> {
let mut aaml = AAML::new();
aaml.merge_content(content)?;
Ok(aaml)
}
pub fn load<P: AsRef<Path>>(file_path: P) -> Result<Self, AamlError> {
let content = fs::read_to_string(file_path)?;
Self::parse(&content)
}
pub fn unwrap_quotes(s: &str) -> &str {
parsing::unwrap_quotes(s)
}
pub fn import_schema(&mut self, name: &str, source: &mut AAML) -> Result<(), AamlError> {
let schema = source.get_schemas_mut().remove(name).ok_or_else(|| {
AamlError::DirectiveError("derive".into(), format!("Schema '{name}' not found"))
})?;
for ty_str in schema.fields.values() {
let ty_name = ListType::parse_inner(ty_str).unwrap_or_else(|| ty_str.clone());
if let Some(ty_def) = source.get_types_mut().remove(&ty_name) {
self.get_types_mut().entry(ty_name).or_insert(ty_def);
}
}
self.get_schemas_mut()
.entry(name.to_string())
.or_insert(schema);
Ok(())
}
pub fn merge_map_weak(&mut self, other_map: &mut HashMap<AamlString, AamlString, Hasher>) {
for (k, v) in other_map.drain() {
self.get_map_mut().entry(k).or_insert(v);
}
}
pub fn keys(&self) -> Vec<&str> {
self.map.keys().map(|k| k.as_ref()).collect()
}
pub fn to_map(&self) -> HashMap<String, String> {
self.map
.iter()
.map(|(k, v)| (k.to_string(), v.to_string()))
.collect()
}
fn register_default_commands(&mut self) {
self.register_command(commands::import::ImportCommand);
self.register_command(commands::typecm::TypeCommand);
self.register_command(commands::schema::SchemaCommand);
self.register_command(commands::derive::DeriveCommand);
}
fn process_line(&mut self, raw_line: &str, line_num: usize) -> Result<(), AamlError> {
let line = parsing::strip_comment(raw_line).trim();
self.process_stripped_line(line, line_num)
}
fn process_stripped_line(&mut self, line: &str, line_num: usize) -> Result<(), AamlError> {
if line.is_empty() {
return Ok(());
}
if let Some(rest) = line.strip_prefix('@') {
return self.process_directive(rest, line_num);
}
self.process_assignment(line, line_num)
}
fn process_assignment(&mut self, line: &str, line_num: usize) -> Result<(), AamlError> {
match parsing::parse_assignment(line) {
Ok((key, value)) => {
self.validate_against_schemas(key, value)?;
self.map.insert(Box::from(key), Box::from(value));
Ok(())
}
Err(details) => Err(AamlError::ParseError {
line: line_num,
content: line.to_string(),
details: details.to_string(),
}),
}
}
fn process_directive(&mut self, content: &str, line_num: usize) -> Result<(), AamlError> {
let mut parts = content.splitn(2, char::is_whitespace);
let command_name = parts.next().unwrap_or("").trim();
let args = parts.next().unwrap_or("");
if command_name.is_empty() {
return Err(AamlError::ParseError {
line: line_num,
content: content.to_string(),
details: "Empty directive".to_string(),
});
}
let command = self.commands.get(command_name).cloned();
match command {
Some(cmd) => cmd.execute(self, args),
None => Err(AamlError::ParseError {
line: line_num,
content: content.to_string(),
details: format!("Unknown directive: @{}", command_name),
}),
}
}
}
impl Add for AAML {
type Output = Self;
fn add(mut self, rhs: Self) -> Self {
self.map.reserve(rhs.map.len());
self.map.extend(rhs.map);
self.types.extend(rhs.types);
self
}
}
impl AddAssign for AAML {
fn add_assign(&mut self, rhs: Self) {
self.map.reserve(rhs.map.len());
self.map.extend(rhs.map);
self.types.extend(rhs.types);
}
}
impl Default for AAML {
fn default() -> Self {
Self::new()
}
}