Use BTreeMap and BTreeSet as Map and Set

This commit is contained in:
Casey Rodarmor 2016-10-30 14:37:03 -07:00
parent 93a3b3533b
commit f925520101
2 changed files with 62 additions and 117 deletions

View File

@ -16,9 +16,9 @@ extern crate tempdir;
use std::io::prelude::*;
use std::{fs, fmt, process, io};
use std::collections::{BTreeMap, HashSet};
use std::fmt::Display;
use regex::Regex;
use std::collections::{BTreeMap as Map, BTreeSet as Set};
use std::os::unix::fs::PermissionsExt;
@ -188,17 +188,17 @@ impl<'a> Recipe<'a> {
fn run(
&self,
arguments: &[&'a str],
scope: &BTreeMap<&'a str, String>,
scope: &Map<&'a str, String>,
dry_run: bool,
) -> Result<(), RunError<'a>> {
let argument_map = arguments .iter().enumerate()
.map(|(i, argument)| (self.arguments[i], *argument)).collect();
let mut evaluator = Evaluator {
evaluated: BTreeMap::new(),
evaluated: Map::new(),
scope: scope,
assignments: &BTreeMap::new(),
overrides: &BTreeMap::new(),
assignments: &Map::new(),
overrides: &Map::new(),
};
if self.shebang {
@ -339,14 +339,14 @@ impl<'a> Display for Recipe<'a> {
}
fn resolve_recipes<'a>(
recipes: &BTreeMap<&'a str, Recipe<'a>>,
assignments: &BTreeMap<&'a str, Expression<'a>>,
recipes: &Map<&'a str, Recipe<'a>>,
assignments: &Map<&'a str, Expression<'a>>,
text: &'a str,
) -> Result<(), Error<'a>> {
let mut resolver = Resolver {
seen: HashSet::new(),
seen: Set::new(),
stack: vec![],
resolved: HashSet::new(),
resolved: Set::new(),
recipes: recipes,
};
@ -393,9 +393,9 @@ fn resolve_recipes<'a>(
struct Resolver<'a: 'b, 'b> {
stack: Vec<&'a str>,
seen: HashSet<&'a str>,
resolved: HashSet<&'a str>,
recipes: &'b BTreeMap<&'a str, Recipe<'a>>,
seen: Set<&'a str>,
resolved: Set<&'a str>,
recipes: &'b Map<&'a str, Recipe<'a>>,
}
impl<'a, 'b> Resolver<'a, 'b> {
@ -433,16 +433,16 @@ impl<'a, 'b> Resolver<'a, 'b> {
}
fn resolve_assignments<'a>(
assignments: &BTreeMap<&'a str, Expression<'a>>,
assignment_tokens: &BTreeMap<&'a str, Token<'a>>,
assignments: &Map<&'a str, Expression<'a>>,
assignment_tokens: &Map<&'a str, Token<'a>>,
) -> Result<(), Error<'a>> {
let mut resolver = AssignmentResolver {
assignments: assignments,
assignment_tokens: assignment_tokens,
stack: vec![],
seen: HashSet::new(),
evaluated: HashSet::new(),
seen: Set::new(),
evaluated: Set::new(),
};
for name in assignments.keys() {
@ -453,11 +453,11 @@ fn resolve_assignments<'a>(
}
struct AssignmentResolver<'a: 'b, 'b> {
assignments: &'b BTreeMap<&'a str, Expression<'a>>,
assignment_tokens: &'b BTreeMap<&'a str, Token<'a>>,
assignments: &'b Map<&'a str, Expression<'a>>,
assignment_tokens: &'b Map<&'a str, Token<'a>>,
stack: Vec<&'a str>,
seen: HashSet<&'a str>,
evaluated: HashSet<&'a str>,
seen: Set<&'a str>,
evaluated: Set<&'a str>,
}
impl<'a: 'b, 'b> AssignmentResolver<'a, 'b> {
@ -507,12 +507,12 @@ impl<'a: 'b, 'b> AssignmentResolver<'a, 'b> {
}
fn evaluate_assignments<'a>(
assignments: &BTreeMap<&'a str, Expression<'a>>,
overrides: &BTreeMap<&str, &str>,
) -> Result<BTreeMap<&'a str, String>, RunError<'a>> {
assignments: &Map<&'a str, Expression<'a>>,
overrides: &Map<&str, &str>,
) -> Result<Map<&'a str, String>, RunError<'a>> {
let mut evaluator = Evaluator {
evaluated: BTreeMap::new(),
scope: &BTreeMap::new(),
evaluated: Map::new(),
scope: &Map::new(),
assignments: assignments,
overrides: overrides,
};
@ -525,17 +525,17 @@ fn evaluate_assignments<'a>(
}
struct Evaluator<'a: 'b, 'b> {
evaluated: BTreeMap<&'a str, String>,
scope: &'b BTreeMap<&'a str, String>,
assignments: &'b BTreeMap<&'a str, Expression<'a>>,
overrides: &'b BTreeMap<&'b str, &'b str>,
evaluated: Map<&'a str, String>,
scope: &'b Map<&'a str, String>,
assignments: &'b Map<&'a str, Expression<'a>>,
overrides: &'b Map<&'b str, &'b str>,
}
impl<'a, 'b> Evaluator<'a, 'b> {
fn evaluate_line(
&mut self,
line: &[Fragment<'a>],
arguments: &BTreeMap<&str, &str>
arguments: &Map<&str, &str>
) -> Result<String, RunError<'a>> {
let mut evaluated = String::new();
for fragment in line {
@ -558,7 +558,7 @@ impl<'a, 'b> Evaluator<'a, 'b> {
if let Some(value) = self.overrides.get(name) {
self.evaluated.insert(name, value.to_string());
} else {
let value = try!(self.evaluate_expression(expression, &BTreeMap::new()));
let value = try!(self.evaluate_expression(expression, &Map::new()));
self.evaluated.insert(name, value);
}
} else {
@ -573,7 +573,7 @@ impl<'a, 'b> Evaluator<'a, 'b> {
fn evaluate_expression(
&mut self,
expression: &Expression<'a>,
arguments: &BTreeMap<&str, &str>
arguments: &Map<&str, &str>
) -> Result<String, RunError<'a>> {
Ok(match *expression {
Expression::Variable{name, ..} => {
@ -616,7 +616,6 @@ struct Error<'a> {
#[derive(Debug, PartialEq)]
enum ErrorKind<'a> {
ArgumentShadowsVariable{argument: &'a str},
BadName{name: &'a str},
CircularRecipeDependency{recipe: &'a str, circle: Vec<&'a str>},
CircularVariableDependency{variable: &'a str, circle: Vec<&'a str>},
DependencyHasArguments{recipe: &'a str, dependency: &'a str},
@ -698,9 +697,6 @@ impl<'a> Display for Error<'a> {
try!(write!(f, "error: "));
match self.kind {
ErrorKind::BadName{name} => {
try!(writeln!(f, "name `{}` did not match /[a-z](-?[a-z0-9])*/", name));
}
ErrorKind::CircularRecipeDependency{recipe, ref circle} => {
if circle.len() == 2 {
try!(write!(f, "recipe `{}` depends on itself", recipe));
@ -792,8 +788,8 @@ impl<'a> Display for Error<'a> {
}
struct Justfile<'a> {
recipes: BTreeMap<&'a str, Recipe<'a>>,
assignments: BTreeMap<&'a str, Expression<'a>>,
recipes: Map<&'a str, Recipe<'a>>,
assignments: Map<&'a str, Expression<'a>>,
}
impl<'a, 'b> Justfile<'a> where 'a: 'b {
@ -821,7 +817,7 @@ impl<'a, 'b> Justfile<'a> where 'a: 'b {
fn run(
&'a self,
overrides: &BTreeMap<&'a str, &'a str>,
overrides: &Map<&'a str, &'a str>,
arguments: &[&'a str],
dry_run: bool,
evaluate: bool,
@ -842,7 +838,7 @@ impl<'a, 'b> Justfile<'a> where 'a: 'b {
return Ok(());
}
let mut ran = HashSet::new();
let mut ran = Set::new();
for (i, argument) in arguments.iter().enumerate() {
if let Some(recipe) = self.recipes.get(argument) {
@ -885,8 +881,8 @@ impl<'a, 'b> Justfile<'a> where 'a: 'b {
&'c self,
recipe: &Recipe<'a>,
arguments: &[&'a str],
scope: &BTreeMap<&'c str, String>,
ran: &mut HashSet<&'a str>,
scope: &Map<&'c str, String>,
ran: &mut Set<&'a str>,
dry_run: bool,
) -> Result<(), RunError> {
for dependency_name in &recipe.dependencies {
@ -1103,17 +1099,17 @@ fn token(pattern: &str) -> Regex {
fn tokenize(text: &str) -> Result<Vec<Token>, Error> {
lazy_static! {
static ref BACKTICK: Regex = token(r"`[^`\n\r]*`" );
static ref COLON: Regex = token(r":" );
static ref COMMENT: Regex = token(r"#([^!].*)?$" );
static ref EOF: Regex = token(r"(?-m)$" );
static ref EOL: Regex = token(r"\n|\r\n" );
static ref EQUALS: Regex = token(r"=" );
static ref INTERPOLATION_END: Regex = token(r"[}][}]" );
static ref INTERPOLATION_START_TOKEN: Regex = token(r"[{][{]" );
static ref NAME: Regex = token(r"([a-zA-Z0-9_-]+)" );
static ref PLUS: Regex = token(r"[+]" );
static ref STRING: Regex = token("\"" );
static ref BACKTICK: Regex = token(r"`[^`\n\r]*`" );
static ref COLON: Regex = token(r":" );
static ref COMMENT: Regex = token(r"#([^!].*)?$" );
static ref EOF: Regex = token(r"(?-m)$" );
static ref EOL: Regex = token(r"\n|\r\n" );
static ref EQUALS: Regex = token(r"=" );
static ref INTERPOLATION_END: Regex = token(r"[}][}]" );
static ref INTERPOLATION_START_TOKEN: Regex = token(r"[{][{]" );
static ref NAME: Regex = token(r"([a-zA-Z_-][a-zA-Z0-9_-]*)");
static ref PLUS: Regex = token(r"[+]" );
static ref STRING: Regex = token("\"" );
static ref INDENT: Regex = re(r"^([ \t]*)[^ \t\n\r]" );
static ref INTERPOLATION_START: Regex = re(r"^[{][{]" );
static ref LEADING_TEXT: Regex = re(r"^(?m)(.+?)[{][{]" );
@ -1347,15 +1343,6 @@ fn tokenize(text: &str) -> Result<Vec<Token>, Error> {
fn parse(text: &str) -> Result<Justfile, Error> {
let tokens = try!(tokenize(text));
let filtered: Vec<_> = tokens.into_iter().filter(|token| token.kind != Comment).collect();
if let Some(token) = filtered.iter().find(|token| {
lazy_static! {
static ref GOOD_NAME: Regex = re("^[a-z](-?[a-z0-9])*$");
}
token.kind == Name && !GOOD_NAME.is_match(token.lexeme)
}) {
return Err(token.error(ErrorKind::BadName{name: token.lexeme}));
}
let parser = Parser{
text: text,
tokens: filtered.into_iter().peekable()
@ -1563,9 +1550,10 @@ impl<'a> Parser<'a> {
}
fn file(mut self) -> Result<Justfile<'a>, Error<'a>> {
let mut recipes = BTreeMap::<&str, Recipe>::new();
let mut assignments = BTreeMap::<&str, Expression>::new();
let mut assignment_tokens = BTreeMap::<&str, Token<'a>>::new();
let mut recipes = Map::<&str, Recipe>::new();
let mut assignments = Map::<&str, Expression>::new();
let mut assignment_tokens = Map::<&str, Token<'a>>::new();
let mut exports = Set::<&str>::new();
loop {
match self.tokens.next() {

View File

@ -2,7 +2,7 @@ extern crate tempdir;
use super::{Token, Error, ErrorKind, Justfile, RunError};
use super::TokenKind::*;
use std::collections::BTreeMap;
use std::collections::BTreeMap as Map;
fn tokenize_success(text: &str, expected_summary: &str) {
let tokens = super::tokenize(text).unwrap();
@ -579,7 +579,7 @@ fn conjoin_and() {
#[test]
fn unknown_recipes() {
match parse_success("a:\nb:\nc:").run(&BTreeMap::new(), &["a", "x", "y", "z"], false, false).unwrap_err() {
match parse_success("a:\nb:\nc:").run(&Map::new(), &["a", "x", "y", "z"], false, false).unwrap_err() {
RunError::UnknownRecipes{recipes} => assert_eq!(recipes, &["x", "y", "z"]),
other => panic!("expected an unknown recipe error, but got: {}", other),
}
@ -603,49 +603,6 @@ fn extra_whitespace() {
parse_success("a:\n #!\n print(1)");
}
#[test]
fn bad_recipe_names() {
// We are extra strict with names. Although the tokenizer
// will tokenize anything that matches /[a-zA-Z0-9_-]+/
// as a name, we throw an error if names do not match
// / [a-z](-?[a-z])* /. This is to support future expansion
// of justfile and command line syntax.
fn bad_name(text: &str, name: &str, index: usize, line: usize, column: usize) {
parse_error(text, Error {
text: text,
index: index,
line: line,
column: column,
width: Some(name.len()),
kind: ErrorKind::BadName{name: name}
});
}
bad_name("-a", "-a", 0, 0, 0);
bad_name("_a", "_a", 0, 0, 0);
bad_name("a-", "a-", 0, 0, 0);
bad_name("a_", "a_", 0, 0, 0);
bad_name("a__a", "a__a", 0, 0, 0);
bad_name("a--a", "a--a", 0, 0, 0);
bad_name("a: a--", "a--", 3, 0, 3);
bad_name("a: 9a", "9a", 3, 0, 3);
bad_name("a: 9a", "9a", 3, 0, 3);
bad_name("a:\nZ:", "Z", 3, 1, 0);
}
#[test]
fn bad_interpolation_variable_name() {
let text = "a:\n echo {{hello--hello}}";
parse_error(text, Error {
text: text,
index: 11,
line: 1,
column: 8,
width: Some(12),
kind: ErrorKind::BadName{name: "hello--hello"}
});
}
#[test]
fn interpolation_outside_of_recipe() {
let text = "{{";
@ -747,7 +704,7 @@ a:
x
";
match parse_success(text).run(&BTreeMap::new(), &["a"], false, false).unwrap_err() {
match parse_success(text).run(&Map::new(), &["a"], false, false).unwrap_err() {
RunError::Code{recipe, code} => {
assert_eq!(recipe, "a");
assert_eq!(code, 200);
@ -758,7 +715,7 @@ a:
#[test]
fn code_error() {
match parse_success("fail:\n @function x { return 100; }; x").run(&BTreeMap::new(), &["fail"], false, false).unwrap_err() {
match parse_success("fail:\n @function x { return 100; }; x").run(&Map::new(), &["fail"], false, false).unwrap_err() {
RunError::Code{recipe, code} => {
assert_eq!(recipe, "fail");
assert_eq!(code, 100);
@ -773,7 +730,7 @@ fn run_args() {
a return code:
@function x { {{return}} {{code + "0"}}; }; x"#;
match parse_success(text).run(&BTreeMap::new(), &["a", "return", "15"], false, false).unwrap_err() {
match parse_success(text).run(&Map::new(), &["a", "return", "15"], false, false).unwrap_err() {
RunError::Code{recipe, code} => {
assert_eq!(recipe, "a");
assert_eq!(code, 150);
@ -784,7 +741,7 @@ a return code:
#[test]
fn missing_args() {
match parse_success("a b c d:").run(&BTreeMap::new(), &["a", "b", "c"], false, false).unwrap_err() {
match parse_success("a b c d:").run(&Map::new(), &["a", "b", "c"], false, false).unwrap_err() {
RunError::ArgumentCountMismatch{recipe, found, expected} => {
assert_eq!(recipe, "a");
assert_eq!(found, 2);
@ -796,7 +753,7 @@ fn missing_args() {
#[test]
fn missing_default() {
match parse_success("a b c d:\n echo {{b}}{{c}}{{d}}").run(&BTreeMap::new(), &["a"], false, false).unwrap_err() {
match parse_success("a b c d:\n echo {{b}}{{c}}{{d}}").run(&Map::new(), &["a"], false, false).unwrap_err() {
RunError::ArgumentCountMismatch{recipe, found, expected} => {
assert_eq!(recipe, "a");
assert_eq!(found, 0);
@ -808,7 +765,7 @@ fn missing_default() {
#[test]
fn backtick_code() {
match parse_success("a:\n echo {{`function f { return 100; }; f`}}").run(&BTreeMap::new(), &["a"], false, false).unwrap_err() {
match parse_success("a:\n echo {{`function f { return 100; }; f`}}").run(&Map::new(), &["a"], false, false).unwrap_err() {
RunError::BacktickCode{code, token} => {
assert_eq!(code, 100);
assert_eq!(token.lexeme, "`function f { return 100; }; f`");
@ -819,7 +776,7 @@ fn backtick_code() {
#[test]
fn unknown_overrides() {
let mut overrides = BTreeMap::new();
let mut overrides = Map::new();
overrides.insert("foo", "bar");
overrides.insert("baz", "bob");
match parse_success("a:\n echo {{`function f { return 100; }; f`}}")