use crate::pipeline::parser::AstNode;
use crate::pipeline::tasks::{ExecutionStats, ExecutionTask, ParseTask, ValidationTask};
use crate::pipeline::{PipelineBuildHasher, PipelineHashMap};
use smol_str::SmolStr;
use std::collections::HashSet;
#[inline]
fn new_pipeline_map<K, V>() -> PipelineHashMap<K, V> {
PipelineHashMap::with_hasher(PipelineBuildHasher::default())
}
#[derive(Debug)]
pub struct ExecutionDescriptor<'a> {
pub line_numbers: Vec<usize>,
pub context: ExecutionContext<'a>,
pub parsed_outputs: Vec<AstNode<'a>>,
pub parse_tasks: Vec<ParseTask<'a>>,
pub validation_tasks: Vec<ValidationTask<'a>>,
pub execution_tasks: Vec<ExecutionTask<'a>>,
pub stats: ExecutionStats,
}
#[derive(Debug)]
pub struct ExecutionContext<'a> {
pub source: std::borrow::Cow<'a, str>,
pub map: PipelineHashMap<SmolStr, SmolStr>,
pub schemas: PipelineHashMap<SmolStr, SchemaInfo>,
pub types: PipelineHashMap<SmolStr, TypeInfo>,
pub commands: PipelineHashMap<std::borrow::Cow<'a, str>, CommandInfo<'a>>,
pub key_line_map: PipelineHashMap<SmolStr, usize>,
pub scope_stack: Vec<std::borrow::Cow<'a, str>>,
pub visited_keys: HashSet<SmolStr>,
pub imported_files: HashSet<std::borrow::Cow<'a, str>>,
}
#[derive(Debug, Clone)]
pub struct SchemaInfo {
pub name: SmolStr,
pub fields: PipelineHashMap<SmolStr, (SmolStr, bool)>,
pub line: usize,
}
#[derive(Debug, Clone)]
pub struct TypeInfo {
pub name: SmolStr,
pub spec: SmolStr,
pub validator: Option<SmolStr>,
pub default_value: Option<SmolStr>,
pub metadata: PipelineHashMap<SmolStr, SmolStr>,
pub line: usize,
}
#[derive(Debug, Clone)]
pub struct CommandInfo<'a> {
pub name: std::borrow::Cow<'a, str>,
pub arg_pattern: std::borrow::Cow<'a, str>,
pub line: usize,
}
impl<'a> ExecutionContext<'a> {
pub fn new(source: impl Into<std::borrow::Cow<'a, str>>) -> Self {
let mut context = Self {
source: source.into(),
map: new_pipeline_map(),
schemas: new_pipeline_map(),
types: new_pipeline_map(),
commands: new_pipeline_map(),
key_line_map: new_pipeline_map(),
scope_stack: vec!["root".into()],
visited_keys: HashSet::default(),
imported_files: HashSet::default(),
};
context.register_builtin_type_defaults();
context
}
fn register_builtin_type_defaults(&mut self) {
for (name, default_value) in [
("i32", "0"),
("f64", "0"),
("bool", "false"),
("string", "\"\""),
("color", "#000000"),
] {
self.types.insert(
name.into(),
TypeInfo {
name: name.into(),
spec: name.into(),
validator: None,
default_value: Some(default_value.into()),
metadata: new_pipeline_map(),
line: 0,
},
);
}
}
pub fn default_value_for_type(&self, type_name: &str) -> Option<&str> {
self.types
.get(type_name)
.and_then(|info| info.default_value.as_deref())
}
pub fn current_scope(&self) -> String {
self.scope_stack.join("::")
}
pub fn push_scope(&mut self, scope_name: impl Into<std::borrow::Cow<'a, str>>) {
self.scope_stack.push(scope_name.into());
}
pub fn pop_scope(&mut self) {
if self.scope_stack.len() > 1 {
self.scope_stack.pop();
}
}
pub fn set_value(&mut self, key: impl AsRef<str>, value: impl AsRef<str>, line: usize) {
let key_str = key.as_ref();
let key_smol: SmolStr = key_str.into();
self.map.insert(key_smol.clone(), value.as_ref().into());
self.key_line_map.insert(key_smol, line);
}
pub fn get_value(&self, key: &str) -> Option<&str> {
self.map.get(key).map(|v| v.as_ref())
}
pub fn register_schema(&mut self, schema: SchemaInfo) {
self.schemas.insert(schema.name.clone(), schema);
}
pub fn register_type(&mut self, type_def: TypeInfo) {
self.types.insert(type_def.name.clone(), type_def);
}
pub fn register_command(&mut self, command: CommandInfo<'a>) {
self.commands.insert(command.name.clone(), command);
}
pub fn mark_visited(&mut self, key: &str) {
self.visited_keys.insert(key.into());
}
pub fn is_visited(&self, key: &str) -> bool {
self.visited_keys.contains(key)
}
pub fn reset_visited(&mut self) {
self.visited_keys.clear();
}
pub fn record_import(&mut self, file_path: impl Into<std::borrow::Cow<'a, str>>) {
self.imported_files.insert(file_path.into());
}
pub fn is_imported(&self, file_path: &str) -> bool {
self.imported_files.contains(file_path)
}
pub fn get_line_for_key(&self, key: &str) -> Option<usize> {
self.key_line_map.get(key).copied()
}
}
impl<'a> ExecutionDescriptor<'a> {
pub fn new(
parsed_outputs: Vec<AstNode<'a>>,
source: impl Into<std::borrow::Cow<'a, str>>,
) -> Self {
let line_numbers = parsed_outputs.iter().map(AstNode::line).collect();
Self {
line_numbers,
context: ExecutionContext::new(source),
parsed_outputs,
parse_tasks: Vec::new(),
validation_tasks: Vec::new(),
execution_tasks: Vec::new(),
stats: ExecutionStats::default(),
}
}
pub fn add_parse_task(&mut self, task: ParseTask<'a>) {
self.parse_tasks.push(task);
}
pub fn add_parse_tasks(&mut self, tasks: Vec<ParseTask<'a>>) {
self.parse_tasks.extend(tasks);
}
pub fn add_validation_task(&mut self, task: ValidationTask<'a>) {
self.validation_tasks.push(task);
}
pub fn add_validation_tasks(&mut self, tasks: Vec<ValidationTask<'a>>) {
self.validation_tasks.extend(tasks);
}
pub fn add_execution_task(&mut self, task: ExecutionTask<'a>) {
self.execution_tasks.push(task);
}
pub fn add_execution_tasks(&mut self, tasks: Vec<ExecutionTask<'a>>) {
self.execution_tasks.extend(tasks);
}
pub fn task_count(&self) -> usize {
self.parse_tasks.len() + self.validation_tasks.len() + self.execution_tasks.len()
}
pub fn task_summary(&self) -> String {
format!(
"Parse tasks: {}, Validation tasks: {}, Execution tasks: {}",
self.parse_tasks.len(),
self.validation_tasks.len(),
self.execution_tasks.len()
)
}
pub fn update_stats(&mut self, stats: ExecutionStats) {
self.stats = stats;
}
pub fn source(&self) -> &str {
&self.context.source
}
pub fn context_mut(&mut self) -> &mut ExecutionContext<'a> {
&mut self.context
}
pub fn context(&self) -> &ExecutionContext<'a> {
&self.context
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_execution_context_scope_management() {
let mut ctx = ExecutionContext::new("test.aam".to_string());
assert_eq!(ctx.current_scope(), "root");
ctx.push_scope("section".to_string());
assert_eq!(ctx.current_scope(), "root::section");
ctx.pop_scope();
assert_eq!(ctx.current_scope(), "root");
}
#[test]
fn test_execution_context_key_value_operations() {
let mut ctx = ExecutionContext::new("test.aam".to_string());
ctx.set_value("key1".to_string(), "value1".to_string(), 1);
assert_eq!(ctx.get_value("key1"), Some("value1"));
assert_eq!(ctx.get_line_for_key("key1"), Some(1));
}
#[test]
fn test_visited_keys_tracking() {
let mut ctx = ExecutionContext::new("test.aam".to_string());
assert!(!ctx.is_visited("key1"));
ctx.mark_visited("key1");
assert!(ctx.is_visited("key1"));
ctx.reset_visited();
assert!(!ctx.is_visited("key1"));
}
#[test]
fn test_execution_descriptor_task_management() {
let mut desc = ExecutionDescriptor::new(vec![], "test.aam".to_string());
desc.add_validation_task(ValidationTask::VerifySchemaExists {
schema_name: "MySchema".to_string().into(),
line: 1,
});
assert_eq!(desc.validation_tasks.len(), 1);
assert_eq!(desc.task_count(), 1);
}
}