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

View File

@ -2,7 +2,7 @@ extern crate tempdir;
use super::{Token, Error, ErrorKind, Justfile, RunError}; use super::{Token, Error, ErrorKind, Justfile, RunError};
use super::TokenKind::*; use super::TokenKind::*;
use std::collections::BTreeMap; use std::collections::BTreeMap as Map;
fn tokenize_success(text: &str, expected_summary: &str) { fn tokenize_success(text: &str, expected_summary: &str) {
let tokens = super::tokenize(text).unwrap(); let tokens = super::tokenize(text).unwrap();
@ -579,7 +579,7 @@ fn conjoin_and() {
#[test] #[test]
fn unknown_recipes() { 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"]), RunError::UnknownRecipes{recipes} => assert_eq!(recipes, &["x", "y", "z"]),
other => panic!("expected an unknown recipe error, but got: {}", other), other => panic!("expected an unknown recipe error, but got: {}", other),
} }
@ -603,49 +603,6 @@ fn extra_whitespace() {
parse_success("a:\n #!\n print(1)"); 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] #[test]
fn interpolation_outside_of_recipe() { fn interpolation_outside_of_recipe() {
let text = "{{"; let text = "{{";
@ -747,7 +704,7 @@ a:
x 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} => { RunError::Code{recipe, code} => {
assert_eq!(recipe, "a"); assert_eq!(recipe, "a");
assert_eq!(code, 200); assert_eq!(code, 200);
@ -758,7 +715,7 @@ a:
#[test] #[test]
fn code_error() { 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} => { RunError::Code{recipe, code} => {
assert_eq!(recipe, "fail"); assert_eq!(recipe, "fail");
assert_eq!(code, 100); assert_eq!(code, 100);
@ -773,7 +730,7 @@ fn run_args() {
a return code: a return code:
@function x { {{return}} {{code + "0"}}; }; x"#; @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} => { RunError::Code{recipe, code} => {
assert_eq!(recipe, "a"); assert_eq!(recipe, "a");
assert_eq!(code, 150); assert_eq!(code, 150);
@ -784,7 +741,7 @@ a return code:
#[test] #[test]
fn missing_args() { 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} => { RunError::ArgumentCountMismatch{recipe, found, expected} => {
assert_eq!(recipe, "a"); assert_eq!(recipe, "a");
assert_eq!(found, 2); assert_eq!(found, 2);
@ -796,7 +753,7 @@ fn missing_args() {
#[test] #[test]
fn missing_default() { 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} => { RunError::ArgumentCountMismatch{recipe, found, expected} => {
assert_eq!(recipe, "a"); assert_eq!(recipe, "a");
assert_eq!(found, 0); assert_eq!(found, 0);
@ -808,7 +765,7 @@ fn missing_default() {
#[test] #[test]
fn backtick_code() { 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} => { RunError::BacktickCode{code, token} => {
assert_eq!(code, 100); assert_eq!(code, 100);
assert_eq!(token.lexeme, "`function f { return 100; }; f`"); assert_eq!(token.lexeme, "`function f { return 100; }; f`");
@ -819,7 +776,7 @@ fn backtick_code() {
#[test] #[test]
fn unknown_overrides() { fn unknown_overrides() {
let mut overrides = BTreeMap::new(); let mut overrides = Map::new();
overrides.insert("foo", "bar"); overrides.insert("foo", "bar");
overrides.insert("baz", "bob"); overrides.insert("baz", "bob");
match parse_success("a:\n echo {{`function f { return 100; }; f`}}") match parse_success("a:\n echo {{`function f { return 100; }; f`}}")