1use crate::error::AamlError;
7use crate::pipeline::parser::AstNode;
8use crate::pipeline::tasks::{ExecutionStats, ExecutionTask, ParseTask, ValidationTask};
9use crate::pipeline::{PipelineBuildHasher, PipelineHashMap};
10use smol_str::SmolStr;
11use std::collections::HashSet;
12
13#[inline]
14fn new_pipeline_map<K, V>() -> PipelineHashMap<K, V> {
15 PipelineHashMap::with_hasher(PipelineBuildHasher::default())
16}
17
18#[derive(Debug)]
23pub struct ExecutionDescriptor<'a> {
24 pub line_numbers: Vec<usize>,
26
27 pub context: ExecutionContext<'a>,
29
30 pub parsed_outputs: Vec<AstNode<'a>>,
32
33 pub parse_tasks: Vec<ParseTask<'a>>,
35
36 pub validation_tasks: Vec<ValidationTask<'a>>,
38
39 pub execution_tasks: Vec<ExecutionTask<'a>>,
41
42 pub stats: ExecutionStats,
44}
45
46#[derive(Debug)]
51pub struct ExecutionContext<'a> {
52 pub source: std::borrow::Cow<'a, str>,
54
55 pub map: PipelineHashMap<SmolStr, SmolStr>,
57
58 pub schemas: PipelineHashMap<SmolStr, SchemaInfo>,
60
61 pub types: PipelineHashMap<SmolStr, TypeInfo>,
63
64 pub commands: PipelineHashMap<std::borrow::Cow<'a, str>, CommandInfo<'a>>,
66
67 pub key_line_map: PipelineHashMap<SmolStr, usize>,
69
70 pub scope_stack: Vec<std::borrow::Cow<'a, str>>,
72
73 pub visited_keys: HashSet<SmolStr>,
75
76 pub imported_files: HashSet<std::borrow::Cow<'a, str>>,
78}
79
80#[derive(Debug, Clone)]
82pub struct SchemaInfo {
83 pub name: SmolStr,
85
86 pub fields: PipelineHashMap<SmolStr, (SmolStr, bool)>,
88
89 pub line: usize,
91}
92
93#[derive(Debug, Clone)]
95pub struct TypeInfo {
96 pub name: SmolStr,
98
99 pub spec: SmolStr,
101
102 pub validator: Option<SmolStr>,
104
105 pub default_value: Option<SmolStr>,
107
108 pub metadata: PipelineHashMap<SmolStr, SmolStr>,
110
111 pub line: usize,
113}
114
115#[derive(Debug, Clone)]
117pub struct CommandInfo<'a> {
118 pub name: std::borrow::Cow<'a, str>,
120
121 pub arg_pattern: std::borrow::Cow<'a, str>,
123
124 pub line: usize,
126}
127
128impl<'a> ExecutionContext<'a> {
129 pub fn new(source: impl Into<std::borrow::Cow<'a, str>>) -> Self {
131 let mut context = Self {
132 source: source.into(),
133 map: new_pipeline_map(),
134 schemas: new_pipeline_map(),
135 types: new_pipeline_map(),
136 commands: new_pipeline_map(),
137 key_line_map: new_pipeline_map(),
138 scope_stack: vec!["root".into()],
139 visited_keys: HashSet::default(),
140 imported_files: HashSet::default(),
141 };
142
143 context.register_builtin_type_defaults();
144 context
145 }
146
147 fn register_builtin_type_defaults(&mut self) {
148 for (name, default_value) in [
149 ("i32", "0"),
150 ("f64", "0"),
151 ("bool", "false"),
152 ("string", "\"\""),
153 ("color", "#000000"),
154 ] {
155 self.types.insert(
156 name.into(),
157 TypeInfo {
158 name: name.into(),
159 spec: name.into(),
160 validator: None,
161 default_value: Some(default_value.into()),
162 metadata: new_pipeline_map(),
163 line: 0,
164 },
165 );
166 }
167 }
168
169 #[must_use]
171 pub fn default_value_for_type(&self, type_name: &str) -> Option<&str> {
172 self.types
173 .get(type_name)
174 .and_then(|info| info.default_value.as_deref())
175 }
176
177 #[must_use]
179 pub fn current_scope(&self) -> String {
180 self.scope_stack.join("::")
181 }
182
183 pub fn push_scope(&mut self, scope_name: impl Into<std::borrow::Cow<'a, str>>) {
185 self.scope_stack.push(scope_name.into());
186 }
187
188 pub fn pop_scope(&mut self) {
190 if self.scope_stack.len() > 1 {
191 self.scope_stack.pop();
192 }
193 }
194
195 pub fn set_value(&mut self, key: impl AsRef<str>, value: impl AsRef<str>, line: usize) {
197 let key_str = key.as_ref();
198 let key_smol: SmolStr = key_str.into();
199 self.map.insert(key_smol.clone(), value.as_ref().into());
200 self.key_line_map.insert(key_smol, line);
201 }
202
203 #[must_use]
205 pub fn get_value(&self, key: &str) -> Option<&str> {
206 self.map.get(key).map(AsRef::as_ref)
207 }
208
209 pub fn register_schema(&mut self, schema: SchemaInfo) {
211 self.schemas.insert(schema.name.clone(), schema);
212 }
213
214 pub fn register_type(&mut self, type_def: TypeInfo) -> Result<(), AamlError> {
221 let name = type_def.name.to_string();
222 self.types.insert(type_def.name.clone(), type_def);
223
224 let mut trail = Vec::new();
226 if let Err(err @ AamlError::CircularDependency { .. }) =
227 super::Pipeline::validate_type_reference(&name, self, &mut trail)
228 {
229 return Err(err);
230 }
231
232 Ok(())
233 }
234
235 pub fn register_command(&mut self, command: CommandInfo<'a>) {
237 self.commands.insert(command.name.clone(), command);
238 }
239
240 pub fn mark_visited(&mut self, key: &str) {
242 self.visited_keys.insert(key.into());
243 }
244
245 #[must_use]
247 pub fn is_visited(&self, key: &str) -> bool {
248 self.visited_keys.contains(key)
249 }
250
251 pub fn reset_visited(&mut self) {
253 self.visited_keys.clear();
254 }
255
256 pub fn record_import(&mut self, file_path: impl Into<std::borrow::Cow<'a, str>>) {
258 self.imported_files.insert(file_path.into());
259 }
260
261 #[must_use]
263 pub fn is_imported(&self, file_path: &str) -> bool {
264 self.imported_files.contains(file_path)
265 }
266
267 #[must_use]
269 pub fn get_line_for_key(&self, key: &str) -> Option<usize> {
270 self.key_line_map.get(key).copied()
271 }
272}
273
274impl<'a> ExecutionDescriptor<'a> {
275 pub fn new(
277 parsed_outputs: Vec<AstNode<'a>>,
278 source: impl Into<std::borrow::Cow<'a, str>>,
279 ) -> Self {
280 let line_numbers = parsed_outputs.iter().map(AstNode::line).collect();
281
282 Self {
283 line_numbers,
284 context: ExecutionContext::new(source),
285 parsed_outputs,
286 parse_tasks: Vec::new(),
287 validation_tasks: Vec::new(),
288 execution_tasks: Vec::new(),
289 stats: ExecutionStats::default(),
290 }
291 }
292
293 pub fn add_parse_task(&mut self, task: ParseTask<'a>) {
295 self.parse_tasks.push(task);
296 }
297
298 pub fn add_parse_tasks(&mut self, tasks: Vec<ParseTask<'a>>) {
300 self.parse_tasks.extend(tasks);
301 }
302
303 pub fn add_validation_task(&mut self, task: ValidationTask<'a>) {
305 self.validation_tasks.push(task);
306 }
307
308 pub fn add_validation_tasks(&mut self, tasks: Vec<ValidationTask<'a>>) {
310 self.validation_tasks.extend(tasks);
311 }
312
313 pub fn add_execution_task(&mut self, task: ExecutionTask<'a>) {
315 self.execution_tasks.push(task);
316 }
317
318 pub fn add_execution_tasks(&mut self, tasks: Vec<ExecutionTask<'a>>) {
320 self.execution_tasks.extend(tasks);
321 }
322
323 #[must_use]
325 pub const fn task_count(&self) -> usize {
326 self.parse_tasks.len() + self.validation_tasks.len() + self.execution_tasks.len()
327 }
328
329 #[must_use]
331 pub fn task_summary(&self) -> String {
332 format!(
333 "Parse tasks: {}, Validation tasks: {}, Execution tasks: {}",
334 self.parse_tasks.len(),
335 self.validation_tasks.len(),
336 self.execution_tasks.len()
337 )
338 }
339
340 pub const fn update_stats(&mut self, stats: ExecutionStats) {
342 self.stats = stats;
343 }
344
345 #[must_use]
347 pub fn source(&self) -> &str {
348 &self.context.source
349 }
350
351 pub const fn context_mut(&mut self) -> &mut ExecutionContext<'a> {
353 &mut self.context
354 }
355
356 #[must_use]
358 pub const fn context(&self) -> &ExecutionContext<'a> {
359 &self.context
360 }
361}
362
363#[cfg(test)]
364mod tests {
365 use super::*;
366
367 #[test]
368 fn test_execution_context_scope_management() {
369 let mut ctx = ExecutionContext::new("test.aam".to_string());
370 assert_eq!(ctx.current_scope(), "root");
371
372 ctx.push_scope("section".to_string());
373 assert_eq!(ctx.current_scope(), "root::section");
374
375 ctx.pop_scope();
376 assert_eq!(ctx.current_scope(), "root");
377 }
378
379 #[test]
380 fn test_execution_context_key_value_operations() {
381 let mut ctx = ExecutionContext::new("test.aam".to_string());
382 ctx.set_value("key1", "value1", 1);
383
384 assert_eq!(ctx.get_value("key1"), Some("value1"));
385 assert_eq!(ctx.get_line_for_key("key1"), Some(1));
386 }
387
388 #[test]
389 fn test_visited_keys_tracking() {
390 let mut ctx = ExecutionContext::new("test.aam".to_string());
391 assert!(!ctx.is_visited("key1"));
392
393 ctx.mark_visited("key1");
394 assert!(ctx.is_visited("key1"));
395
396 ctx.reset_visited();
397 assert!(!ctx.is_visited("key1"));
398 }
399
400 #[test]
401 fn test_execution_descriptor_task_management() {
402 let mut desc = ExecutionDescriptor::new(vec![], "test.aam".to_string());
403
404 desc.add_validation_task(ValidationTask::VerifySchemaExists {
405 schema_name: "MySchema".to_string().into(),
406 line: 1,
407 });
408
409 assert_eq!(desc.validation_tasks.len(), 1);
410 assert_eq!(desc.task_count(), 1);
411 }
412}