| //! Represents parsed Ninja strings with embedded variable references, e.g. |
| //! `c++ $in -o $out`, and mechanisms for expanding those into plain strings. |
| |
| use rustc_hash::FxHashMap; |
| |
| use crate::smallmap::SmallMap; |
| use std::borrow::Borrow; |
| use std::borrow::Cow; |
| |
| /// An environment providing a mapping of variable name to variable value. |
| /// This represents one "frame" of evaluation context, a given EvalString may |
| /// need multiple environments in order to be fully expanded. |
| pub trait Env { |
| fn get_var(&self, var: &str) -> Option<EvalString<Cow<str>>>; |
| } |
| |
| /// One token within an EvalString, either literal text or a variable reference. |
| #[derive(Debug, Clone, PartialEq)] |
| pub enum EvalPart<T: AsRef<str>> { |
| Literal(T), |
| VarRef(T), |
| } |
| |
| /// A parsed but unexpanded variable-reference string, e.g. "cc $in -o $out". |
| /// This is generic to support EvalString<&str>, which is used for immediately- |
| /// expanded evals, like top-level bindings, and EvalString<String>, which is |
| /// used for delayed evals like in `rule` blocks. |
| #[derive(Debug, PartialEq)] |
| pub struct EvalString<T: AsRef<str>>(Vec<EvalPart<T>>); |
| impl<T: AsRef<str>> EvalString<T> { |
| pub fn new(parts: Vec<EvalPart<T>>) -> Self { |
| EvalString(parts) |
| } |
| |
| fn evaluate_inner(&self, result: &mut String, envs: &[&dyn Env]) { |
| for part in &self.0 { |
| match part { |
| EvalPart::Literal(s) => result.push_str(s.as_ref()), |
| EvalPart::VarRef(v) => { |
| for (i, env) in envs.iter().enumerate() { |
| if let Some(v) = env.get_var(v.as_ref()) { |
| v.evaluate_inner(result, &envs[i + 1..]); |
| break; |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| fn calc_evaluated_length(&self, envs: &[&dyn Env]) -> usize { |
| self.0 |
| .iter() |
| .map(|part| match part { |
| EvalPart::Literal(s) => s.as_ref().len(), |
| EvalPart::VarRef(v) => { |
| for (i, env) in envs.iter().enumerate() { |
| if let Some(v) = env.get_var(v.as_ref()) { |
| return v.calc_evaluated_length(&envs[i + 1..]); |
| } |
| } |
| 0 |
| } |
| }) |
| .sum() |
| } |
| |
| /// evalulate turns the EvalString into a regular String, looking up the |
| /// values of variable references in the provided Envs. It will look up |
| /// its variables in the earliest Env that has them, and then those lookups |
| /// will be recursively expanded starting from the env after the one that |
| /// had the first successful lookup. |
| pub fn evaluate(&self, envs: &[&dyn Env]) -> String { |
| let mut result = String::new(); |
| result.reserve(self.calc_evaluated_length(envs)); |
| self.evaluate_inner(&mut result, envs); |
| result |
| } |
| } |
| |
| impl EvalString<&str> { |
| pub fn into_owned(self) -> EvalString<String> { |
| EvalString( |
| self.0 |
| .into_iter() |
| .map(|part| match part { |
| EvalPart::Literal(s) => EvalPart::Literal(s.to_owned()), |
| EvalPart::VarRef(s) => EvalPart::VarRef(s.to_owned()), |
| }) |
| .collect(), |
| ) |
| } |
| } |
| |
| impl EvalString<String> { |
| pub fn as_cow(&self) -> EvalString<Cow<str>> { |
| EvalString( |
| self.0 |
| .iter() |
| .map(|part| match part { |
| EvalPart::Literal(s) => EvalPart::Literal(Cow::Borrowed(s.as_ref())), |
| EvalPart::VarRef(s) => EvalPart::VarRef(Cow::Borrowed(s.as_ref())), |
| }) |
| .collect(), |
| ) |
| } |
| } |
| |
| impl EvalString<&str> { |
| pub fn as_cow(&self) -> EvalString<Cow<str>> { |
| EvalString( |
| self.0 |
| .iter() |
| .map(|part| match part { |
| EvalPart::Literal(s) => EvalPart::Literal(Cow::Borrowed(*s)), |
| EvalPart::VarRef(s) => EvalPart::VarRef(Cow::Borrowed(*s)), |
| }) |
| .collect(), |
| ) |
| } |
| } |
| |
| /// A single scope's worth of variable definitions. |
| #[derive(Debug, Default)] |
| pub struct Vars<'text>(FxHashMap<&'text str, String>); |
| |
| impl<'text> Vars<'text> { |
| pub fn insert(&mut self, key: &'text str, val: String) { |
| self.0.insert(key, val); |
| } |
| pub fn get(&self, key: &str) -> Option<&String> { |
| self.0.get(key) |
| } |
| } |
| impl<'a> Env for Vars<'a> { |
| fn get_var(&self, var: &str) -> Option<EvalString<Cow<str>>> { |
| Some(EvalString::new(vec![EvalPart::Literal( |
| std::borrow::Cow::Borrowed(self.get(var)?), |
| )])) |
| } |
| } |
| |
| impl<K: Borrow<str> + PartialEq> Env for SmallMap<K, EvalString<String>> { |
| fn get_var(&self, var: &str) -> Option<EvalString<Cow<str>>> { |
| Some(self.get(var)?.as_cow()) |
| } |
| } |
| |
| impl<K: Borrow<str> + PartialEq> Env for SmallMap<K, EvalString<&str>> { |
| fn get_var(&self, var: &str) -> Option<EvalString<Cow<str>>> { |
| Some(self.get(var)?.as_cow()) |
| } |
| } |
| |
| impl Env for SmallMap<&str, String> { |
| fn get_var(&self, var: &str) -> Option<EvalString<Cow<str>>> { |
| Some(EvalString::new(vec![EvalPart::Literal( |
| std::borrow::Cow::Borrowed(self.get(var)?), |
| )])) |
| } |
| } |