summaryrefslogtreecommitdiff
path: root/src/eval.rs
blob: 4b711c5ca6f08a6244b093370c59f4300579785e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
//! Represents parsed Ninja strings with embedded variable references, e.g.
//! `c++ $in -o $out`, and mechanisms for expanding those into plain strings.

use crate::smallmap::SmallMap;
use std::borrow::Borrow;
use std::borrow::Cow;
use std::collections::HashMap;

/// 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>(HashMap<&'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)?),
        )]))
    }
}