impl<'src> MakefileParser<'src> {
#[must_use]
pub fn new(input: &'src str) -> Self {
Self {
input,
cursor: 0,
line: 1,
column: 1,
errors: Vec::new(),
}
}
fn safe_slice(&self, start: usize, end: usize) -> &str {
if self.input.is_empty() {
return "";
}
let bytes = self.input.as_bytes();
let len = bytes.len();
let start = start.min(len);
let end = end.min(len);
if start >= end {
return "";
}
let mut safe_start = start;
while safe_start > 0 && !self.input.is_char_boundary(safe_start) {
safe_start -= 1;
}
let mut safe_end = end;
while safe_end < len && !self.input.is_char_boundary(safe_end) {
safe_end += 1;
}
if safe_start > safe_end {
return "";
}
&self.input[safe_start..safe_end]
}
pub fn parse(&mut self) -> Result<MakefileAst, Vec<ParseError>> {
let mut ast = MakefileAst::new();
while !self.at_end() {
self.skip_whitespace_and_blank_lines();
if self.at_end() {
break;
}
if let Err(e) = self.parse_line(&mut ast) {
self.errors.push(e);
self.skip_to_next_line();
}
}
ast.metadata.target_count = ast.count_targets();
ast.metadata.has_phony_rules = !ast.get_phony_targets().is_empty();
ast.metadata.has_pattern_rules = ast.has_pattern_rules();
ast.metadata.uses_automatic_variables = ast.uses_automatic_variables();
if self.errors.is_empty() {
Ok(ast)
} else {
Err(self.errors.clone())
}
}
fn parse_line(&mut self, ast: &mut MakefileAst) -> Result<(), ParseError> {
let _start_pos = self.cursor;
let _start_line = self.line;
let _start_col = self.column;
self.skip_spaces();
if let Some(result) = self.try_parse_special_line(ast)? {
return result;
}
if let Some(line_type) = self.find_assignment_or_colon() {
self.parse_line_by_type(ast, line_type)?;
} else if self.is_directive_line() {
self.parse_directive_line(ast)?;
} else {
self.skip_to_next_line();
}
Ok(())
}
fn try_parse_special_line(
&mut self,
ast: &mut MakefileAst,
) -> Result<Option<Result<(), ParseError>>, ParseError> {
if self.peek() == Some('#') {
self.parse_comment(ast);
return Ok(Some(Ok(())));
}
if self.peek() == Some('\t') {
return Ok(Some(Err(ParseError::InvalidSyntax(
"Recipe without rule".to_string(),
))));
}
Ok(None)
}
fn parse_line_by_type(
&mut self,
ast: &mut MakefileAst,
line_type: LineType,
) -> Result<(), ParseError> {
match line_type {
LineType::Assignment(op_pos, op) => self.parse_variable(ast, op_pos, op),
LineType::Rule(colon_pos, is_double) => self.parse_rule(ast, colon_pos, is_double),
}
}
fn is_directive_line(&self) -> bool {
self.starts_with("include")
|| self.starts_with("-include")
|| self.is_conditional_directive()
}
fn is_conditional_directive(&self) -> bool {
self.starts_with("ifeq")
|| self.starts_with("ifneq")
|| self.starts_with("ifdef")
|| self.starts_with("ifndef")
}
fn parse_directive_line(&mut self, ast: &mut MakefileAst) -> Result<(), ParseError> {
if self.starts_with("include") || self.starts_with("-include") {
self.parse_include(ast)
} else {
self.parse_conditional(ast)
}
}
}