1use anyhow::{Context, Result};
2use std::path::{Path, PathBuf};
3
4use intern::{GetStr, InternStr};
5use syntax::ast;
6use util::{HashMap, Hasher, IdVec, PathEncodingError};
7
8use crate::{
9 branch::parse_compact_branch_str, AbstractTaskId, AbstractValueId, BranchSpec, Error, IdentId,
10 LiteralId, ModuleId, Plan, Task, Value, WorkflowStrings,
11};
12
13#[derive(Debug, Default)]
15pub struct SizeHints {
16 pub max_inputs: u8,
17 pub max_outputs: u8,
18 pub max_params: u8,
19 pub max_vars: u8,
20}
21
22#[derive(Debug)]
25pub struct Workflow {
26 pub strings: WorkflowStrings,
28 config: HashMap<IdentId, AbstractValueId>,
30 tasks: IdVec<AbstractTaskId, Task>,
32 plans: Vec<(IdentId, Plan)>,
34 modules: IdVec<ModuleId, LiteralId>,
36 values: IdVec<AbstractValueId, Value>,
38 sizes: SizeHints,
40}
41
42impl Default for Workflow {
43 fn default() -> Self {
44 Self {
45 strings: WorkflowStrings::default(),
46 config: HashMap::with_capacity_and_hasher(64, Hasher::default()),
47 tasks: IdVec::with_capacity(16),
48 plans: Vec::with_capacity(8),
49 modules: IdVec::with_capacity(8),
50 values: IdVec::with_capacity(128),
51 sizes: SizeHints::default(),
52 }
53 }
54}
55
56impl Workflow {
57 #[rustfmt::skip]
60 pub fn load(&mut self, blocks: Vec<ast::Item>, config_dir: &Path) -> Result<()> {
61 for block in blocks {
62 match block {
63 ast::Item::GlobalConfig(assts) => self.add_config(assts)?,
64 ast::Item::Task(task) => self.add_task(task)?,
65 ast::Item::Plan(plan) => self.add_plan(plan)?,
66 ast::Item::Module(name, path) => self.add_module(name, path, config_dir)?,
67 _ => {
68 return Err(Error::Unsupported(
69 "blocks other than config, task, plan, module".to_owned(),
70 )
71 .into())
72 }
73 }
74 }
75 Ok(())
76 }
77
78 #[inline]
80 pub fn sizes(&self) -> &SizeHints {
81 &self.sizes
82 }
83
84 #[inline]
86 pub fn get_module_path(&self, module: ModuleId) -> Result<&str> {
87 let lit_id = self.modules.get(module).ok_or(Error::ModuleNotFound(module))?;
88 self.strings.literals.get(*lit_id)
89 }
90
91 #[inline]
93 pub fn get_task(&self, task: AbstractTaskId) -> Result<&Task, Error> {
94 self.tasks.get(task).filter(|t| t.exists).ok_or(Error::TaskNotFound(task))
95 }
96
97 #[inline]
99 pub fn get_value(&self, value: AbstractValueId) -> Result<&Value, Error> {
100 self.values.get(value).ok_or(Error::ValueNotFound(value))
101 }
102
103 #[inline]
104 pub fn get_config_value(&self, ident: IdentId) -> Option<AbstractValueId> {
105 self.config.get(&ident).copied()
106 }
107
108 #[inline]
110 pub fn num_values(&self) -> usize {
111 self.values.len()
112 }
113
114 pub fn get_plan(&self, plan_name: IdentId) -> Result<&Plan, Error> {
116 for (k, plan) in &self.plans {
117 if *k == plan_name {
118 return Ok(plan);
119 }
120 }
121 Err(Error::PlanNotFound(plan_name))
122 }
123
124 #[inline]
127 pub fn parse_compact_branch_str(&mut self, s: &str) -> Result<BranchSpec> {
128 parse_compact_branch_str(self, s)
129 }
130}
131
132impl Workflow {
134 fn add_config(&mut self, assignments: Vec<(&str, ast::Rhs)>) -> Result<()> {
135 for (lhs, rhs) in assignments {
136 let v = self.strings.create_value(lhs, rhs)?;
137 let vid = self.values.push(v);
138 let k = self.strings.idents.intern(lhs)?;
139 self.config.insert(k, vid);
140 }
141 Ok(())
142 }
143
144 fn add_task(&mut self, task: ast::TasklikeBlock) -> Result<()> {
145 let name_id = self.strings.tasks.intern(task.name)?;
146 let task = Task::create(task, &mut self.strings, &mut self.values)?;
147 self.update_sizes(&task);
148 self.tasks.insert(name_id, task);
152 Ok(())
153 }
154
155 fn update_sizes(&mut self, task: &Task) {
156 let num_inputs = task.vars.inputs.len() as u8;
157 let num_outputs = task.vars.outputs.len() as u8;
158 let num_params = task.vars.params.len() as u8;
159 let num_vars = num_inputs + num_outputs + num_params;
160 self.sizes.max_inputs = self.sizes.max_inputs.max(num_inputs);
161 self.sizes.max_outputs = self.sizes.max_outputs.max(num_outputs);
162 self.sizes.max_params = self.sizes.max_params.max(num_params);
163 self.sizes.max_vars = self.sizes.max_vars.max(num_vars);
164 }
165
166 fn add_plan(&mut self, plan: ast::Plan) -> Result<()> {
167 let plan_id = self.strings.idents.intern(plan.name)?;
168 let ast::Plan { cross_products, .. } = plan;
169
170 if cross_products.is_empty() {
173 return Err(Error::EmptyPlan(plan.name.to_owned()).into());
174 }
175
176 let plan = Plan::create(&mut self.strings, cross_products)
177 .with_context(|| format!("while creating AST for plan \"{}\"", plan.name))?;
178
179 self.plans.push((plan_id, plan));
182 Ok(())
183 }
184
185 fn add_module(&mut self, name: &str, path: ast::Rhs, config_dir: &Path) -> Result<()> {
186 let id = self.strings.modules.intern(name)?;
187 if let ast::Rhs::Literal { val } = path {
188 let mut path = PathBuf::from(val);
189
190 if path.is_relative() {
191 path = config_dir.join(path);
192 }
193
194 if path.exists() {
195 path = path.canonicalize()?;
196 } else {
197 log::debug!(
198 "Module path {:?} does not exist; this may cause errors later.",
199 path
200 );
201 }
202 let path_str = path.to_str().ok_or(PathEncodingError)?;
203 let literal_id = self.strings.literals.intern(path_str)?;
204 self.modules.insert(id, literal_id);
205 Ok(())
206 } else {
207 Err(Error::Unsupported(format!(
208 "Module values other than literal strings (in module \"{}\")",
209 name
210 ))
211 .into())
212 }
213 }
214}