use std::collections::HashMap;
#[cfg(feature = "full")]
use std::collections::HashSet;
use std::env;
use std::fmt;
#[cfg(feature = "full")]
use std::io::BufRead;
#[cfg(feature = "full")]
use super::eval_context::DeferredEvalContext;
#[cfg(feature = "full")]
use super::MacroScopeStack;
use super::{ItemSource, TokenString};
#[cfg(feature = "full")]
use eyre::Result;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Macro {
pub source: ItemSource,
pub text: TokenString,
#[cfg(feature = "full")]
pub eagerly_expanded: bool,
}
#[cfg(feature = "full")]
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ExportConfig {
Only(HashSet<String>),
AllBut(HashSet<String>),
}
#[cfg(feature = "full")]
impl ExportConfig {
pub fn all_but() -> Self {
Self::AllBut(HashSet::new())
}
pub fn only() -> Self {
Self::Only(HashSet::new())
}
pub fn add_all<'a, I: IntoIterator<Item = &'a str>>(&mut self, iter: I) {
match self {
Self::Only(exported) => {
exported.extend(iter.into_iter().map(|x| x.to_owned()));
}
Self::AllBut(not_exported) => {
for added in iter {
not_exported.remove(added);
}
}
}
}
pub fn remove_all<'a, I: IntoIterator<Item = &'a str>>(&mut self, iter: I) {
match self {
Self::Only(exported) => {
for removed in iter {
exported.remove(removed);
}
}
Self::AllBut(not_exported) => {
not_exported.extend(iter.into_iter().map(|x| x.into()));
}
}
}
fn should_export(&self, x: &str) -> bool {
match self {
Self::Only(exported) => exported.contains(x),
Self::AllBut(not_exported) => !not_exported.contains(x),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Set {
pub data: HashMap<String, Macro>,
#[cfg(feature = "full")]
pub exported: ExportConfig,
}
impl Set {
pub fn new() -> Self {
Self {
data: HashMap::new(),
#[cfg(feature = "full")]
exported: ExportConfig::only(),
}
}
pub fn add_builtins(&mut self) {
for (k, v) in builtins() {
self.data.insert(
k.into(),
Macro {
source: ItemSource::Builtin,
text: v,
#[cfg(feature = "full")]
eagerly_expanded: false,
},
);
}
}
pub fn add_env(&mut self) {
for (k, v) in env::vars() {
if k != "MAKEFLAGS" && k != "SHELL" {
self.data.insert(
k,
Macro {
source: ItemSource::Environment,
text: TokenString::text(v),
#[cfg(feature = "full")]
eagerly_expanded: false,
},
);
}
}
}
pub fn get_non_recursive(&self, name: &str) -> Option<&Macro> {
self.data.get(name)
}
pub fn set(&mut self, name: String, r#macro: Macro) {
self.data.insert(name, r#macro);
}
pub fn extend(&mut self, other: Self) {
#[cfg(feature = "full")]
match (&mut self.exported, other.exported) {
(ExportConfig::Only(se), ExportConfig::Only(oe)) => {
se.extend(oe);
}
(ExportConfig::AllBut(sne), ExportConfig::AllBut(one)) => {
sne.extend(one);
}
(ExportConfig::Only(se), ExportConfig::AllBut(one)) => {
se.extend(
other
.data
.keys()
.filter(|name| !one.contains(*name))
.cloned(),
);
}
(ExportConfig::AllBut(sne), ExportConfig::Only(oe)) => {
sne.extend(
other
.data
.keys()
.filter(|name| !oe.contains(*name))
.cloned(),
);
}
}
self.data.extend(other.data);
}
#[cfg(feature = "full")]
pub fn resolve_exports<R: BufRead>(
&self,
mut eval_context: Option<&mut DeferredEvalContext<R>>,
) -> Result<Vec<(&str, String)>> {
let own_exports = self
.data
.iter()
.filter(|(name, _)| self.exported.should_export(name))
.map(|(name, value)| {
MacroScopeStack::from_scope(self)
.expand(&value.text, eval_context.as_deref_mut())
.map(|text| (name.as_ref(), text))
})
.collect::<Result<Vec<_>>>()?;
Ok(own_exports)
}
}
impl fmt::Display for Set {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let pieces = self
.data
.iter()
.map(|(k, x)| format!("{}={}", k, &x.text))
.collect::<Vec<_>>();
write!(f, "{}", pieces.join("\n"))
}
}
impl Default for Set {
fn default() -> Self {
Self::new()
}
}
fn builtins() -> Vec<(&'static str, TokenString)> {
macro_rules! handle {
($value:ident) => {
stringify!($value).parse().unwrap()
};
($value:literal) => {
$value.parse().unwrap()
};
($value:expr) => {
$value
};
}
macro_rules! make {
($($name:ident=$value:tt)+) => {vec![$(
(stringify!($name), handle!($value))
),+]};
}
make![
AR=ar
YACC=yacc
YFLAGS=""
LEX=lex
LFLAGS=""
LDFLAGS=""
AS=as
CC=cc
CXX="g++"
CPP="$(CC) -E"
FC=f77
PC=pc
CO=co
GET=get
LINT=lint
MAKEINFO=makeinfo
TEX=tex
TEXI2DVI=texi2dvi
WEAVE=weave
CWEAVE=cweave
TANGLE=tangle
CTANGLE=ctangle
RM="rm -f"
ARFLAGS="rv"
CFLAGS=""
FFLAGS=""
]
}
#[cfg(test)]
mod test {
use super::*;
#[cfg(feature = "full")]
use crate::makefile::functions::NO_EVAL;
use crate::MacroScopeStack;
use eyre::Result;
type R = Result<()>;
#[test]
fn subst() -> R {
let mut macros = Set::new();
macros.set(
"oof".to_owned(),
Macro {
source: ItemSource::Builtin,
text: TokenString::text("bruh; swag; yeet;"),
#[cfg(feature = "full")]
eagerly_expanded: false,
},
);
assert_eq!(
MacroScopeStack::from_scope(¯os).expand(
&"$(oof:;=?)".parse()?,
#[cfg(feature = "full")]
NO_EVAL
)?,
"bruh? swag? yeet?"
);
Ok(())
}
#[test]
#[cfg(feature = "full")]
fn hell_subst() -> R {
let mut macros = Set::new();
macros.set(
"m".to_owned(),
Macro {
source: ItemSource::FunctionCall,
text: TokenString::text("conf"),
eagerly_expanded: false,
},
);
assert_eq!(
MacroScopeStack::from_scope(¯os).expand(&"$(m:%=%-objs)".parse()?, NO_EVAL)?,
"conf-objs"
);
Ok(())
}
}