1use indexmap::{IndexMap, IndexSet};
9use rustc_hash::FxBuildHasher;
10use thiserror::Error;
11
12use parser::{Spanned, AST};
13
14pub mod lexer;
15pub mod parser;
16pub mod runtime;
17
18pub(crate) type HashMap<K, V> = IndexMap<K, V, FxBuildHasher>;
19pub(crate) type HashSet<K> = IndexSet<K, FxBuildHasher>;
20
21#[derive(Debug, Clone)]
22pub struct Module {
23 expose: HashMap<Pragma, Spanned<String>>,
24 units: HashMap<Spanned<String>, Unit>,
25}
26
27impl Module {
28 pub fn lower(ast: Vec<AST>) -> Result<Self, Vec<LowerError>> {
29 let mut errors = Vec::new();
30
31 let mut unit_iter = ast.iter().filter(|a| matches!(a, AST::Unit(_, _)));
32 let mut units: HashMap<Spanned<String>, Unit> =
33 HashMap::with_capacity_and_hasher(unit_iter.size_hint().0, FxBuildHasher);
34
35 while let Some(AST::Unit(name, cmds)) = unit_iter.next() {
36 let res = Unit::lower(cmds);
37 let unit = match res {
38 Ok(u) => u,
39 Err(e) => {
40 errors.extend(e);
41 Unit::default()
44 }
45 };
46
47 if let Some((other, _)) = units.get_key_value(name) {
48 errors.push(LowerError::DuplicateUnit(other.clone(), name.clone()));
49 } else {
50 units.insert(name.clone(), unit);
51 }
52 }
53
54 let mut expose_iter = ast.into_iter().filter(|a| matches!(a, AST::Expose(_, _)));
55 let mut expose =
56 HashMap::with_capacity_and_hasher(expose_iter.size_hint().0, FxBuildHasher);
57
58 while let Some(AST::Expose(prag, unit)) = expose_iter.next() {
59 let mut err = false;
60 if expose.contains_key(&prag) {
61 err = true;
62 errors.push(LowerError::DuplicateExpose(prag, unit.clone()));
63 }
64 if !units.contains_key(&unit) {
65 err = true;
66 errors.push(LowerError::NameError(unit.clone()));
67 }
68 if err {
69 continue;
70 }
71
72 expose.insert(prag, unit.clone());
73 }
74
75 for unit in units.values() {
76 for u in &unit.depends_on {
77 if !units.contains_key(u) {
78 errors.push(LowerError::NameError(u.clone()));
79 }
80 }
81
82 let mut dos_iter = unit.commands.iter().filter(|c| matches!(c, Command::Do(_)));
83 while let Some(Command::Do(dos)) = dos_iter.next() {
84 for d in dos {
85 if !units.contains_key(d) {
86 errors.push(LowerError::NameError(d.clone()));
87 }
88 }
89 }
90 }
91
92 if !errors.is_empty() {
93 return Err(errors);
94 }
95 Ok(Self { expose, units })
96 }
97
98 pub fn units(&self) -> &HashMap<Spanned<String>, Unit> {
99 &self.units
100 }
101
102 pub fn unit_exists(&self, name: impl Into<String>) -> bool {
103 self.units.contains_key(&Spanned::new(name.into()))
104 }
105
106 pub fn unit_by_pragma(&self, pragma: Pragma) -> Option<String> {
107 self.expose.get(&pragma).map(Spanned::inner_owned)
108 }
109}
110
111#[derive(Debug, Clone, PartialEq, Eq, Error)]
112pub enum LowerError {
113 #[error("Attempted to define another binding for pragma {0:?}")]
114 DuplicateExpose(Pragma, Spanned<String>),
115 #[error("Attempted to define multiple units with the name {1}")]
116 DuplicateUnit(Spanned<String>, Spanned<String>),
117 #[error("Dependency {1} defined multiple times in the same unit")]
118 DuplicateDependency(Spanned<String>, Spanned<String>),
119 #[error("Meta item {1} defined multiple times in the same unit")]
120 DuplicateMetaItem(Spanned<String>, Spanned<String>),
121 #[error("Refers to a unit {0} that doesn't exist")]
122 NameError(Spanned<String>),
123}
124
125#[derive(Debug, Clone, PartialEq, Eq, Default)]
126pub struct Unit {
127 depends_on: HashSet<Spanned<String>>,
128 pub meta: HashMap<Spanned<String>, String>,
129 commands: Vec<Command>,
130}
131
132impl Unit {
133 pub fn lower(cmds: &[Command]) -> Result<Self, Vec<LowerError>> {
134 let mut errors = Vec::new();
135
136 let depends_iter = cmds
137 .iter()
138 .filter_map(|c| {
139 if let Command::DependsOn(deps) = c {
140 Some(deps)
141 } else {
142 None
143 }
144 })
145 .flatten();
146 let mut depends_on: HashSet<Spanned<String>> =
147 HashSet::with_capacity_and_hasher(depends_iter.size_hint().0, FxBuildHasher);
148
149 for dep in depends_iter {
150 if let Some(other) = depends_on.get(dep) {
151 errors.push(LowerError::DuplicateDependency(other.clone(), dep.clone()));
152 } else {
153 depends_on.insert(dep.clone());
154 }
155 }
156
157 let meta_iter = cmds.iter().filter_map(|c| {
158 if let Command::Meta(meta) = c {
159 Some(meta)
160 } else {
161 None
162 }
163 });
164 let mut meta: HashMap<Spanned<String>, String> =
165 HashMap::with_capacity_and_hasher(meta_iter.size_hint().0, FxBuildHasher);
166
167 for meta_items in meta_iter {
168 for (var, val) in meta_items {
169 if let Some((other, _)) = meta.get_key_value(var) {
170 errors.push(LowerError::DuplicateMetaItem(other.clone(), var.clone()));
171 } else {
172 meta.insert(var.clone(), val.clone());
173 }
174 }
175 }
176
177 if !errors.is_empty() {
178 return Err(errors);
179 }
180 Ok(Self {
181 depends_on,
182 meta,
183 commands: cmds
184 .iter()
185 .filter(|c| !matches!(c, Command::Meta(_) | Command::DependsOn(_)))
186 .cloned()
187 .collect(),
188 })
189 }
190
191 pub fn deps(&self) -> &HashSet<Spanned<String>> {
192 &self.depends_on
193 }
194}
195
196#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
197pub enum Pragma {
198 Test,
199 Build,
200}
201
202#[derive(Debug, Clone, PartialEq, Eq)]
203pub enum Command {
204 DependsOn(Vec<Spanned<String>>),
205 Meta(Vec<(Spanned<String>, String)>),
206 Do(Vec<Spanned<String>>),
207 Exec(Vec<Spanned<String>>),
208 Concurrent(Vec<Box<Command>>),
209
210 Fs(FsCommand),
211 Io(IoCommand),
212 Env(EnvCommand),
213}
214
215#[derive(Debug, Clone, PartialEq, Eq)]
216pub enum FsCommand {
217 Create(Spanned<String>),
218 CreateDir(Spanned<String>),
219 Remove(Spanned<String>),
220 Move(Spanned<String>, Spanned<String>),
221 MoveTo(
222 Spanned<String>,
223 Vec<(Spanned<String>, Option<Spanned<String>>)>,
224 ),
225 Copy(Spanned<String>, Spanned<String>),
226 CopyTo(
227 Spanned<String>,
228 Vec<(Spanned<String>, Option<Spanned<String>>)>,
229 ),
230 PrintFile(Spanned<String>),
231 EPrintFile(Spanned<String>),
232}
233
234#[derive(Debug, Clone, PartialEq, Eq)]
235pub enum IoCommand {
236 PrintLn(Spanned<String>),
237 Print(Spanned<String>),
238 EPrintLn(Spanned<String>),
239 EPrint(Spanned<String>),
240}
241
242#[derive(Debug, Clone, PartialEq, Eq)]
243pub enum EnvCommand {
244 SetVar(Spanned<String>, Spanned<String>),
245 RemoveVar(Spanned<String>),
246 PathPush(Spanned<String>),
247 PathRemove(Spanned<String>),
248}