1#[cfg(feature = "aot")]
2use crate::aot::{AamCompiler, AamLoader, MappedAam};
3use crate::error::{AamlError, set_error_render_context};
4use crate::pipeline::{
5 DefaultLexer, DefaultParser, FormatRange, FormattingOptions, Lexer, Parser, Pipeline,
6 PipelineHashMap, PipelineOutput, SchemaInfo, TypeInfo,
7};
8use smol_str::SmolStr;
9use std::path::Path;
10
11#[derive(Debug)]
14pub enum AamBackend {
15 Dynamic(PipelineOutput),
17 #[cfg(feature = "aot")]
19 Mapped(MappedAam),
20}
21
22#[derive(Debug)]
27pub struct AAM {
28 backend: AamBackend,
29}
30
31#[derive(Debug)]
33pub struct AamLspAssist {
34 pub diagnostics: Vec<AamlError>,
35 pub formatted: Option<String>,
36}
37
38impl AAM {
39 fn try_read_file(text: &str) -> Result<Option<(String, Option<&Path>)>, Vec<AamlError>> {
42 let path = Path::new(text);
43 if path.is_file() {
44 let content = std::fs::read_to_string(path).map_err(|e| {
45 vec![AamlError::IoError {
46 details: format!("failed to read '{}': {e}", path.display()),
47 diagnostics: None,
48 }]
49 })?;
50 Ok(Some((content, path.parent())))
51 } else {
52 Ok(None)
53 }
54 }
55
56 fn parse_with_source_name(source_name: &str, text: &str) -> Result<Self, Vec<AamlError>> {
57 set_error_render_context(source_name.to_string(), text);
58 let pipeline = Pipeline::new();
59 let output = pipeline.process(text)?;
60
61 Ok(Self {
62 backend: AamBackend::Dynamic(output),
63 })
64 }
65
66 #[must_use]
68 pub fn new() -> Self {
69 Self {
70 backend: AamBackend::Dynamic(PipelineOutput::new()),
71 }
72 }
73
74 pub fn parse(text: &str) -> Result<Self, Vec<AamlError>> {
84 if let Some((content, source_dir)) = Self::try_read_file(text)? {
85 set_error_render_context(text.to_string(), &content);
86 let pipeline = Pipeline::new();
87 let output = pipeline.process_with_source_dir(&content, source_dir)?;
88 Ok(Self {
89 backend: AamBackend::Dynamic(output),
90 })
91 } else {
92 Self::parse_with_source_name("raw_string", text)
93 }
94 }
95
96 pub fn from_pipeline(pipeline: &Pipeline, text: &str) -> Result<Self, Vec<AamlError>> {
106 if let Some((content, source_dir)) = Self::try_read_file(text)? {
107 set_error_render_context(text.to_string(), &content);
108 let output = pipeline.process_with_source_dir(&content, source_dir)?;
109 Ok(Self {
110 backend: AamBackend::Dynamic(output),
111 })
112 } else {
113 set_error_render_context("raw_string", text);
114 let output = pipeline.process(text)?;
115 Ok(Self {
116 backend: AamBackend::Dynamic(output),
117 })
118 }
119 }
120
121 pub fn load(path: impl AsRef<Path>) -> Result<Self, Vec<AamlError>> {
132 #[cfg(feature = "aot")]
133 {
134 let mapped = AamLoader::load_fast(path)?;
135 Ok(Self {
136 backend: AamBackend::Mapped(mapped),
137 })
138 }
139
140 #[cfg(not(feature = "aot"))]
141 {
142 let path = path.as_ref();
143 let content = std::fs::read_to_string(path).map_err(|e| {
144 vec![AamlError::IoError {
145 details: format!("failed to read '{}': {e}", path.display()),
146 diagnostics: None,
147 }]
148 })?;
149
150 let source_name = path.display().to_string();
151 set_error_render_context(source_name.clone(), &content);
152 let pipeline = Pipeline::new();
153 let source_dir = path.parent();
154 let output = pipeline.process_with_source_dir(&content, source_dir)?;
155
156 Ok(Self {
157 backend: AamBackend::Dynamic(output),
158 })
159 }
160 }
161
162 pub fn format(&self, content: &str, options: &FormattingOptions) -> Result<String, AamlError> {
168 set_error_render_context("<format>", content);
169 let lexer = DefaultLexer::new();
170 let parser = DefaultParser::new();
171 let tokens = lexer.tokenize(content)?;
172 let crate::pipeline::parser::ParseOutput { ast, errors } =
173 parser.parse_with_recovery(&tokens);
174
175 if let Some(first_error) = errors.into_iter().next() {
176 return Err(first_error);
177 }
178
179 Pipeline::new().format(&ast, options)
180 }
181
182 pub fn format_range(
188 &self,
189 content: &str,
190 range: FormatRange,
191 options: &FormattingOptions,
192 ) -> Result<String, AamlError> {
193 set_error_render_context("<format>", content);
194 let lexer = DefaultLexer::new();
195 let parser = DefaultParser::new();
196 let tokens = lexer.tokenize(content)?;
197 let crate::pipeline::parser::ParseOutput { ast, errors } =
198 parser.parse_with_recovery(&tokens);
199
200 if let Some(first_error) = errors.into_iter().next() {
201 return Err(first_error);
202 }
203
204 Pipeline::new().format_range(&ast, range, options)
205 }
206
207 #[must_use]
209 pub fn lsp_assist(content: &str, options: &FormattingOptions) -> AamLspAssist {
210 set_error_render_context("<lsp>", content);
211 let lexer = DefaultLexer::new();
212 let parser = DefaultParser::new();
213
214 let tokens = match lexer.tokenize(content) {
215 Ok(tokens) => tokens,
216 Err(err) => {
217 return AamLspAssist {
218 diagnostics: vec![err],
219 formatted: None,
220 };
221 }
222 };
223
224 let parse_output = parser.parse_with_recovery(&tokens);
225 let formatted = if parse_output.errors.is_empty() {
226 Pipeline::new().format(&parse_output.ast, options).ok()
227 } else {
228 None
229 };
230
231 AamLspAssist {
232 diagnostics: parse_output.errors,
233 formatted,
234 }
235 }
236
237 #[cfg(feature = "aot")]
244 pub fn cook(path: impl AsRef<Path>) -> Result<std::path::PathBuf, Vec<AamlError>> {
245 AamCompiler::cook(path)
246 }
247
248 #[cfg(feature = "aot")]
255 pub fn load_fast(path: impl AsRef<Path>) -> Result<MappedAam, Vec<AamlError>> {
256 AamLoader::load_fast(path)
257 }
258
259 #[must_use]
263 pub fn deep_search(&self, pattern: &str) -> Vec<(&str, &str)> {
264 self.iter().filter(|(k, _)| k.contains(pattern)).collect()
265 }
266
267 #[must_use]
269 pub fn reverse_search(&self, target_value: &str) -> Vec<&str> {
270 self.iter()
271 .filter(|(_, v)| *v == target_value)
272 .map(|(k, _)| k)
273 .collect()
274 }
275
276 pub fn find_by<F>(&self, predicate: F) -> Vec<(&str, &str)>
278 where
279 F: Fn(&str, &str) -> bool,
280 {
281 self.iter().filter(|(k, v)| predicate(k, v)).collect()
282 }
283
284 #[must_use]
288 pub fn find<'a>(&'a self, query: &'a str) -> Vec<(&'a str, &'a str)> {
289 if let Some(v) = self.get(query) {
290 return vec![(query, v)];
291 }
292
293 self.iter().filter(|(_, v)| *v == query).collect()
294 }
295
296 #[inline]
301 #[must_use]
302 pub fn get(&self, key: &str) -> Option<&str> {
303 match &self.backend {
304 AamBackend::Dynamic(output) => output.map.get(key).map(AsRef::as_ref),
305 #[cfg(feature = "aot")]
306 AamBackend::Mapped(mapped) => mapped.get(key),
307 }
308 }
309
310 #[must_use]
313 pub fn iter(&self) -> Box<dyn Iterator<Item = (&str, &str)> + '_> {
314 match &self.backend {
315 AamBackend::Dynamic(output) => {
316 Box::new(output.map.iter().map(|(k, v)| (k.as_ref(), v.as_ref())))
317 }
318 #[cfg(feature = "aot")]
319 AamBackend::Mapped(mapped) => Box::new(mapped.iter_pairs()),
320 }
321 }
322
323 #[inline]
326 #[must_use]
327 pub fn keys(&self) -> Vec<&str> {
328 self.iter().map(|(k, _)| k).collect()
329 }
330
331 #[inline]
334 #[must_use]
335 pub fn to_map(&self) -> PipelineHashMap<String, String> {
336 self.iter()
337 .map(|(k, v)| (k.to_string(), v.to_string()))
338 .collect()
339 }
340
341 #[inline]
346 #[must_use]
347 pub const fn schemas(&self) -> Option<&PipelineHashMap<SmolStr, SchemaInfo>> {
348 match &self.backend {
349 AamBackend::Dynamic(output) => Some(&output.schemas),
350 #[cfg(feature = "aot")]
351 AamBackend::Mapped(_) => None,
352 }
353 }
354
355 #[must_use]
357 pub fn get_schema(&self, name: &str) -> Option<&SchemaInfo> {
358 self.schemas().and_then(|schemas| schemas.get(name))
359 }
360
361 #[inline]
364 #[must_use]
365 pub const fn types(&self) -> Option<&PipelineHashMap<SmolStr, TypeInfo>> {
366 match &self.backend {
367 AamBackend::Dynamic(output) => Some(&output.types),
368 #[cfg(feature = "aot")]
369 AamBackend::Mapped(_) => None,
370 }
371 }
372
373 #[must_use]
375 pub fn get_type(&self, name: &str) -> Option<&TypeInfo> {
376 self.types().and_then(|types| types.get(name))
377 }
378}
379
380impl Default for AAM {
381 fn default() -> Self {
382 Self::new()
383 }
384}
385
386impl<'a> IntoIterator for &'a AAM {
387 type Item = (&'a str, &'a str);
388 type IntoIter = Box<dyn Iterator<Item = (&'a str, &'a str)> + 'a>;
389
390 fn into_iter(self) -> Self::IntoIter {
391 self.iter()
392 }
393}