blob: 7ee54933872268312e66893e9b175ecf116f4768 [file] [log] [blame]
//! Parsing of Makefile syntax as found in `.d` files emitted by C compilers.
use crate::scanner::{ParseResult, Scanner};
/// Dependency information for a single target.
#[derive(Debug)]
pub struct Deps<'a> {
/// Output name, as found in the `.d` input.
pub target: &'a str,
/// Input names, as found in the `.d` input.
pub deps: Vec<&'a str>,
}
/// Skip spaces and backslashed newlines.
fn skip_spaces(scanner: &mut Scanner) -> ParseResult<()> {
loop {
match scanner.read() {
' ' => {}
'\\' => match scanner.read() {
'\r' => scanner.expect('\n')?,
'\n' => {}
_ => return scanner.parse_error("invalid backslash escape"),
},
_ => {
scanner.back();
break;
}
}
}
Ok(())
}
/// Read one path from the input scanner.
/// Note: treats colon as a valid character in a path because of Windows-style
/// paths, but this means that the inital `output: ...` path will include the
/// trailing colon.
fn read_path<'a>(scanner: &mut Scanner<'a>) -> ParseResult<Option<&'a str>> {
skip_spaces(scanner)?;
let start = scanner.ofs;
loop {
match scanner.read() {
'\0' | ' ' | '\r' | '\n' => {
scanner.back();
break;
}
'\\' => {
let peek = scanner.peek();
if peek == '\n' || peek == '\r' {
scanner.back();
break;
}
}
_ => {}
}
}
let end = scanner.ofs;
if end == start {
return Ok(None);
}
Ok(Some(scanner.slice(start, end)))
}
/// Parse a `.d` file into `Deps`.
pub fn parse<'a>(scanner: &mut Scanner<'a>) -> ParseResult<Deps<'a>> {
let target = match read_path(scanner)? {
None => return scanner.parse_error("expected file"),
Some(o) => o,
};
scanner.skip_spaces();
let target = match target.strip_suffix(':') {
None => {
scanner.expect(':')?;
target
}
Some(target) => target,
};
let mut deps = Vec::new();
while let Some(p) = read_path(scanner)? {
deps.push(p);
}
scanner.skip('\r');
scanner.skip('\n');
scanner.skip_spaces();
scanner.expect('\0')?;
Ok(Deps { target, deps })
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::Path;
fn try_parse(buf: &mut Vec<u8>) -> Result<Deps, String> {
buf.push(0);
let mut scanner = Scanner::new(buf);
parse(&mut scanner).map_err(|err| scanner.format_parse_error(Path::new("test"), err))
}
fn must_parse(buf: &mut Vec<u8>) -> Deps {
match try_parse(buf) {
Err(err) => {
println!("{}", err);
panic!("failed parse");
}
Ok(d) => d,
}
}
fn test_for_crlf(input: &str, test: fn(String)) {
let crlf = input.replace('\n', "\r\n");
for test_case in [String::from(input), crlf] {
test(test_case);
}
}
#[test]
fn test_parse() {
test_for_crlf(
"build/browse.o: src/browse.cc src/browse.h build/browse_py.h\n",
|text| {
let mut file = text.into_bytes();
let deps = must_parse(&mut file);
assert_eq!(deps.target, "build/browse.o");
assert_eq!(deps.deps.len(), 3);
},
);
}
#[test]
fn test_parse_space_suffix() {
test_for_crlf("build/browse.o: src/browse.cc \n", |text| {
let mut file = text.into_bytes();
let deps = must_parse(&mut file);
assert_eq!(deps.target, "build/browse.o");
assert_eq!(deps.deps.len(), 1);
});
}
#[test]
fn test_parse_multiline() {
test_for_crlf(
"build/browse.o: src/browse.cc\\\n build/browse_py.h",
|text| {
let mut file = text.into_bytes();
let deps = must_parse(&mut file);
assert_eq!(deps.target, "build/browse.o");
assert_eq!(deps.deps.len(), 2);
},
);
}
#[test]
fn test_parse_without_final_newline() {
let mut file = b"build/browse.o: src/browse.cc".to_vec();
let deps = must_parse(&mut file);
assert_eq!(deps.target, "build/browse.o");
assert_eq!(deps.deps.len(), 1);
}
#[test]
fn test_parse_spaces_before_colon() {
let mut file = b"build/browse.o : src/browse.cc".to_vec();
let deps = must_parse(&mut file);
assert_eq!(deps.target, "build/browse.o");
assert_eq!(deps.deps.len(), 1);
}
#[test]
fn test_parse_windows_dep_path() {
let mut file = b"odd/path.o: C:/odd\\path.c".to_vec();
let deps = must_parse(&mut file);
assert_eq!(deps.target, "odd/path.o");
assert_eq!(deps.deps[0], "C:/odd\\path.c");
assert_eq!(deps.deps.len(), 1);
}
#[test]
fn test_parse_missing_colon() {
let mut file = b"foo bar".to_vec();
let err = try_parse(&mut file).unwrap_err();
assert!(
err.starts_with("parse error: expected ':'"),
"expected parse error, got {:?}",
err
);
}
}