1#![allow(clippy::missing_errors_doc)]
11
12pub mod execution_descriptor;
13pub mod formatter;
14pub mod lexer;
15pub mod parser;
16pub mod scope_manager;
17pub mod utils;
18
19pub(crate) mod source_dir;
20
21pub mod executer;
22pub mod executor_traits;
23pub mod tasks;
24pub mod validator;
25
26pub use executer::{DefaultExecuter, Executer};
27pub use execution_descriptor::{
28 CommandInfo, ExecutionContext, ExecutionDescriptor, SchemaInfo, TypeInfo,
29};
30pub use executor_traits::{
31 DefaultParserExecutor, DefaultValidateExecutor, ParserExecutor, ValidateExecutor,
32};
33pub use formatter::{DefaultFormatter, FormatRange, Formatter, FormattingOptions};
34pub use lexer::{DefaultLexer, Lexer, Token};
35pub use parser::{AstNode, DefaultParser, Parser};
36pub use scope_manager::ScopeManager;
37pub use tasks::{ExecutionTask, ParseTask, TaskError, TaskExecutionResult, ValidationTask};
38pub use validator::{DefaultValidator, Validator};
39
40use crate::error::AamlError;
41use bumpalo::Bump;
42use smol_str::SmolStr;
43use std::collections::HashSet;
44use tinyvec::TinyVec;
45
46#[cfg(feature = "hash-ripemd")]
49#[derive(Default, Clone)]
50pub struct RipemdBuildHasher;
51
52#[cfg(feature = "hash-ripemd")]
53#[derive(Default, Clone)]
54pub struct RipemdHasher {
55 bytes: Vec<u8>,
56}
57
58#[cfg(feature = "hash-ripemd")]
59impl std::hash::BuildHasher for RipemdBuildHasher {
60 type Hasher = RipemdHasher;
61
62 fn build_hasher(&self) -> Self::Hasher {
63 RipemdHasher::default()
64 }
65}
66
67#[cfg(feature = "hash-ripemd")]
68impl std::hash::Hasher for RipemdHasher {
69 fn finish(&self) -> u64 {
70 use ripemd::Digest;
71 let mut hasher = ripemd::Ripemd160::new();
72 hasher.update(&self.bytes);
73 let digest = hasher.finalize();
74
75 let mut out = [0_u8; 8];
76 out.copy_from_slice(&digest[..8]);
77 u64::from_le_bytes(out)
78 }
79
80 fn write(&mut self, bytes: &[u8]) {
81 self.bytes.extend_from_slice(bytes);
82 }
83}
84
85#[cfg(feature = "hash-std")]
86pub type PipelineBuildHasher = std::collections::hash_map::RandomState;
87
88#[cfg(all(not(feature = "hash-std"), feature = "hash-fx"))]
89pub type PipelineBuildHasher = rustc_hash::FxBuildHasher;
90
91#[cfg(all(
92 not(feature = "hash-std"),
93 not(feature = "hash-fx"),
94 feature = "hash-ahash"
95))]
96pub type PipelineBuildHasher = ahash::RandomState;
97
98#[cfg(all(
99 not(feature = "hash-std"),
100 not(feature = "hash-fx"),
101 not(feature = "hash-ahash"),
102 feature = "hash-rapidhash"
103))]
104pub type PipelineBuildHasher = rapidhash::fast::RandomState;
105
106#[cfg(all(
107 not(feature = "hash-std"),
108 not(feature = "hash-fx"),
109 not(feature = "hash-ahash"),
110 not(feature = "hash-rapidhash"),
111 feature = "hash-ripemd"
112))]
113pub type PipelineBuildHasher = RipemdBuildHasher;
114
115#[cfg(not(any(
116 feature = "hash-std",
117 feature = "hash-fx",
118 feature = "hash-ahash",
119 feature = "hash-rapidhash",
120 feature = "hash-ripemd"
121)))]
122pub type PipelineBuildHasher = std::collections::hash_map::RandomState;
123
124pub type PipelineHashMap<K, V> = std::collections::HashMap<K, V, PipelineBuildHasher>;
125
126#[inline]
127pub(crate) fn new_pipeline_hash_map<K, V>() -> PipelineHashMap<K, V> {
128 PipelineHashMap::with_hasher(PipelineBuildHasher::default())
129}
130
131type AamlString = SmolStr;
132type ErrorAccumulator = TinyVec<[Option<AamlError>; 4]>;
133
134pub struct PipelineOutput {
136 pub map: PipelineHashMap<AamlString, AamlString>,
137 pub schemas: PipelineHashMap<AamlString, SchemaInfo>,
138 pub types: PipelineHashMap<AamlString, TypeInfo>,
139}
140
141impl std::fmt::Debug for PipelineOutput {
142 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
143 f.debug_struct("PipelineOutput")
144 .field("map", &self.map)
145 .field("schemas", &self.schemas)
146 .field("types_count", &self.types.len())
147 .finish()
148 }
149}
150
151impl PipelineOutput {
152 #[must_use]
153 pub fn new() -> Self {
154 Self {
155 map: new_pipeline_hash_map(),
156 schemas: new_pipeline_hash_map(),
157 types: new_pipeline_hash_map(),
158 }
159 }
160}
161
162impl Default for PipelineOutput {
163 fn default() -> Self {
164 Self::new()
165 }
166}
167
168pub struct Pipeline {
169 lexer: Box<dyn Lexer>,
170 parser: Box<dyn Parser>,
171 validator: Box<dyn Validator>,
172 validate_executor: Box<dyn ValidateExecutor>,
173 parser_executor: Box<dyn ParserExecutor>,
174 executer: Box<dyn Executer>,
175 formatter: Box<dyn Formatter>,
176}
177
178impl Pipeline {
179 pub fn process(&self, content: &str) -> Result<PipelineOutput, Vec<AamlError>> {
181 let arena = Bump::new();
182 self.process_with_arena(content, &arena)
183 }
184
185 pub fn process_with_source_dir(
187 &self,
188 content: &str,
189 source_dir: Option<&std::path::Path>,
190 ) -> Result<PipelineOutput, Vec<AamlError>> {
191 let arena = Bump::new();
192 self.process_with_arena_and_source_dir(content, &arena, source_dir)
193 }
194
195 #[must_use]
199 pub fn new() -> Self {
200 Self {
201 lexer: Box::new(DefaultLexer::new()),
202 parser: Box::new(DefaultParser::new()),
203 validator: Box::new(DefaultValidator::new()),
204 validate_executor: Box::new(DefaultValidateExecutor::new()),
205 parser_executor: Box::new(DefaultParserExecutor::new()),
206 executer: Box::new(DefaultExecuter::new()),
207 formatter: Box::new(DefaultFormatter::new()),
208 }
209 }
210
211 #[cfg(feature = "parallel")]
212 fn execute_parse_tasks_parallel(tasks: &[ParseTask<'_>]) -> Vec<TaskError> {
213 use rayon::prelude::*;
214
215 tasks
216 .par_iter()
217 .filter_map(|task| task.validate_stateless().err())
218 .collect()
219 }
220
221 fn collect_parse_errors(all_errors: &mut ErrorAccumulator, errors: Vec<TaskError>) {
222 for err in errors {
223 all_errors.push(Some(AamlError::ParseError {
224 line: err.line,
225 content: String::new(),
226 details: err.message,
227 diagnostics: None,
228 }));
229 }
230 }
231
232 fn collect_validation_errors(all_errors: &mut ErrorAccumulator, errors: Vec<TaskError>) {
233 for err in errors {
234 all_errors.push(Some(AamlError::DirectiveError {
235 directive: "validation".to_string(),
236 message: err.message,
237 diagnostics: None,
238 }));
239 }
240 }
241
242 fn parse_ast<'a>(
243 &self,
244 tokens: &[Token<'a>],
245 all_errors: &mut ErrorAccumulator,
246 ) -> Vec<AstNode<'a>> {
247 let parse_output = self.parser.parse_with_recovery(tokens);
248 for err in parse_output.errors {
249 all_errors.push(Some(err));
250 }
251 parse_output.ast
252 }
253
254 fn run_parse_tasks<'a>(
255 &self,
256 ast: &[AstNode<'a>],
257 arena: &'a Bump,
258 descriptor: &mut ExecutionDescriptor<'a>,
259 all_errors: &mut ErrorAccumulator,
260 ) {
261 let parse_tasks = self.parser.generate_parse_tasks(ast);
262 descriptor.add_parse_tasks(parse_tasks.clone());
263
264 #[cfg(feature = "parallel")]
265 let stateless_errors = Self::execute_parse_tasks_parallel(&parse_tasks);
266
267 #[cfg(feature = "parallel")]
268 {
269 if !stateless_errors.is_empty() {
270 Self::collect_parse_errors(all_errors, stateless_errors);
271 }
272 }
273
274 #[cfg(feature = "parallel")]
275 let sequential_tasks: Vec<ParseTask<'a>> = parse_tasks
276 .into_iter()
277 .filter(|task| task.validate_stateless().is_ok())
278 .collect();
279
280 #[cfg(not(feature = "parallel"))]
281 let sequential_tasks = parse_tasks;
282
283 let parse_result =
284 self.parser_executor
285 .execute_batch(&sequential_tasks, arena, descriptor.context_mut());
286 if !parse_result.success {
287 Self::collect_parse_errors(all_errors, parse_result.errors);
288 }
289 }
290
291 fn run_validation_tasks<'a>(
292 &self,
293 ast: &'a [AstNode<'a>],
294 descriptor: &mut ExecutionDescriptor<'a>,
295 all_errors: &mut ErrorAccumulator,
296 ) {
297 let validation_tasks = self.validator.validate(ast).unwrap_or_else(|e| {
298 all_errors.push(Some(e));
299 Vec::new()
300 });
301 descriptor.add_validation_tasks(validation_tasks.clone());
302
303 let validation_result = self
304 .validate_executor
305 .execute_batch(&validation_tasks, descriptor.context());
306 if !validation_result.success {
307 Self::collect_validation_errors(all_errors, validation_result.errors);
308 }
309
310 for err in Self::validate_registered_types(descriptor.context()) {
311 all_errors.push(Some(err));
312 }
313
314 for err in Self::validate_schema_declared_types(descriptor.context()) {
315 all_errors.push(Some(err));
316 }
317
318 for err in Self::validate_schema_required_fields(descriptor.context()) {
319 all_errors.push(Some(err));
320 }
321
322 for err in Self::validate_schema_field_types(descriptor.context()) {
323 all_errors.push(Some(err));
324 }
325 }
326
327 fn validate_type_reference(
328 type_name: &str,
329 context: &ExecutionContext,
330 trail: &mut Vec<String>,
331 ) -> Result<(), AamlError> {
332 if let Some(inner) = crate::types_aam::list::ListType::parse_inner(type_name) {
333 return Self::validate_type_reference(inner.trim(), context, trail);
334 }
335
336 if crate::types_aam::resolve_builtin(type_name).is_ok()
337 || context.schemas.contains_key(type_name)
338 {
339 return Ok(());
340 }
341
342 let Some(type_info) = context.types.get(type_name) else {
343 return Err(AamlError::InvalidType {
344 type_name: type_name.to_string(),
345 details: format!("Unknown type '{type_name}'"),
346 provided: String::new(),
347 diagnostics: None,
348 });
349 };
350
351 if type_info.spec == "schema" {
352 return Ok(());
353 }
354
355 if let Some(cycle_start) = trail.iter().position(|n| n == type_name) {
356 let mut cycle = trail[cycle_start..].to_vec();
357 cycle.push(type_name.to_string());
358 return Err(AamlError::CircularDependency {
359 path: cycle.join(" -> "),
360 diagnostics: None,
361 });
362 }
363
364 if type_info.spec == type_name {
365 return Err(AamlError::InvalidType {
366 type_name: type_name.to_string(),
367 details: format!("Unknown type '{type_name}'"),
368 provided: String::new(),
369 diagnostics: None,
370 });
371 }
372
373 trail.push(type_name.to_string());
374 let result = Self::validate_type_reference(type_info.spec.as_str(), context, trail);
375 trail.pop();
376 result
377 }
378
379 fn validate_registered_types(context: &ExecutionContext) -> Vec<AamlError> {
380 context
381 .types
382 .iter()
383 .filter(|(_, type_info)| type_info.line > 0 && type_info.spec != "schema")
384 .filter_map(|(_, type_info)| {
385 let mut trail = Vec::new();
386 Self::validate_type_reference(type_info.spec.as_str(), context, &mut trail)
387 .err()
388 .map(|err| match err {
389 AamlError::CircularDependency { .. } => err,
390 _ => AamlError::InvalidType {
391 type_name: type_info.name.to_string(),
392 details: format!(
393 "Type '{}' references unknown definition '{}'",
394 type_info.name, type_info.spec
395 ),
396 provided: err.short_message(),
397 diagnostics: None,
398 },
399 })
400 })
401 .collect()
402 }
403
404 fn validate_schema_declared_types(context: &ExecutionContext) -> Vec<AamlError> {
405 context
406 .schemas
407 .iter()
408 .flat_map(|(schema_name, schema)| {
409 schema
410 .fields
411 .iter()
412 .filter_map(move |(field, (type_name, _))| {
413 let mut trail = Vec::new();
414 Self::validate_type_reference(type_name, context, &mut trail)
415 .err()
416 .map(|_| AamlError::SchemaValidationError {
417 schema: schema_name.to_string(),
418 field: field.to_string(),
419 type_name: type_name.to_string(),
420 details: format!(
421 "Unknown type '{type_name}' declared for field '{field}'"
422 ),
423 diagnostics: None,
424 })
425 })
426 })
427 .collect()
428 }
429
430 fn collect_referenced_schemas(context: &ExecutionContext) -> HashSet<SmolStr> {
431 context
432 .schemas
433 .values()
434 .flat_map(|schema| schema.fields.values())
435 .filter_map(|(type_name, _)| {
436 if context.schemas.contains_key(type_name.as_str()) {
437 Some(type_name.clone())
438 } else {
439 None
440 }
441 })
442 .collect()
443 }
444
445 fn validate_schema_required_fields(context: &ExecutionContext) -> Vec<AamlError> {
446 let referenced_schemas = Self::collect_referenced_schemas(context);
447
448 context
449 .schemas
450 .iter()
451 .filter(|(schema_name, _)| !referenced_schemas.contains(schema_name.as_str()))
452 .filter(|(_, schema)| {
453 schema
454 .fields
455 .keys()
456 .any(|f| context.map.contains_key(f.as_str()))
457 })
458 .flat_map(|(schema_name, schema)| {
459 schema
460 .fields
461 .iter()
462 .filter_map(move |(field, (type_name, is_optional))| {
463 if *is_optional || context.map.contains_key(field.as_str()) {
464 return None;
465 }
466
467 let mut seen_aliases = HashSet::new();
468 if Self::is_schema_type_reference(type_name, context, &mut seen_aliases) {
469 return None;
470 }
471
472 Some(AamlError::SchemaValidationError {
473 schema: schema_name.to_string(),
474 field: field.to_string(),
475 type_name: type_name.to_string(),
476 details: format!("Missing required field '{field}'"),
477 diagnostics: None,
478 })
479 })
480 })
481 .collect()
482 }
483
484 fn is_schema_type_reference(
485 type_name: &str,
486 context: &ExecutionContext,
487 seen_aliases: &mut HashSet<String>,
488 ) -> bool {
489 if context.schemas.contains_key(type_name) {
490 return true;
491 }
492
493 let Some(type_info) = context.types.get(type_name) else {
494 return false;
495 };
496
497 if type_info.spec == "schema" {
498 return true;
499 }
500
501 if type_info.spec == type_name || !seen_aliases.insert(type_name.to_string()) {
502 return false;
503 }
504
505 Self::is_schema_type_reference(type_info.spec.as_str(), context, seen_aliases)
506 }
507
508 fn validate_schema_field_types(context: &ExecutionContext) -> Vec<AamlError> {
509 context
510 .map
511 .iter()
512 .filter_map(|(key, value)| {
513 let (schema_name, (type_name, _)) =
514 context.schemas.iter().find_map(|(schema_name, schema)| {
515 schema
516 .fields
517 .get(key.as_str())
518 .map(|field_info| (schema_name, field_info))
519 })?;
520
521 utils::validate_type_value(value, type_name, context)
522 .err()
523 .map(|e| AamlError::SchemaValidationError {
524 schema: schema_name.to_string(),
525 field: key.to_string(),
526 type_name: type_name.to_string(),
527 details: format!(
528 "Type mismatch for field '{}': {}",
529 key,
530 e.short_message()
531 ),
532 diagnostics: None,
533 })
534 })
535 .collect()
536 }
537
538 fn process_with_tasks<'a>(
539 &self,
540 content: &'a str,
541 arena: &'a Bump,
542 source_dir: Option<&std::path::Path>,
543 ) -> Result<PipelineOutput, Vec<AamlError>> {
544 let mut all_errors: ErrorAccumulator = TinyVec::new();
545
546 let tokens = match self.lexer.tokenize(content) {
547 Ok(t) => t,
548 Err(e) => return Err(vec![e]),
549 };
550
551 let ast = self.parse_ast(&tokens, &mut all_errors);
552 let mut descriptor = ExecutionDescriptor::new(ast.clone(), "inline".to_string());
553
554 let _guard = source_dir::SourceDirGuard::new(source_dir.map(std::path::Path::to_path_buf));
556
557 self.run_parse_tasks(&ast, arena, &mut descriptor, &mut all_errors);
558 self.run_validation_tasks(&ast, &mut descriptor, &mut all_errors);
559
560 if let Some(errors) = finalize_error_accumulator(all_errors) {
561 return Err(errors);
562 }
563
564 descriptor.add_execution_tasks(self.parser.generate_execution_tasks(&ast));
565
566 if let Err(e) = self.executer.execute(&mut descriptor) {
567 return Err(vec![e]);
568 }
569
570 Ok(PipelineOutput {
571 map: descriptor.context.map,
572 schemas: descriptor.context.schemas,
573 types: descriptor.context.types,
574 })
575 }
576
577 pub fn process_with_arena<'a>(
578 &self,
579 content: &'a str,
580 arena: &'a Bump,
581 ) -> Result<PipelineOutput, Vec<AamlError>> {
582 self.process_with_tasks(content, arena, None)
583 }
584
585 pub fn process_with_arena_and_source_dir<'a>(
586 &self,
587 content: &'a str,
588 arena: &'a Bump,
589 source_dir: Option<&std::path::Path>,
590 ) -> Result<PipelineOutput, Vec<AamlError>> {
591 self.process_with_tasks(content, arena, source_dir)
592 }
593
594 pub fn format(
595 &self,
596 nodes: &[AstNode],
597 options: &FormattingOptions,
598 ) -> Result<String, AamlError> {
599 self.formatter.format_document(nodes, options)
600 }
601
602 pub fn format_range(
603 &self,
604 nodes: &[AstNode],
605 range: FormatRange,
606 options: &FormattingOptions,
607 ) -> Result<String, AamlError> {
608 self.formatter.format_range(nodes, range, options)
609 }
610}
611
612impl Default for Pipeline {
613 fn default() -> Self {
614 Self::new()
615 }
616}
617
618#[inline]
619fn finalize_error_accumulator(errors: ErrorAccumulator) -> Option<Vec<AamlError>> {
620 if errors.is_empty() {
621 return None;
622 }
623 Some(errors.into_iter().flatten().collect())
624}