blob: 2b49bbd5ca5c9d3e3ed1b780b248eff1f4672c31 [file] [log] [blame]
//! Represents parsed Ninja strings with embedded variable references, e.g.
//! `c++ $in -o $out`, and mechanisms for expanding those into plain strings.
use crate::load::Scope;
use crate::load::ScopePosition;
use crate::parse::EvalParser;
use crate::smallmap::SmallMap;
use std::borrow::Borrow;
/// 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 evaluate_var(
&self,
result: &mut String,
var: &str,
envs: &[&dyn Env],
scope: &Scope,
position: ScopePosition,
);
}
/// 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>>(T);
impl<T: AsRef<str>> EvalString<T> {
pub fn new(inner: T) -> Self {
EvalString(inner)
}
pub fn evaluate_inner(
&self,
result: &mut String,
envs: &[&dyn Env],
scope: &Scope,
position: ScopePosition,
) {
for part in self.parse() {
match part {
EvalPart::Literal(s) => result.push_str(s.as_ref()),
EvalPart::VarRef(v) => {
if let Some(env) = envs.first() {
env.evaluate_var(result, v.as_ref(), &envs[1..], scope, position);
} else {
scope.evaluate(result, v.as_ref(), position);
}
}
}
}
}
/// 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], scope: &Scope, position: ScopePosition) -> String {
let mut result = String::new();
self.evaluate_inner(&mut result, envs, scope, position);
result
}
pub fn maybe_literal(&self) -> Option<&str> {
let result = self.0.as_ref();
if result.contains('$') {
None
} else {
Some(result)
}
}
pub fn parse(&self) -> impl Iterator<Item = EvalPart<&str>> {
EvalParser::new(self.0.as_ref().as_bytes())
}
}
impl EvalString<&str> {
pub fn into_owned(self) -> EvalString<String> {
EvalString(self.0.to_owned())
}
}
impl<K: Borrow<str> + PartialEq, V: AsRef<str>> Env for SmallMap<K, EvalString<V>> {
fn evaluate_var(
&self,
result: &mut String,
var: &str,
envs: &[&dyn Env],
scope: &Scope,
position: ScopePosition,
) {
if let Some(v) = self.get(var) {
v.evaluate_inner(result, envs, scope, position);
} else if let Some(env) = envs.first() {
env.evaluate_var(result, var, &envs[1..], scope, position);
} else {
scope.evaluate(result, var, position);
}
}
}