1#![feature(is_sorted)]
18use ast as past;
20use ninja_metrics::scoped_metric;
21use std::{
22 cell::RefCell,
23 collections::{HashMap, HashSet},
24 rc::Rc,
25 str::Utf8Error,
26 string::FromUtf8Error,
27};
28use thiserror::Error;
29
30pub trait Loader {
31 fn load(&mut self, from: Option<&[u8]>, request: &[u8]) -> Result<Vec<u8>, std::io::Error>;
32}
33
34mod ast;
35mod env;
36mod lexer;
37mod parser;
38pub mod repr;
39
40use env::Env;
41use parser::{ParseError, Parser};
42pub use repr::*;
43
44#[derive(Error, Debug)]
45#[error("{position}: {inner}")]
46pub struct ProcessingErrorWithPosition {
47 inner: ProcessingError,
48 position: lexer::Position,
49}
50
51#[derive(Error, Debug)]
52pub enum ProcessingError {
53 #[error("utf-8 error")]
54 Utf8Error(#[from] Utf8Error),
55 #[error("string utf-8 error")]
56 StringUtf8Error(#[from] FromUtf8Error),
57 #[error("duplicate rule name: {0}")]
58 DuplicateRule(String),
59 #[error("duplicate output: {0}")]
60 DuplicateOutput(String),
61 #[error("build edge refers to unknown rule: {0}")]
62 UnknownRule(String),
63 #[error("missing 'command' for rule: {0}")]
64 MissingCommand(String),
65 #[error(transparent)]
66 ParseFailed(#[from] ParseError),
67 #[error(transparent)]
68 IoError(#[from] std::io::Error),
69 #[error(transparent)]
70 WithPosition(#[from] Box<ProcessingErrorWithPosition>),
71}
72
73impl ProcessingError {
74 fn with_position(self, position: lexer::Position) -> ProcessingErrorWithPosition {
75 ProcessingErrorWithPosition {
76 inner: self,
77 position,
78 }
79 }
80
81 fn with_position_boxed(self, position: lexer::Position) -> Box<ProcessingErrorWithPosition> {
82 Box::new(self.with_position(position))
83 }
84}
85
86const PHONY: &[u8] = &[112, 104, 111, 110, 121];
87
88fn space_seperated_paths(paths: &Vec<Vec<u8>>) -> Vec<u8> {
89 let mut vec = Vec::new();
90 for (i, el) in paths.iter().enumerate() {
91 vec.extend(el);
92 if i != paths.len() - 1 {
93 vec.push(b' ');
94 }
95 }
96 vec
97}
98
99struct ParseState {
100 known_rules: HashMap<Vec<u8>, past::Rule>,
101 outputs_seen: HashSet<Vec<u8>>,
102 description: Description,
103 bindings: Rc<RefCell<Env>>,
104}
105
106impl Default for ParseState {
107 fn default() -> Self {
108 let mut rules = HashMap::default();
109 rules.insert(
111 PHONY.to_vec(),
112 past::Rule {
113 name: PHONY.to_vec(),
114 bindings: HashMap::default(),
115 },
116 );
117 Self {
118 known_rules: rules,
119 outputs_seen: HashSet::default(),
120 description: Description::default(),
121 bindings: Rc::new(RefCell::new(Env::default())),
122 }
123 }
124}
125
126impl ParseState {
127 fn add_rule(&mut self, rule: past::Rule) -> Result<(), ProcessingError> {
128 if self.known_rules.get(&rule.name).is_some() {
129 Err(ProcessingError::DuplicateRule(
132 std::str::from_utf8(&rule.name)?.to_owned(),
133 ))
134 } else {
135 self.known_rules.insert(rule.name.clone(), rule);
136 Ok(())
137 }
138 }
139
140 fn add_build_edge(
141 &mut self,
142 build: past::Build,
143 _top: Rc<RefCell<Env>>,
144 ) -> Result<(), ProcessingError> {
145 let mut evaluated_outputs = Vec::with_capacity(build.outputs.len());
146 for output in &build.outputs {
150 let output = output.eval(&build.bindings);
151 if self.outputs_seen.contains(&output) {
152 return Err(ProcessingError::DuplicateOutput(
155 String::from_utf8(output)?.to_owned(),
156 ));
157 }
158 self.outputs_seen.insert(output.clone());
159 evaluated_outputs.push(output);
160 }
161
162 let evaluated_inputs: Vec<Vec<u8>> = build
163 .inputs
164 .iter()
165 .map(|i| i.eval(&build.bindings))
166 .collect();
167
168 let evaluated_implicit_inputs: Vec<Vec<u8>> = build
169 .implicit_inputs
170 .iter()
171 .map(|i| i.eval(&build.bindings))
172 .collect();
173
174 let evaluated_order_inputs: Vec<Vec<u8>> = build
175 .order_inputs
176 .iter()
177 .map(|i| i.eval(&build.bindings))
178 .collect();
179
180 let mut env = Env::with_parent(Rc::new(RefCell::new(build.bindings)));
185 env.add_binding(b"out".to_vec(), space_seperated_paths(&evaluated_outputs));
186 env.add_binding(b"in".to_vec(), space_seperated_paths(&evaluated_inputs));
187
188 let action = {
189 match build.rule.as_slice() {
190 [112, 104, 111, 110, 121] => Action::Phony,
191 other => {
192 let rule = self.known_rules.get(other);
193 if rule.is_none() {
194 return Err(ProcessingError::UnknownRule(
195 std::str::from_utf8(&other)?.to_owned(),
196 ));
197 }
198
199 let rule = rule.unwrap();
200 let command = rule.bindings.get("command".as_bytes());
201 if command.is_none() {
202 return Err(ProcessingError::MissingCommand(
203 std::str::from_utf8(&rule.name)?.to_owned(),
204 ));
205 }
206
207 Action::Command(String::from_utf8(
208 command.unwrap().eval_for_build(&env, &rule),
209 )?)
210 }
211 }
212 };
213 self.description.builds.push(Build {
214 action,
215 inputs: evaluated_inputs,
216 implicit_inputs: evaluated_implicit_inputs,
217 order_inputs: evaluated_order_inputs,
218 outputs: evaluated_outputs,
219 });
220 Ok(())
221 }
222
223 fn add_default(&mut self, entries: Vec<u8>) {
224 if self.description.defaults.is_none() {
225 self.description.defaults = Some(HashSet::new());
226 }
227 self.description.defaults.as_mut().unwrap().insert(entries);
228 }
229
230 fn into_description(self) -> Description {
231 self.description
232 }
233}
234
235fn parse_single(
236 contents: &[u8],
237 name: Option<Vec<u8>>,
238 state: &mut ParseState,
239 loader: &mut dyn Loader,
240) -> Result<(), ProcessingError> {
241 Parser::new(&contents, name).parse(state, loader)?;
242 Ok(())
243}
244
245pub fn build_representation(
246 loader: &mut dyn Loader,
247 start: Vec<u8>,
248) -> Result<Description, ProcessingError> {
249 scoped_metric!("parse");
250 let mut state = ParseState::default();
251 let contents = loader.load(None, &start)?;
252 parse_single(&contents, Some(start), &mut state, loader)?;
253 Ok(state.into_description())
254}
255
256#[cfg(test)]
257mod test {
258
259 use super::{ast as past, ParseState, ProcessingError};
260 use crate::env::Env;
261 use insta::assert_debug_snapshot;
262 use std::{cell::RefCell, rc::Rc};
263
264 macro_rules! lit {
265 ($name:expr) => {
266 past::Term::Literal($name.to_vec())
267 };
268 }
269
270 macro_rules! aref {
271 ($name:literal) => {
272 past::Term::Reference($name.to_vec())
273 };
274 }
275
276 macro_rules! rule {
277 ($name:literal) => {
278 past::Rule {
279 name: $name.as_bytes().to_vec(),
280 bindings: vec![(b"command".to_vec(), past::Expr(vec![lit!(b"")]))]
281 .into_iter()
282 .collect(),
283 }
284 };
285 ($name:literal, $command:literal) => {
286 past::Rule {
287 name: $name.as_bytes().to_vec(),
288 bindings: vec![(
289 b"command".to_vec(),
290 past::Expr(vec![lit!($command.as_bytes())]),
291 )]
292 .into_iter()
293 .collect(),
294 }
295 };
296 }
297
298 #[test]
299 fn no_rule_named_phony() {
300 let mut parse_state = ParseState::default();
301 let err = parse_state.add_rule(rule!["phony"]).unwrap_err();
302 assert!(matches!(err, ProcessingError::DuplicateRule(_)));
303 }
304
305 #[test]
306 fn err_duplicate_rule() {
307 let mut parse_state = ParseState::default();
308 let _ = parse_state.add_rule(rule!["link"]).unwrap();
309 let _ = parse_state.add_rule(rule!["compile"]).unwrap();
310 let err = parse_state.add_rule(rule!["link"]).expect_err("duplicate");
311 assert!(matches!(err, ProcessingError::DuplicateRule(_)));
312 }
313
314 #[test]
315 fn duplicate_output() {
316 let mut parse_state = ParseState::default();
317 let env = Rc::new(RefCell::new(Env::default()));
318 let _ = parse_state
319 .add_build_edge(
320 past::Build {
321 rule: b"phony".to_vec(),
322 outputs: vec![past::Expr(vec![lit!(b"a.txt")])],
323 ..Default::default()
324 },
325 env.clone(),
326 )
327 .unwrap();
328 let err = parse_state
329 .add_build_edge(
330 past::Build {
331 rule: b"phony".to_vec(),
332 outputs: vec![past::Expr(vec![lit!(b"a.txt")])],
333 ..Default::default()
334 },
335 env.clone(),
336 )
337 .expect_err("duplicate output");
338 assert!(matches!(err, ProcessingError::DuplicateOutput(_)));
339 }
340
341 #[test]
342 fn duplicate_output2() {
343 let mut parse_state = ParseState::default();
344 let env = Rc::new(RefCell::new(Env::default()));
345 let _ = parse_state
346 .add_build_edge(
347 past::Build {
348 rule: b"phony".to_vec(),
349 outputs: vec![
350 past::Expr(vec![lit!(b"b.txt")]),
351 past::Expr(vec![lit!(b"a.txt")]),
352 ],
353 ..Default::default()
354 },
355 env.clone(),
356 )
357 .unwrap();
358 let err = parse_state
359 .add_build_edge(
360 past::Build {
361 rule: b"phony".to_vec(),
362 outputs: vec![
363 past::Expr(vec![lit!(b"a.txt")]),
364 past::Expr(vec![lit!(b"c.txt")]),
365 ],
366 ..Default::default()
367 },
368 env.clone(),
369 )
370 .expect_err("duplicate output");
371 assert!(matches!(err, ProcessingError::DuplicateOutput(_)));
372 }
373
374 #[test]
375 fn unknown_rule() {
376 let mut parse_state = ParseState::default();
377 let env = Rc::new(RefCell::new(Env::default()));
378 let err = parse_state
379 .add_build_edge(
380 past::Build {
381 rule: b"baloney".to_vec(),
382 ..Default::default()
383 },
384 env,
385 )
386 .expect_err("unknown rule");
387 assert!(matches!(err, ProcessingError::UnknownRule(_)));
388 }
389
390 #[test]
391 fn success() {
392 let mut parse_state = ParseState::default();
393 let env = Rc::new(RefCell::new(Env::default()));
394
395 for rule in vec![
396 rule!["link", "link.exe"],
397 rule!["cc", "clang"],
398 rule!["unused"],
399 ] {
400 parse_state.add_rule(rule).unwrap();
401 }
402
403 for build in vec![
404 past::Build {
405 rule: b"phony".to_vec(),
406 inputs: vec![past::Expr(vec![lit!(b"source.txt")])],
407 outputs: vec![past::Expr(vec![lit!(b"a.txt")])],
408 ..Default::default()
409 },
410 past::Build {
411 rule: b"cc".to_vec(),
412 inputs: vec![
413 past::Expr(vec![lit!(b"hello.c")]),
414 past::Expr(vec![lit!(b"hello.h")]),
415 ],
416 outputs: vec![past::Expr(vec![lit!(b"hello.o")])],
417 ..Default::default()
418 },
419 past::Build {
420 rule: b"link".to_vec(),
421 inputs: vec![
422 past::Expr(vec![lit!(b"hello.o")]),
423 past::Expr(vec![lit!(b"my_shared_lib.so")]),
424 ],
425 outputs: vec![past::Expr(vec![lit!(b"hello")])],
426 ..Default::default()
427 },
428 ] {
429 parse_state.add_build_edge(build, env.clone()).unwrap();
430 }
431 let repr = parse_state.into_description();
432 assert_debug_snapshot!(repr);
433 }
434
435 #[test]
436 fn in_and_out_basic() {
437 let mut parse_state = ParseState::default();
438 let env = Rc::new(RefCell::new(Env::default()));
439 parse_state
440 .add_rule(past::Rule {
441 name: b"echo".to_vec(),
442 bindings: vec![(
443 b"command".to_vec(),
444 past::Expr(vec![
445 lit!(b"echo "),
446 aref!(b"in"),
447 lit!(b" makes "),
448 aref!(b"out"),
449 ]),
450 )]
451 .into_iter()
452 .collect(),
453 })
454 .unwrap();
455 for build in vec![past::Build {
456 rule: b"echo".to_vec(),
457 inputs: vec![
458 past::Expr(vec![lit!(b"a.txt")]),
459 past::Expr(vec![lit!(b"b.txt")]),
460 ],
461 outputs: vec![
462 past::Expr(vec![lit!(b"c.txt")]),
463 past::Expr(vec![lit!(b"d.txt")]),
464 ],
465 ..Default::default()
466 }] {
467 let _ = parse_state.add_build_edge(build, env.clone()).unwrap();
468 }
469 let repr = parse_state.into_description();
470 assert_debug_snapshot!(repr);
471 }
472}