ninja_parse/
lib.rs

1/*
2 * Copyright 2020 Nikhil Marathe <nsm.nikhil@gmail.com>
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *     http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#![feature(is_sorted)]
18// Holding place until we figure out refactor.
19use 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        // Insert built-in rules.
110        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            // TODO: Also add line/col information from token position, which isn't being preserved
130            // right now!
131            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        // TODO: Use the environment in scope + the rule environment.
147        // TODO: Are the build bindings available to the input and output path evaluation?
148
149        for output in &build.outputs {
150            let output = output.eval(&build.bindings);
151            if self.outputs_seen.contains(&output) {
152                // TODO: Also add line/col information from token position, which isn't being preserved
153                // right now!
154                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        // TODO: Note that any rule/build level binding can refer to these variables, so the entire
181        // build statement evaluation must have this environment available. In addition, these are
182        // "shell quoted" when expanding within a command.
183        // TODO: Get environment from rule!
184        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}