Skip to content

Commit eec13b0

Browse files
committed
Evaluate build paths in the context of the build's bindings
This fixes an incompatibility with ninja. I've also refactored Env to make it clearer and remove the TODO(evmar#83), but I actually think we could do signifigant further cleanup. Only rules should require EvalStrings, global variables and build bindings can be evaluated as soon as they're read, although maybe that would change with subninjas. Also, rules currently parse their variables as EvalString<String>, but I think that could be changed to EvalString<&'text str> if we hold onto the byte buffers of all the included files until the parsing is done. Fixes evmar#91 and evmar#39.
1 parent 54eeb86 commit eec13b0

File tree

5 files changed

+207
-60
lines changed

5 files changed

+207
-60
lines changed

src/eval.rs

+44-28
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,14 @@
22
//! `c++ $in -o $out`, and mechanisms for expanding those into plain strings.
33
44
use crate::smallmap::SmallMap;
5+
use std::borrow::Borrow;
56
use std::{borrow::Cow, collections::HashMap};
67

78
/// An environment providing a mapping of variable name to variable value.
8-
/// A given EvalString may be expanded with multiple environments as possible
9-
/// context.
9+
/// This represents one "frame" of evaluation context, a given EvalString may
10+
/// need multiple environments in order to be fully expanded.
1011
pub trait Env {
11-
fn get_var(&self, var: &str) -> Option<Cow<str>>;
12+
fn get_var(&self, var: &str) -> Option<EvalString<Cow<str>>>;
1213
}
1314

1415
/// One token within an EvalString, either literal text or a variable reference.
@@ -29,22 +30,31 @@ impl<T: AsRef<str>> EvalString<T> {
2930
EvalString(parts)
3031
}
3132

32-
pub fn evaluate(&self, envs: &[&dyn Env]) -> String {
33-
let mut val = String::new();
33+
fn evaluate_inner(&self, result: &mut String, envs: &[&dyn Env]) {
3434
for part in &self.0 {
3535
match part {
36-
EvalPart::Literal(s) => val.push_str(s.as_ref()),
36+
EvalPart::Literal(s) => result.push_str(s.as_ref()),
3737
EvalPart::VarRef(v) => {
3838
for env in envs {
3939
if let Some(v) = env.get_var(v.as_ref()) {
40-
val.push_str(&v);
40+
v.evaluate_inner(result, &envs[1..]);
4141
break;
4242
}
4343
}
4444
}
4545
}
4646
}
47-
val
47+
}
48+
49+
/// evalulate turns the EvalString into a regular String, looking up the
50+
/// values of variable references in the provided Envs. It will look up
51+
/// its variables in the earliest Env that has them, and then those lookups
52+
/// will be recursively expanded starting from the env after the one that
53+
/// had the first successful lookup.
54+
pub fn evaluate(&self, envs: &[&dyn Env]) -> String {
55+
let mut result = String::new();
56+
self.evaluate_inner(&mut result, envs);
57+
result
4858
}
4959
}
5060

@@ -62,6 +72,20 @@ impl EvalString<&str> {
6272
}
6373
}
6474

