use crate::error::{AamlError, set_error_render_context};
use crate::pipeline::{
DefaultLexer, DefaultParser, FormatRange, FormattingOptions, Lexer, Parser, Pipeline,
PipelineHashMap, PipelineOutput, SchemaInfo, TypeInfo,
};
use smol_str::SmolStr;
use std::path::Path;
#[cfg(feature = "aot")]
use crate::aot::{AamCompiler, AamLoader, MappedAam};
#[derive(Debug)]
pub enum AamBackend {
Dynamic(PipelineOutput),
#[cfg(feature = "aot")]
Mapped(MappedAam),
}
#[derive(Debug)]
pub struct AAM {
backend: AamBackend,
}
#[derive(Debug)]
pub struct AamLspAssist {
pub diagnostics: Vec<AamlError>,
pub formatted: Option<String>,
}
impl AAM {
fn parse_with_source_name(source_name: &str, text: &str) -> Result<Self, Vec<AamlError>> {
set_error_render_context(source_name.to_string(), text);
let pipeline = Pipeline::new();
let output = pipeline.process(text)?;
Ok(Self {
backend: AamBackend::Dynamic(output),
})
}
pub fn new() -> Self {
Self {
backend: AamBackend::Dynamic(PipelineOutput::new()),
}
}
pub fn parse(text: &str) -> Result<Self, Vec<AamlError>> {
Self::parse_with_source_name("raw_string", text)
}
pub fn from_pipeline(pipeline: Pipeline, text: &str) -> Result<Self, Vec<AamlError>> {
set_error_render_context("raw_string", text);
let output = pipeline.process(text)?;
Ok(Self {
backend: AamBackend::Dynamic(output),
})
}
pub fn load(path: impl AsRef<Path>) -> Result<Self, Vec<AamlError>> {
#[cfg(feature = "aot")]
{
let mapped = AamLoader::load_fast(path)?;
return Ok(Self {
backend: AamBackend::Mapped(mapped),
});
}
#[cfg(not(feature = "aot"))]
{
let content = std::fs::read_to_string(path.as_ref()).map_err(|e| {
vec![AamlError::IoError {
details: format!("failed to read '{}': {e}", path.as_ref().display()),
diagnostics: None,
}]
})?;
Self::parse_with_source_name(&path.as_ref().display().to_string(), &content)
}
}
pub fn format(&self, content: &str, options: &FormattingOptions) -> Result<String, AamlError> {
set_error_render_context("<format>", content);
let lexer = DefaultLexer::new();
let parser = DefaultParser::new();
let tokens = lexer.tokenize(content)?;
let crate::pipeline::parser::ParseOutput { ast, errors } =
parser.parse_with_recovery(&tokens);
if let Some(first_error) = errors.into_iter().next() {
return Err(first_error);
}
Pipeline::new().format(&ast, options)
}
pub fn format_range(
&self,
content: &str,
range: FormatRange,
options: &FormattingOptions,
) -> Result<String, AamlError> {
set_error_render_context("<format>", content);
let lexer = DefaultLexer::new();
let parser = DefaultParser::new();
let tokens = lexer.tokenize(content)?;
let crate::pipeline::parser::ParseOutput { ast, errors } =
parser.parse_with_recovery(&tokens);
if let Some(first_error) = errors.into_iter().next() {
return Err(first_error);
}
Pipeline::new().format_range(&ast, range, options)
}
pub fn lsp_assist(content: &str, options: &FormattingOptions) -> AamLspAssist {
set_error_render_context("<lsp>", content);
let lexer = DefaultLexer::new();
let parser = DefaultParser::new();
let tokens = match lexer.tokenize(content) {
Ok(tokens) => tokens,
Err(err) => {
return AamLspAssist {
diagnostics: vec![err],
formatted: None,
};
}
};
let parse_output = parser.parse_with_recovery(&tokens);
let formatted = if parse_output.errors.is_empty() {
Pipeline::new().format(&parse_output.ast, options).ok()
} else {
None
};
AamLspAssist {
diagnostics: parse_output.errors,
formatted,
}
}
#[cfg(feature = "aot")]
pub fn cook(path: impl AsRef<Path>) -> Result<std::path::PathBuf, Vec<AamlError>> {
AamCompiler::cook(path)
}
#[cfg(feature = "aot")]
pub fn load_fast(path: impl AsRef<Path>) -> Result<MappedAam, Vec<AamlError>> {
AamLoader::load_fast(path)
}
pub fn deep_search(&self, pattern: &str) -> Vec<(&str, &str)> {
self.iter().filter(|(k, _)| k.contains(pattern)).collect()
}
pub fn reverse_search(&self, target_value: &str) -> Vec<&str> {
self.iter()
.filter(|(_, v)| *v == target_value)
.map(|(k, _)| k)
.collect()
}
pub fn find_by<F>(&self, predicate: F) -> Vec<(&str, &str)>
where
F: Fn(&str, &str) -> bool,
{
self.iter().filter(|(k, v)| predicate(*k, *v)).collect()
}
pub fn find<'a>(&'a self, query: &'a str) -> Vec<(&'a str, &'a str)> {
if let Some(v) = self.get(query) {
return vec![(query, v)];
}
self.iter().filter(|(_, v)| *v == query).collect()
}
#[inline]
pub fn get(&self, key: &str) -> Option<&str> {
match &self.backend {
AamBackend::Dynamic(output) => output.map.get(key).map(|v| v.as_ref()),
#[cfg(feature = "aot")]
AamBackend::Mapped(mapped) => mapped.get(key),
}
}
pub fn iter(&self) -> Box<dyn Iterator<Item = (&str, &str)> + '_> {
match &self.backend {
AamBackend::Dynamic(output) => {
Box::new(output.map.iter().map(|(k, v)| (k.as_ref(), v.as_ref())))
}
#[cfg(feature = "aot")]
AamBackend::Mapped(mapped) => Box::new(mapped.iter_pairs()),
}
}
#[inline]
pub fn keys(&self) -> Vec<&str> {
self.iter().map(|(k, _)| k).collect()
}
#[inline]
pub fn to_map(&self) -> PipelineHashMap<String, String> {
self.iter()
.map(|(k, v)| (k.to_string(), v.to_string()))
.collect()
}
#[inline]
pub fn schemas(&self) -> Option<&PipelineHashMap<SmolStr, SchemaInfo>> {
match &self.backend {
AamBackend::Dynamic(output) => Some(&output.schemas),
#[cfg(feature = "aot")]
AamBackend::Mapped(_) => None,
}
}
pub fn get_schema(&self, name: &str) -> Option<&SchemaInfo> {
self.schemas().and_then(|schemas| schemas.get(name))
}
#[inline]
pub fn types(&self) -> Option<&PipelineHashMap<SmolStr, TypeInfo>> {
match &self.backend {
AamBackend::Dynamic(output) => Some(&output.types),
#[cfg(feature = "aot")]
AamBackend::Mapped(_) => None,
}
}
pub fn get_type(&self, name: &str) -> Option<&TypeInfo> {
self.types().and_then(|types| types.get(name))
}
}