diff --git a/make/src/config.rs b/make/src/config.rs index 1ce5f1b8..e8106083 100644 --- a/make/src/config.rs +++ b/make/src/config.rs @@ -7,7 +7,7 @@ // SPDX-License-Identifier: MIT // -use std::collections::{BTreeMap, BTreeSet}; +use std::collections::{BTreeMap, BTreeSet, HashMap}; /// Represents the configuration of the make utility #[derive(Debug, Clone, PartialEq, Eq)] @@ -36,6 +36,7 @@ pub struct Config { pub precious: bool, pub rules: BTreeMap>, + pub macros: HashMap, } impl Default for Config { @@ -113,6 +114,7 @@ impl Default for Config { .collect::>(), ) ]), + macros: HashMap::new() } } } diff --git a/make/src/lib.rs b/make/src/lib.rs index 74abb027..a7204ded 100644 --- a/make/src/lib.rs +++ b/make/src/lib.rs @@ -20,7 +20,7 @@ use std::{ time::SystemTime, }; -use parser::{Makefile, VariableDefinition}; +use parser::Makefile; use crate::special_target::InferenceTarget; use config::Config; @@ -38,7 +38,6 @@ const DEFAULT_SHELL: &str = "/bin/sh"; /// /// The only way to create a Make is from a Makefile and a Config. pub struct Make { - macros: Vec, rules: Vec, default_rule: Option, // .DEFAULT pub config: Config, @@ -111,7 +110,7 @@ impl Make { for prerequisite in &newer_prerequisites { self.build_target(prerequisite)?; } - rule.run(&self.config, &self.macros, target, up_to_date)?; + rule.run(&self.config, target, up_to_date)?; Ok(true) } @@ -183,11 +182,17 @@ impl Make { impl TryFrom<(Makefile, Config)> for Make { type Error = ErrorCode; - fn try_from((makefile, config): (Makefile, Config)) -> Result { + fn try_from((makefile, mut config): (Makefile, Config)) -> Result { let mut rules = vec![]; let mut special_rules = vec![]; let mut inference_rules = vec![]; + for macr in makefile.macros() { + config + .macros + .insert(macr.name().unwrap(), macr.raw_value().unwrap()); + } + for rule in makefile.rules() { let rule = Rule::from(rule); let Some(target) = rule.targets().next() else { @@ -205,7 +210,6 @@ impl TryFrom<(Makefile, Config)> for Make { let mut make = Self { rules, - macros: makefile.variable_definitions().collect(), default_rule: None, config, }; diff --git a/make/src/main.rs b/make/src/main.rs index f4eaa4ab..a4636f81 100644 --- a/make/src/main.rs +++ b/make/src/main.rs @@ -8,7 +8,7 @@ // use core::str::FromStr; -use std::collections::{BTreeMap, BTreeSet}; +use std::collections::{BTreeMap, BTreeSet, HashMap}; use std::ffi::OsString; use std::io::Read; use std::path::{Path, PathBuf}; @@ -179,6 +179,10 @@ fn main() -> Result<(), Box> { keep_going, mut targets, } = Args::parse(); + let target_list = targets + .iter() + .filter_map(|x| x.clone().into_string().ok()) + .fold(String::new(), |acc, x| acc + " " + &x); let mut status_code = 0; @@ -199,6 +203,10 @@ fn main() -> Result<(), Box> { print, precious: false, terminate, + macros: HashMap::from([ + (String::from("MAKECMDGOALS"), target_list), + (String::from("MAKE"), env::args().next().unwrap()), + ]), ..Default::default() }; diff --git a/make/src/parser/lex.rs b/make/src/parser/lex.rs index 8dd1496d..d68c53e0 100644 --- a/make/src/parser/lex.rs +++ b/make/src/parser/lex.rs @@ -27,10 +27,6 @@ impl<'a> Lexer<'a> { } } - fn is_whitespace(c: char) -> bool { - c == ' ' || c == '\t' - } - fn is_newline(c: char) -> bool { c == '\n' || c == '\r' } @@ -64,6 +60,9 @@ impl<'a> Lexer<'a> { /// - `None` if the input is exhausted. /// fn next_token(&mut self) -> Option<(SyntaxKind, String)> { + while matches!(self.input.peek(), Some(' ')) { + self.input.next(); + } if let Some(&c) = self.input.peek() { match (c, self.line_type) { ('\t', None) => { @@ -96,9 +95,6 @@ impl<'a> Lexer<'a> { Some((SyntaxKind::TEXT, self.read_while(|c| !Self::is_newline(c)))) } LineType::Other => match c { - c if Self::is_whitespace(c) => { - Some((SyntaxKind::WHITESPACE, self.read_while(Self::is_whitespace))) - } c if Self::is_valid_identifier_char(c) => { let ident = self.read_while(Self::is_valid_identifier_char); diff --git a/make/src/parser/mod.rs b/make/src/parser/mod.rs index c2437724..8cf3eefb 100644 --- a/make/src/parser/mod.rs +++ b/make/src/parser/mod.rs @@ -10,7 +10,7 @@ pub mod lex; pub mod parse; pub mod preprocessor; -pub use parse::{Identifier, Makefile, Rule, VariableDefinition}; +pub use parse::{Identifier, MacroDef, Makefile, Rule}; /// Let's start with defining all kinds of tokens and /// composite nodes. @@ -71,6 +71,7 @@ pub enum SyntaxKind { RECIPE, VARIABLE, EXPR, + MACRODEF, MACRO, } diff --git a/make/src/parser/parse.rs b/make/src/parser/parse.rs index e9335216..890096a4 100644 --- a/make/src/parser/parse.rs +++ b/make/src/parser/parse.rs @@ -75,7 +75,6 @@ impl rowan::Language for Lang { use rowan::GreenNode; use super::SyntaxKind; -use crate::parser::preprocessor::preprocess; /// You can construct GreenNodes by hand, but a builder /// is helpful for top-down parsers: it maintains a stack /// of currently in-progress nodes @@ -144,7 +143,7 @@ pub fn parse(text: &str) -> Result { self.builder.start_node(RECIPE.into()); self.expect(INDENT); self.expect(TEXT); - self.expect(NEWLINE); + self.try_expect(NEWLINE); self.builder.finish_node(); } @@ -156,18 +155,30 @@ pub fn parse(text: &str) -> Result { self.expect(IDENTIFIER); self.skip_ws(); } - self.expect(NEWLINE); + self.try_expect(NEWLINE); self.builder.token(IDENTIFIER.into(), "variables.mk"); dbg!(&self.builder); self.builder.finish_node(); } + fn parse_macro_defintion(&mut self) { + self.builder.start_node(MACRODEF.into()); + self.try_expect(EXPORT); + self.expect(IDENTIFIER); + self.expect(EQUALS); + self.parse_expr(); + self.builder.finish_node(); + } + fn parse_rule(&mut self) { self.builder.start_node(RULE.into()); self.skip_ws(); self.try_expect(EXPORT); self.skip_ws(); - self.expect(IDENTIFIER); + let is_pattern = self.try_expect(PERCENT); + if !is_pattern { + self.expect(IDENTIFIER); + } self.skip_ws(); if self.tokens.pop() == Some((COLON, ":".to_string())) { self.builder.token(COLON.into(), ":"); @@ -176,7 +187,7 @@ pub fn parse(text: &str) -> Result { } self.skip_ws(); self.parse_expr(); - self.expect(NEWLINE); + self.try_expect(NEWLINE); loop { match self.current() { Some(INDENT) => { @@ -197,7 +208,10 @@ pub fn parse(text: &str) -> Result { fn parse(mut self) -> Parse { self.builder.start_node(ROOT.into()); loop { - match self.find(|&&(k, _)| k == COLON || k == NEWLINE || k == INCLUDE) { + match self + .find(|&&(k, _)| k == COLON || k == NEWLINE || k == INCLUDE || k == EQUALS) + { + Some((EQUALS, "=")) => self.parse_macro_defintion(), Some((COLON, ":")) => { self.parse_rule(); } @@ -347,12 +361,16 @@ macro_rules! ast_node { }; } +ast_node!(Macro, MACRO); +ast_node!(MacroDef, MACRODEF); ast_node!(Makefile, ROOT); ast_node!(Rule, RULE); ast_node!(Identifier, IDENTIFIER); -ast_node!(VariableDefinition, VARIABLE); -impl VariableDefinition { +impl Macro {} +impl MacroDef {} + +impl MacroDef { pub fn name(&self) -> Option { self.syntax().children_with_tokens().find_map(|it| { it.as_token().and_then(|it| { @@ -403,15 +421,17 @@ impl Makefile { self.syntax().children().filter_map(Rule::cast) } + pub fn macros(&self) -> impl Iterator { + self.syntax().children().filter_map(MacroDef::cast) + } + pub fn rules_by_target<'a>(&'a self, target: &'a str) -> impl Iterator + 'a { self.rules() .filter(move |rule| rule.targets().any(|t| t == target)) } - pub fn variable_definitions(&self) -> impl Iterator { - self.syntax() - .children() - .filter_map(VariableDefinition::cast) + pub fn variable_definitions(&self) -> impl Iterator { + self.syntax().children().filter_map(MacroDef::cast) } pub fn add_rule(&mut self, target: &str) -> Rule { @@ -530,7 +550,6 @@ impl FromStr for Makefile { type Err = ParseError; fn from_str(s: &str) -> Result { - let processed = preprocess(s).map_err(|e| ParseError(vec![e.to_string()]))?; - parse(&processed).map(|node| node.root()) + parse(s).map(|node| node.root()) } } diff --git a/make/src/parser/preprocessor.rs b/make/src/parser/preprocessor.rs index a9a6076f..361395af 100644 --- a/make/src/parser/preprocessor.rs +++ b/make/src/parser/preprocessor.rs @@ -1,8 +1,10 @@ -use std::collections::HashMap; +use crate::rule::prerequisite::Prerequisite; +use crate::rule::target::Target; +use std::collections::{HashMap, HashSet}; use std::fmt::{Debug, Display, Formatter}; -use std::fs; use std::iter::Peekable; -use std::path::Path; +use std::path::PathBuf; +use std::process::Command; use std::sync::atomic::AtomicBool; use std::sync::atomic::Ordering::Acquire; @@ -39,7 +41,7 @@ fn skip_blank(letters: &mut Peekable>) { } fn suitable_ident(c: &char) -> bool { - c.is_alphanumeric() || matches!(c, '_' | '.') + c.is_alphanumeric() || matches!(c, '_' | '.' | '-') } fn get_ident(letters: &mut Peekable>) -> Result { @@ -64,7 +66,7 @@ fn take_till_eol(letters: &mut Peekable>) -> String { let mut content = String::new(); while let Some(letter) = letters.peek() { - if matches!(letter, '\n' | '#') { + if matches!(letter, '\n' | '#') && !content.ends_with('\\') { break; }; content.push(*letter); @@ -76,10 +78,15 @@ fn take_till_eol(letters: &mut Peekable>) -> String { /// Searches for all the lines in makefile that resemble macro definition /// and creates hashtable from macro names and bodies -fn generate_macro_table( +pub fn generate_macro_table<'a>( source: &str, + target: &Target, + files: &(PathBuf, PathBuf), + prereqs: impl Iterator + Clone, ) -> std::result::Result, PreprocError> { - let macro_defs = source.lines().filter(|line| line.contains('=')); + let macro_defs = source + .lines() + .filter(|line| line.contains('=') && line.strip_prefix('\t').is_none()); let mut macro_table = HashMap::::new(); for def in macro_defs { @@ -94,6 +101,12 @@ fn generate_macro_table( } let mut text = def.chars().peekable(); + let line = take_till_eol(&mut text); + if line.is_empty() || line.split_whitespace().next().is_none() { + continue; + } + + let mut text = line.chars().peekable(); let mut macro_name = get_ident(&mut text)?; if macro_name == "export" { @@ -141,7 +154,7 @@ fn generate_macro_table( }; Operator::Plus } - c => Err(PreprocError::UnexpectedSymbol(c))?, + c => Err(dbg!(PreprocError::UnexpectedSymbol(c)))?, }; skip_blank(&mut text); let mut macro_body = take_till_eol(&mut text); @@ -149,7 +162,8 @@ fn generate_macro_table( match operator { Operator::Equals => {} Operator::Colon | Operator::Colon2 => loop { - let (result, substitutions) = substitute(¯o_body, ¯o_table)?; + let (result, substitutions) = + substitute(¯o_body, ¯o_table, target, files, prereqs.clone())?; if substitutions == 0 { break; } else { @@ -157,14 +171,13 @@ fn generate_macro_table( } }, Operator::Colon3 => { - macro_body = substitute(¯o_body, ¯o_table)?.0; + macro_body = + substitute(¯o_body, ¯o_table, target, files, prereqs.clone())?.0; } Operator::Bang => { - macro_body = substitute(¯o_body, ¯o_table)?.0; - let Ok(result) = std::process::Command::new("sh") - .args(["-c", ¯o_body]) - .output() - else { + macro_body = + substitute(¯o_body, ¯o_table, target, files, prereqs.clone())?.0; + let Ok(result) = Command::new("sh").args(["-c", ¯o_body]).output() else { Err(PreprocError::CommandFailed)? }; macro_body = String::from_utf8_lossy(&result.stdout).to_string(); @@ -189,7 +202,145 @@ fn generate_macro_table( pub static ENV_MACROS: AtomicBool = AtomicBool::new(false); -fn substitute(source: &str, table: &HashMap) -> Result<(String, u32)> { +#[derive(Debug)] +enum MacroFunction { + FilterOut, + Shell, + And, + Or, + If, +} + +fn detect_function(s: impl AsRef) -> Option { + match s.as_ref() { + "filter-out" => Some(MacroFunction::FilterOut), + "shell" => Some(MacroFunction::Shell), + "and" => Some(MacroFunction::And), + "or" => Some(MacroFunction::Or), + "if" => Some(MacroFunction::If), + _ => None, + } +} +fn parse_function<'a>( + f: MacroFunction, + src: &mut Peekable>, + table: &HashMap, + target: &Target, + files: &(PathBuf, PathBuf), + prereqs: impl Iterator + Clone, +) -> Result { + let mut args = String::new(); + let mut counter = 0; + for c in src.by_ref() { + if c == '(' || c == '{' { + counter += 1; + } + + if (c == ')' || c == '}') && counter == 0 { + break; + } + + if c == ')' || c == '}' { + counter -= 1; + } + + args.push(c); + } + + match f { + MacroFunction::FilterOut => { + let mut args = args.split(','); + let Some(pattern) = args.next() else { + Err(PreprocError::CommandFailed)? + }; + let (pattern, _) = substitute(pattern, table, target, files, prereqs.clone())?; + let Some(text) = args.next() else { + Err(PreprocError::CommandFailed)? + }; + let (text, _) = substitute(text, table, target, files, prereqs.clone())?; + + let patterns = pattern.split_whitespace(); + let words = text.split_whitespace(); + let pattern_set = HashSet::<&str>::from_iter(patterns.clone()); + + let result = words + .filter(|s| !pattern_set.contains(s)) + .fold(String::new(), |acc, s| acc + s); + + Ok(result) + } + MacroFunction::Shell => { + let output = Command::new("sh").args(["-c", &args]).output(); + let mut result = output + .into_iter() + .filter_map(|x| String::from_utf8(x.stdout).ok()); + result.next().ok_or(PreprocError::CommandFailed) + } + MacroFunction::If => { + let mut args = args.split(','); + let Some(cond) = args.next() else { + Err(PreprocError::CommandFailed)? + }; + let Some(on_true) = args.next() else { + Err(PreprocError::CommandFailed)? + }; + let on_false = args.next(); + + let (result, _) = substitute(cond, table, target, files, prereqs.clone())?; + let cond = result.split_whitespace().next().is_none(); + let (output, _) = if cond { + substitute(on_true, table, target, files, prereqs.clone())? + } else { + on_false + .iter() + .flat_map(|x| substitute(x, table, target, files, prereqs.clone())) + .next() + .unwrap_or_default() + }; + + Ok(output) + } + MacroFunction::And => { + let args = args.split(','); + let expanded = args.map(|x| substitute(x, table, target, files, prereqs.clone())); + let is_true = expanded.clone().all(|x| { + if let Ok((s, _)) = x { + s.split_whitespace().next().is_some() + } else { + false + } + }); + + let (result, _) = expanded.last().ok_or(PreprocError::CommandFailed)??; + Ok(if is_true { result } else { String::new() }) + } + MacroFunction::Or => { + let args = args.split(','); + let expanded = args.map(|x| substitute(x, table, target, files, prereqs.clone())); + let chosen = expanded.clone().find(|x| { + if let Ok((s, _)) = x { + s.split_whitespace().next().is_some() + } else { + false + } + }); + + Ok(if let Some(x) = chosen { + x?.0 + } else { + String::new() + }) + } + } +} + +fn substitute<'a>( + source: &str, + table: &HashMap, + target: &Target, + files: &(PathBuf, PathBuf), + mut prereqs: impl Iterator + Clone, +) -> Result<(String, u32)> { let env_macros = ENV_MACROS.load(Acquire); let mut substitutions = 0; @@ -211,8 +362,30 @@ fn substitute(source: &str, table: &HashMap) -> Result<(String, // yet as they will be dealt with in the // parsing stage with more context available c @ ('$' | '@' | '%' | '?' | '<' | '*') => { - result.push('$'); - result.push(c); + match c { + '@' => { + if let Some(s) = target.as_ref().split('(').next() { + result.push_str(s) + } + } + '%' => { + if let Some(body) = target.as_ref().split('(').nth(1) { + result.push_str(body.strip_suffix(')').unwrap_or(body)) + } + } + '?' => { + (&mut prereqs) + .map(|x| x.as_ref()) + .for_each(|x| result.push_str(x)); + } + '$' => result.push('$'), + '<' => result.push_str(files.0.to_str().unwrap()), + '*' => result.push_str(files.1.to_str().unwrap()), + _ => { + eprintln!("Unexpected `$`") + } + } + continue; } c if suitable_ident(&c) => { @@ -234,12 +407,22 @@ fn substitute(source: &str, table: &HashMap) -> Result<(String, let Ok(macro_name) = get_ident(&mut letters) else { Err(PreprocError::BadMacroName)? }; + + if let Some(name) = detect_function(¯o_name) { + let macro_body = + parse_function(name, &mut letters, table, target, files, prereqs.clone())?; + result.push_str(¯o_body); + substitutions += 1; + continue; + } + skip_blank(&mut letters); + let Some(finilizer) = letters.next() else { Err(PreprocError::UnexpectedEOF)? }; if !matches!(finilizer, ')' | '}') { - Err(PreprocError::UnexpectedSymbol(finilizer))? + Err(dbg!(PreprocError::UnexpectedSymbol(finilizer)))? } let env_macro = if env_macros { @@ -256,39 +439,13 @@ fn substitute(source: &str, table: &HashMap) -> Result<(String, continue; } - c => Err(PreprocError::UnexpectedSymbol(c))?, + c => Err(dbg!(PreprocError::UnexpectedSymbol(c)))?, } } Ok((result, substitutions)) } -/// Copy-pastes included makefiles into single one recursively. -/// Pretty much the same as C preprocessor and `#include` directive -fn process_include_lines(source: &str, table: &HashMap) -> (String, usize) { - let mut counter = 0; - let result = source - .lines() - .map(|x| { - if let Some(s) = x.strip_prefix("include") { - counter += 1; - let s = s.trim(); - let (source, _) = substitute(s, table).unwrap_or_default(); - let path = Path::new(&source); - - fs::read_to_string(path).unwrap() - } else { - x.to_string() - } - }) - .map(|mut x| { - x.push('\n'); - x - }) - .collect::(); - (result, counter) -} - fn remove_variables(source: &str) -> String { source .lines() @@ -302,20 +459,19 @@ fn remove_variables(source: &str) -> String { } /// Processes `include`s and macros -pub fn preprocess(source: &str) -> Result { +pub fn preprocess<'a>( + source: &str, + table: &HashMap, + target: &Target, + files: &(PathBuf, PathBuf), + prereqs: impl Iterator + Clone, +) -> Result { let mut source = source.to_string(); - let mut includes = 1; - let mut table = generate_macro_table(&source)?; - - while includes > 0 { - (source, includes) = process_include_lines(&source, &HashMap::new()); - table = generate_macro_table(&source)?; - } source = remove_variables(&source); loop { - let (result, substitutions) = substitute(&source, &table)?; + let (result, substitutions) = substitute(&source, table, target, files, prereqs.clone())?; if substitutions == 0 { break Ok(result); } else { diff --git a/make/src/rule.rs b/make/src/rule.rs index fe25cb04..c2817555 100644 --- a/make/src/rule.rs +++ b/make/src/rule.rs @@ -12,10 +12,11 @@ pub mod prerequisite; pub mod recipe; pub mod target; +use crate::parser::preprocessor::preprocess; use crate::{ config::Config as GlobalConfig, error_code::ErrorCode::{self, *}, - parser::{Rule as ParsedRule, VariableDefinition}, + parser::Rule as ParsedRule, signal_handler, DEFAULT_SHELL, DEFAULT_SHELL_VAR, }; use config::Config; @@ -35,6 +36,7 @@ use std::{ time::SystemTime, }; use target::Target; +// use crate::parser::MacroDef; type LazyArcMutex = LazyLock>>; @@ -58,7 +60,7 @@ impl Rule { self.targets.iter() } - pub fn prerequisites(&self) -> impl Iterator { + pub fn prerequisites(&self) -> impl Iterator + Clone { self.prerequisites.iter() } @@ -72,7 +74,6 @@ impl Rule { pub fn run( &self, global_config: &GlobalConfig, - macros: &[VariableDefinition], target: &Target, up_to_date: bool, ) -> Result<(), ErrorCode> { @@ -81,7 +82,7 @@ impl Rule { dry_run: global_dry_run, silent: global_silent, touch: global_touch, - env_macros: global_env_macros, + env_macros: _, quit: global_quit, clear: _, print: global_print, @@ -89,6 +90,7 @@ impl Rule { terminate: global_terminate, precious: global_precious, rules: _, + ref macros, } = *global_config; let Config { ignore: rule_ignore, @@ -124,7 +126,6 @@ impl Rule { let silent = global_silent || rule_silent || recipe_silent; let force_run = recipe_force_run; let touch = global_touch; - let env_macros = global_env_macros; let quit = global_quit; let print = global_print; let precious = global_precious || rule_precious; @@ -170,9 +171,16 @@ impl Rule { .unwrap_or(DEFAULT_SHELL), ); - self.init_env(env_macros, &mut command, macros); + // self.init_env(env_macros, &mut command, macros); + let recipe = self.substitute_general_macros( + recipe, + target, + macros, + &inout, + self.prerequisites(), + ); let recipe = - self.substitute_internal_macros(target, recipe, &inout, self.prerequisites()); + self.substitute_internal_macros(target, &recipe, &inout, self.prerequisites()); command.args(["-c", recipe.as_ref()]); let status = match command.status() { @@ -220,6 +228,19 @@ impl Rule { Ok(()) } + fn substitute_general_macros<'a>( + &self, + recipe: &Recipe, + target: &Target, + macros: &HashMap, + files: &(PathBuf, PathBuf), + prereqs: impl Iterator + Clone, + ) -> Recipe { + let recipe = recipe.inner(); + let result = preprocess(recipe, macros, target, files, prereqs).unwrap(); + Recipe::new(result) + } + fn substitute_internal_macros<'a>( &self, target: &Target, @@ -266,24 +287,24 @@ impl Rule { Recipe::new(result) } - /// A helper function to initialize env vars for shell commands. - fn init_env(&self, env_macros: bool, command: &mut Command, variables: &[VariableDefinition]) { - let mut macros: HashMap = variables - .iter() - .map(|v| { - ( - v.name().unwrap_or_default(), - v.raw_value().unwrap_or_default(), - ) - }) - .collect(); - - if env_macros { - let env_vars: HashMap = std::env::vars().collect(); - macros.extend(env_vars); - } - command.envs(macros); - } + // A helper function to initialize env vars for shell commands. + // fn init_env(&self, env_macros: bool, command: &mut Command, variables: &[MacroDef]) { + // let mut macros: HashMap = variables + // .iter() + // .map(|v| { + // ( + // v.name().unwrap_or_default(), + // v.raw_value().unwrap_or_default(), + // ) + // }) + // .collect(); + // + // if env_macros { + // let env_vars: HashMap = std::env::vars().collect(); + // macros.extend(env_vars); + // } + // command.envs(macros); + // } } impl From for Rule { diff --git a/make/tests/integration.rs b/make/tests/integration.rs index 9a583df0..080e7a31 100644 --- a/make/tests/integration.rs +++ b/make/tests/integration.rs @@ -372,7 +372,7 @@ mod macros { mod target_behavior { use super::*; use libc::{kill, SIGINT}; - use posixutils_make::parser::parse::ParseError; + use std::{thread, time::Duration}; #[test] @@ -380,11 +380,8 @@ mod target_behavior { run_test_helper( &["-f", "tests/makefiles/target_behavior/no_targets.mk"], "", - "make: parse error: *** No targets. Stop.\n\n", - ErrorCode::ParserError { - constraint: ParseError(vec![]), - } - .into(), + "make: no targets to execute\n", + 6, ); } diff --git a/make/tests/parser.rs b/make/tests/parser.rs index 70090c7a..64e6fef8 100644 --- a/make/tests/parser.rs +++ b/make/tests/parser.rs @@ -7,24 +7,36 @@ // mod preprocess { - use posixutils_make::parser::preprocessor::preprocess; + use posixutils_make::parser::preprocessor::{generate_macro_table, preprocess}; + use posixutils_make::rule::target::Target; + use std::path::PathBuf; #[test] fn test_macros_simple() { - const MACROS: &'static str = r#" -VAR = var + const MACROS: &str = r#"VAR = var V = ok all: - $(VAR) $V ${VAR} ${V} $(V) -"#; - - const EXPECTED: &'static str = r#" + $(VAR) $V ${VAR} ${V} $(V)"#; + const EXPECTED: &str = r#" all: - var ok var ok ok + var ok var ok ok "#; - let Ok(result) = preprocess(MACROS) else { + let table = generate_macro_table( + MACROS, + &Target::Simple { name: "test" }, + &(PathBuf::new(), PathBuf::new()), + [].iter(), + ) + .unwrap(); + let Ok(result) = preprocess( + MACROS, + &table, + &Target::Simple { name: "test" }, + &(PathBuf::new(), PathBuf::new()), + [].iter(), + ) else { panic!("Test must be preprocessed without an error") }; assert_eq!(result, EXPECTED); @@ -52,15 +64,12 @@ rule: prerequisite .collect::>(), vec![ (IDENTIFIER, "VARIABLE"), - (WHITESPACE, " "), (EQUALS, "="), - (WHITESPACE, " "), (IDENTIFIER, "value"), (NEWLINE, "\n"), (NEWLINE, "\n"), (IDENTIFIER, "rule"), (COLON, ":"), - (WHITESPACE, " "), (IDENTIFIER, "prerequisite"), (NEWLINE, "\n"), (INDENT, "\t"), @@ -92,7 +101,6 @@ rule: prerequisite .collect::>(), vec![ (EXPORT, "export"), - (WHITESPACE, " "), (IDENTIFIER, "VARIABLE"), (NEWLINE, "\n"), ] @@ -109,12 +117,9 @@ rule: prerequisite .collect::>(), vec![ (EXPORT, "export"), - (WHITESPACE, " "), (IDENTIFIER, "VARIABLE"), - (WHITESPACE, " "), (COLON, ":"), (EQUALS, "="), - (WHITESPACE, " "), (IDENTIFIER, "value"), (NEWLINE, "\n"), ] @@ -131,7 +136,6 @@ rule: prerequisite .collect::>(), [ (INCLUDE, "include"), - (WHITESPACE, " "), (IDENTIFIER, "FILENAME"), (NEWLINE, "\n") ] @@ -151,9 +155,7 @@ rule: prerequisite vec![ (IDENTIFIER, "rule"), (COLON, ":"), - (WHITESPACE, " "), (IDENTIFIER, "prerequisite1"), - (WHITESPACE, " "), (IDENTIFIER, "prerequisite2"), (NEWLINE, "\n"), (INDENT, "\t"), @@ -173,10 +175,8 @@ rule: prerequisite .collect::>(), vec![ (IDENTIFIER, "VARIABLE"), - (WHITESPACE, " "), (QUESTION, "?"), (EQUALS, "="), - (WHITESPACE, " "), (IDENTIFIER, "value"), (NEWLINE, "\n"), ] @@ -194,11 +194,9 @@ endif .collect::>(), vec![ (IDENTIFIER, "ifneq"), - (WHITESPACE, " "), (LPAREN, "("), (IDENTIFIER, "a"), (COMMA, ","), - (WHITESPACE, " "), (IDENTIFIER, "b"), (RPAREN, ")"), (NEWLINE, "\n"), @@ -217,9 +215,7 @@ endif .collect::>(), vec![ (IDENTIFIER, "VARIABLE"), - (WHITESPACE, " "), (EQUALS, "="), - (WHITESPACE, " "), (DOLLAR, "$"), (LPAREN, "("), (IDENTIFIER, "value"), @@ -238,9 +234,7 @@ endif .collect::>(), vec![ (IDENTIFIER, "VARIABLE"), - (WHITESPACE, " "), (EQUALS, "="), - (WHITESPACE, " "), (DOLLAR, "$"), (LPAREN, "("), (IDENTIFIER, "value"), @@ -256,20 +250,35 @@ endif } mod parse { - use posixutils_make::parser::preprocessor::preprocess; + use posixutils_make::parser::preprocessor::{generate_macro_table, preprocess}; use posixutils_make::parser::{parse::parse, Makefile}; + use posixutils_make::rule::target::Target; use rowan::ast::AstNode; + use std::path::PathBuf; #[test] fn test_parse_simple() { const SIMPLE: &str = r#"VARIABLE = command2 -rule: dependency - command - ${VARIABLE} - -"#; - let Ok(processed) = preprocess(SIMPLE) else { + rule: dependency + command + ${VARIABLE} + + "#; + let table = generate_macro_table( + SIMPLE, + &Target::Simple { name: "test" }, + &(PathBuf::new(), PathBuf::new()), + [].iter(), + ) + .unwrap(); + let Ok(processed) = preprocess( + SIMPLE, + &table, + &Target::Simple { name: "test" }, + &(PathBuf::new(), PathBuf::new()), + [].iter(), + ) else { panic!("Must be preprocessed without an error") }; let parsed = parse(&processed); @@ -280,22 +289,22 @@ rule: dependency format!("{:#?}", node), r#"ROOT@0..38 NEWLINE@0..1 "\n" - RULE@1..38 + RULE@1..37 IDENTIFIER@1..5 "rule" COLON@5..6 ":" - WHITESPACE@6..7 " " - EXPR@7..17 - IDENTIFIER@7..17 "dependency" - NEWLINE@17..18 "\n" - RECIPE@18..27 - INDENT@18..19 "\t" - TEXT@19..26 "command" - NEWLINE@26..27 "\n" - RECIPE@27..37 - INDENT@27..28 "\t" - TEXT@28..36 "command2" - NEWLINE@36..37 "\n" - NEWLINE@37..38 "\n" + EXPR@6..16 + IDENTIFIER@6..16 "dependency" + NEWLINE@16..17 "\n" + RECIPE@17..26 + INDENT@17..18 "\t" + TEXT@18..25 "command" + NEWLINE@25..26 "\n" + RECIPE@26..36 + INDENT@26..27 "\t" + TEXT@27..35 "command2" + NEWLINE@35..36 "\n" + NEWLINE@36..37 "\n" + NEWLINE@37..38 "\n" "# ); @@ -315,12 +324,26 @@ rule: dependency #[test] fn test_parse_export_assign() { const EXPORT: &str = r#"export VARIABLE := value -"#; - let Ok(processed) = preprocess(EXPORT).map_err(|e| println!("{e:?}")) else { + "#; + let table = generate_macro_table( + EXPORT, + &Target::Simple { name: "test" }, + &(PathBuf::new(), PathBuf::new()), + [].iter(), + ) + .unwrap(); + let Ok(processed) = preprocess( + EXPORT, + &table, + &Target::Simple { name: "test" }, + &(PathBuf::new(), PathBuf::new()), + [].iter(), + ) + .map_err(|e| println!("{e:?}")) else { panic!("Must be preprocessed without an error") }; let parsed = parse(&processed); - assert!(parsed.clone().err().is_some()); + assert!(parsed.clone().err().is_none()); } // TODO: create `include` test with real files @@ -365,21 +388,19 @@ rule: dependency let node = parsed.clone().unwrap().syntax(); assert_eq!( format!("{:#?}", node), - r#"ROOT@0..40 - RULE@0..40 + r#"ROOT@0..38 + RULE@0..38 IDENTIFIER@0..4 "rule" COLON@4..5 ":" - WHITESPACE@5..6 " " - EXPR@6..29 - IDENTIFIER@6..17 "dependency1" - WHITESPACE@17..18 " " - IDENTIFIER@18..29 "dependency2" - NEWLINE@29..30 "\n" - RECIPE@30..39 - INDENT@30..31 "\t" - TEXT@31..38 "command" - NEWLINE@38..39 "\n" - NEWLINE@39..40 "\n" + EXPR@5..27 + IDENTIFIER@5..16 "dependency1" + IDENTIFIER@16..27 "dependency2" + NEWLINE@27..28 "\n" + RECIPE@28..37 + INDENT@28..29 "\t" + TEXT@29..36 "command" + NEWLINE@36..37 "\n" + NEWLINE@37..38 "\n" "# ); let root = parsed.unwrap().root().clone_for_update();