75+
impl EvalString<String> {
76+
pub fn as_cow(&self) -> EvalString<Cow<str>> {
77+
EvalString(
78+
self.0
79+
.iter()
80+
.map(|part| match part {
81+
EvalPart::Literal(s) => EvalPart::Literal(Cow::Borrowed(s.as_ref())),
82+
EvalPart::VarRef(s) => EvalPart::VarRef(Cow::Borrowed(s.as_ref())),
83+
})
84+
.collect(),
85+
)
86+
}
87+
}
88+
6589
/// A single scope's worth of variable definitions.
6690
#[derive(Debug, Default)]
6791
pub struct Vars<'text>(HashMap<&'text str, String>);
@@ -70,36 +94,28 @@ impl<'text> Vars<'text> {
7094
pub fn insert(&mut self, key: &'text str, val: String) {
7195
self.0.insert(key, val);
7296
}
73-
pub fn get(&self, key: &'text str) -> Option<&String> {
97+
pub fn get(&self, key: &str) -> Option<&String> {
7498
self.0.get(key)
7599
}
76100
}
77101
impl<'a> Env for Vars<'a> {
78-
fn get_var(&self, var: &str) -> Option<Cow<str>> {
79-
self.0.get(var).map(|str| Cow::Borrowed(str.as_str()))
102+
fn get_var(&self, var: &str) -> Option<EvalString<Cow<str>>> {
103+
Some(EvalString::new(vec![EvalPart::Literal(
104+
std::borrow::Cow::Borrowed(self.get(var)?),
105+
)]))
80106
}
81107
}
82108

83-
// Impl for Loader.rules
84-
impl Env for SmallMap<String, EvalString<String>> {
85-
fn get_var(&self, var: &str) -> Option<Cow<str>> {
86-
// TODO(#83): this is wrong in that it doesn't include envs.
87-
// This can occur when you have e.g.
88-
// rule foo
89-
// bar = $baz
90-
// build ...: foo
91-
// x = $bar
92-
// When evaluating the value of `x`, we find `bar` in the rule but
93-
// then need to pick the right env to evaluate $baz. But we also don't
94-
// wanna generically always use all available envs because we don't
95-
// wanna get into evaluation cycles.
96-
self.get(var).map(|val| Cow::Owned(val.evaluate(&[])))
109+
impl<K: Borrow<str> + PartialEq> Env for SmallMap<K, EvalString<String>> {
110+
fn get_var(&self, var: &str) -> Option<EvalString<Cow<str>>> {
111+
Some(self.get(var)?.as_cow())
97112
}
98113
}
99114

100-
// Impl for the variables attached to a build.
101115
impl Env for SmallMap<&str, String> {
102-
fn get_var(&self, var: &str) -> Option<Cow<str>> {
103-
self.get(var).map(|val| Cow::Borrowed(val.as_str()))
116+
fn get_var(&self, var: &str) -> Option<EvalString<Cow<str>>> {
117+
Some(EvalString::new(vec![EvalPart::Literal(
118+
std::borrow::Cow::Borrowed(self.get(var)?),
119+
)]))
104120
}
105121
}

src/load.rs

+10-14
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
33
use crate::{
44
canon::{canon_path, canon_path_fast},
5+
eval::{EvalPart, EvalString},
56
graph::{FileId, RspFile},
67
parse::Statement,
78
smallmap::SmallMap,
@@ -30,12 +31,14 @@ impl<'a> BuildImplicitVars<'a> {
3031
}
3132
}
3233
impl<'a> eval::Env for BuildImplicitVars<'a> {
33-
fn get_var(&self, var: &str) -> Option<Cow<str>> {
34+
fn get_var(&self, var: &str) -> Option<EvalString<Cow<str>>> {
35+
let string_to_evalstring =
36+
|s: String| Some(EvalString::new(vec![EvalPart::Literal(Cow::Owned(s))]));
3437
match var {
35-
"in" => Some(Cow::Owned(self.file_list(self.build.explicit_ins(), ' '))),
36-
"in_newline" => Some(Cow::Owned(self.file_list(self.build.explicit_ins(), '\n'))),
37-
"out" => Some(Cow::Owned(self.file_list(self.build.explicit_outs(), ' '))),
38-
"out_newline" => Some(Cow::Owned(self.file_list(self.build.explicit_outs(), '\n'))),
38+
"in" => string_to_evalstring(self.file_list(self.build.explicit_ins(), ' ')),
39+
"in_newline" => string_to_evalstring(self.file_list(self.build.explicit_ins(), '\n')),
40+
"out" => string_to_evalstring(self.file_list(self.build.explicit_outs(), ' ')),
41+
"out_newline" => string_to_evalstring(self.file_list(self.build.explicit_outs(), '\n')),
3942
_ => None,
4043
}
4144
}
@@ -115,15 +118,8 @@ impl Loader {
115118
build_vars.insert(name, val);
116119
}
117120

118-
let envs: [&dyn eval::Env; 4] = [&implicit_vars, &build_vars, rule, env];
119-
let lookup = |key: &str| -> Option<String> {
120-
// Look up `key = ...` binding in build and rule block.
121-
let val = match build_vars.get(key) {
122-
Some(val) => val.clone(),
123-
None => rule.get(key)?.evaluate(&envs),
124-
};
125-
Some(val)
126-
};
121+
let envs: &[&dyn eval::Env] = &[&implicit_vars, rule, &b.vars, env];
122+
let lookup = |key: &str| -> Option<String> { Some(rule.get(key)?.evaluate(envs)) };
127123

128124
let cmdline = lookup("command");
129125
let desc = lookup("description");

src/parse.rs

+58-18
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,10 @@ impl<'text> Parser<'text> {
131131
self.skip_spaces();
132132
self.scanner.expect('=')?;
133133
self.skip_spaces();
134-
self.read_eval()
134+
let result = self.read_eval(&[]);
135+
self.scanner.skip('\r');
136+
self.scanner.expect('\n')?;
137+
result
135138
}
136139

137140
/// Read a collection of ` foo = bar` variables, with leading indent.
@@ -195,57 +198,85 @@ impl<'text> Parser<'text> {
195198
Ok(())
196199
}
197200

201+
fn read_unevaluated_paths_to(
202+
&mut self,
203+
v: &mut Vec<EvalString<&'text str>>,
204+
) -> ParseResult<()> {
205+
self.skip_spaces();
206+
while self.scanner.peek() != ':'
207+
&& self.scanner.peek() != '|'
208+
&& !self.scanner.peek_newline()
209+
{
210+
v.push(self.read_eval(&[':', '|', ' '])?);
211+
self.skip_spaces();
212+
}
213+
Ok(())
214+
}
215+
198216
fn read_build<L: Loader>(&mut self, loader: &mut L) -> ParseResult<Build<'text, L::Path>> {
199217
let line = self.scanner.line;
200-
let mut outs = Vec::new();
201-
self.read_paths_to(loader, &mut outs)?;
202-
let explicit_outs = outs.len();
218+
let mut unevaluated_outs = Vec::new();
219+
self.read_unevaluated_paths_to(&mut unevaluated_outs)?;
220+
let explicit_outs = unevaluated_outs.len();
203221

204222
if self.scanner.peek() == '|' {
205223
self.scanner.next();
206-
self.read_paths_to(loader, &mut outs)?;
224+
self.read_unevaluated_paths_to(&mut unevaluated_outs)?;
207225
}
208226

209227
self.scanner.expect(':')?;
210228
self.skip_spaces();
211229
let rule = self.read_ident()?;
212230

213-
let mut ins = Vec::new();
214-
self.read_paths_to(loader, &mut ins)?;
215-
let explicit_ins = ins.len();
231+
let mut unevaluated_ins = Vec::new();
232+
self.read_unevaluated_paths_to(&mut unevaluated_ins)?;
233+
let explicit_ins = unevaluated_ins.len();
216234

217235
if self.scanner.peek() == '|' {
218236
self.scanner.next();
219237
let peek = self.scanner.peek();
220238
if peek == '|' || peek == '@' {
221239
self.scanner.back();
222240
} else {
223-
self.read_paths_to(loader, &mut ins)?;
241+
self.read_unevaluated_paths_to(&mut unevaluated_ins)?;
224242
}
225243
}
226-
let implicit_ins = ins.len() - explicit_ins;
244+
let implicit_ins = unevaluated_ins.len() - explicit_ins;
227245

228246
if self.scanner.peek() == '|' {
229247
self.scanner.next();
230248
if self.scanner.peek() == '@' {
231249
self.scanner.back();
232250
} else {
233251
self.scanner.expect('|')?;
234-
self.read_paths_to(loader, &mut ins)?;
252+
self.read_unevaluated_paths_to(&mut unevaluated_ins)?;
235253
}
236254
}
237-
let order_only_ins = ins.len() - implicit_ins - explicit_ins;
255+
let order_only_ins = unevaluated_ins.len() - implicit_ins - explicit_ins;
238256

239257
if self.scanner.peek() == '|' {
240258
self.scanner.next();
241259
self.scanner.expect('@')?;
242-
self.read_paths_to(loader, &mut ins)?;
260+
self.read_unevaluated_paths_to(&mut unevaluated_ins)?;
243261
}
244-
let validation_ins = ins.len() - order_only_ins - implicit_ins - explicit_ins;
262+
let validation_ins = unevaluated_ins.len() - order_only_ins - implicit_ins - explicit_ins;
245263

246264
self.scanner.skip('\r');
247265
self.scanner.expect('\n')?;
248266
let vars = self.read_scoped_vars()?;
267+
268+
let env: &[&dyn crate::eval::Env] = &[&vars, &self.vars];
269+
270+
let mut outs = Vec::new();
271+
for unevaluated_out in unevaluated_outs {
272+
outs.push(loader.path(&mut unevaluated_out.evaluate(env)));
273+
}
274+
275+
let mut ins = Vec::new();
276+
for unevaluated_in in unevaluated_ins {
277+
ins.push(loader.path(&mut unevaluated_in.evaluate(env)));
278+
}
279+
249280
Ok(Build {
250281
rule,
251282
line,
@@ -299,17 +330,26 @@ impl<'text> Parser<'text> {
299330
Ok(self.scanner.slice(start, end))
300331
}
301332

302-
fn read_eval(&mut self) -> ParseResult<EvalString<&'text str>> {
333+
/// Reads an EvalString. Stops at either a newline, or any of the characters
334+
/// in stop_at, without consuming the character that caused it to stop.
335+
fn read_eval(&mut self, stop_at: &[char]) -> ParseResult<EvalString<&'text str>> {
303336
// Guaranteed at least one part.
304337
let mut parts = Vec::with_capacity(1);
305338
let mut ofs = self.scanner.ofs;
306339
let end = loop {
307340
match self.scanner.read() {
308341
'\0' => return self.scanner.parse_error("unexpected EOF"),
309-
'\n' => break self.scanner.ofs - 1,
342+
x if stop_at.contains(&x) => {
343+
self.scanner.back();
344+
break self.scanner.ofs;
345+
}
346+
'\n' => {
347+
self.scanner.back();
348+
break self.scanner.ofs;
349+
}
310350
'\r' if self.scanner.peek() == '\n' => {
311-
self.scanner.next();
312-
break self.scanner.ofs - 2;
351+
self.scanner.back();
352+
break self.scanner.ofs;
313353
}
314354
'$' => {
315355
let end = self.scanner.ofs - 1;

src/scanner.rs

+10
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,16 @@ impl<'a> Scanner<'a> {
3131
pub fn peek(&self) -> char {
3232
unsafe { *self.buf.get_unchecked(self.ofs) as char }
3333
}
34+
pub fn peek_newline(&self) -> bool {
35+
if self.peek() == '\n' {
36+
return true;
37+
}
38+
if self.ofs >= self.buf.len() - 1 {
39+
return false;
40+
}
41+
let peek2 = unsafe { *self.buf.get_unchecked(self.ofs + 1) as char };
42+
return self.peek() == '\r' && peek2 == '\n';
43+
}
3444
pub fn next(&mut self) {
3545
if self.peek() == '\n' {
3646
self.line += 1;

0 commit comments

Comments
 (0)