Lots of work.

This commit is contained in:
Casey Rodarmor 2016-10-03 23:55:55 -07:00
parent 1a7a61acbc
commit c0f58eefe8
4 changed files with 140 additions and 81 deletions

View File

@ -1,5 +1,6 @@
test: test:
cargo test cargo test
cargo run -- quine
# list all recipies # list all recipies
list: list:
@ -8,16 +9,8 @@ list:
args: args:
@echo "I got some arguments: ARG0=${ARG0} ARG1=${ARG1} ARG2=${ARG2}" @echo "I got some arguments: ARG0=${ARG0} ARG1=${ARG1} ARG2=${ARG2}"
# make a quine and compile it # make a quine, compile it, and verify it
quine: create compile quine: create
# create our quine
create:
mkdir -p tmp
echo 'int printf(const char*, ...); int main() { char *s = "int printf(const char*, ...); int main() { char *s = %c%s%c; printf(s, 34, s, 34); return 0; }"; printf(s, 34, s, 34); return 0; }' > tmp/gen0.c
# make sure it's really a quine
compile:
cc tmp/gen0.c -o tmp/gen0 cc tmp/gen0.c -o tmp/gen0
./tmp/gen0 > tmp/gen1.c ./tmp/gen0 > tmp/gen1.c
cc tmp/gen1.c -o tmp/gen1 cc tmp/gen1.c -o tmp/gen1
@ -25,6 +18,12 @@ compile:
diff tmp/gen1.c tmp/gen2.c diff tmp/gen1.c tmp/gen2.c
@echo 'It was a quine!' @echo 'It was a quine!'
# create our quine
create:
mkdir -p tmp
echo 'int printf(const char*, ...); int main() { char *s = "int printf(const char*, ...); int main() { char *s = %c%s%c; printf(s, 34, s, 34); return 0; }"; printf(s, 34, s, 34); return 0; }' > tmp/gen0.c
# clean up # clean up
clean: clean:
rm -r tmp rm -r tmp

11
notes
View File

@ -1,6 +1,13 @@
notes notes
----- -----
-- report double compile error
-- actually run recipes
-- actually parse recipe and validate contents
-- think about maybe using multiple cores
-- should pre-requisite order really be arbitrary?
-- test plan order
- look through all justfiles for features of make that I use. so far: - look through all justfiles for features of make that I use. so far:
. phony . phony
. SHELL := zsh . SHELL := zsh
@ -8,9 +15,11 @@ notes
. make variables . make variables
- ask travis for his justfiles - ask travis for his justfiles
- comment code
command line arguments: command line arguments:
- --show recipe: print recipe information - --show recipe: print recipe information
- --list if there's a bad recipe given - --list recipes if a bad recipe given
execution: execution:
- indent for line continuation - indent for line continuation

View File

@ -41,7 +41,7 @@ fn re(pattern: &str) -> Regex {
Regex::new(pattern).unwrap() Regex::new(pattern).unwrap()
} }
pub struct Recipe<'a> { struct Recipe<'a> {
line: usize, line: usize,
name: &'a str, name: &'a str,
leading_whitespace: &'a str, leading_whitespace: &'a str,
@ -49,6 +49,56 @@ pub struct Recipe<'a> {
dependencies: BTreeSet<&'a str>, dependencies: BTreeSet<&'a str>,
} }
impl<'a> Recipe<'a> {
fn run(&self) -> Result<(), RunError<'a>> {
// TODO: actually run recipes
warn!("running {}", self.name);
for command in &self.commands {
warn!("{}", command);
}
// Err(RunError::Code{recipe: self.name, code: -1})
Ok(())
}
}
fn resolve<'a>(
text: &'a str,
recipes: &BTreeMap<&str, Recipe<'a>>,
resolved: &mut HashSet<&'a str>,
seen: &mut HashSet<&'a str>,
stack: &mut Vec<&'a str>,
recipe: &Recipe<'a>,
) -> Result<(), Error<'a>> {
if resolved.contains(recipe.name) {
return Ok(())
}
stack.push(recipe.name);
seen.insert(recipe.name);
for dependency_name in &recipe.dependencies {
match recipes.get(dependency_name) {
Some(dependency) => if !resolved.contains(dependency.name) {
if seen.contains(dependency.name) {
let first = stack[0];
stack.push(first);
return Err(error(text, recipe.line, ErrorKind::CircularDependency {
circle: stack.iter()
.skip_while(|name| **name != dependency.name)
.cloned().collect()
}));
}
return resolve(text, recipes, resolved, seen, stack, dependency);
},
None => return Err(error(text, recipe.line, ErrorKind::UnknownDependency {
name: recipe.name,
unknown: dependency_name
})),
}
}
resolved.insert(recipe.name);
stack.pop();
Ok(())
}
#[derive(Debug)] #[derive(Debug)]
pub struct Error<'a> { pub struct Error<'a> {
text: &'a str, text: &'a str,
@ -162,7 +212,7 @@ impl<'a> Display for Error<'a> {
} }
pub struct Justfile<'a> { pub struct Justfile<'a> {
pub recipes: BTreeMap<&'a str, Recipe<'a>>, recipes: BTreeMap<&'a str, Recipe<'a>>,
} }
impl<'a> Justfile<'a> { impl<'a> Justfile<'a> {
@ -180,18 +230,67 @@ impl<'a> Justfile<'a> {
first.map(|recipe| recipe.name) first.map(|recipe| recipe.name)
} }
pub fn run(&self, recipes: &[&str]) { pub fn count(&self) -> usize {
if recipes.len() == 0 { self.recipes.len()
println!("running first recipe");
} else {
for recipe in recipes {
println!("running {}...", recipe);
}
}
} }
pub fn contains(&self, name: &str) -> bool { pub fn recipes(&self) -> Vec<&'a str> {
self.recipes.contains_key(name) self.recipes.keys().cloned().collect()
}
fn run_recipe(&self, recipe: &Recipe<'a>, ran: &mut HashSet<&'a str>) -> Result<(), RunError> {
for dependency_name in &recipe.dependencies {
if !ran.contains(dependency_name) {
try!(self.run_recipe(&self.recipes[dependency_name], ran));
}
}
try!(recipe.run());
ran.insert(recipe.name);
Ok(())
}
pub fn run<'b>(&'a self, names: &[&'b str]) -> Result<(), RunError<'b>>
where 'a: 'b
{
let mut missing = vec![];
for recipe in names {
if !self.recipes.contains_key(recipe) {
missing.push(*recipe);
}
}
if missing.len() > 0 {
return Err(RunError::UnknownRecipes{recipes: missing});
}
let recipes = names.iter().map(|name| self.recipes.get(name).unwrap()).collect::<Vec<_>>();
let mut ran = HashSet::new();
for recipe in recipes {
try!(self.run_recipe(recipe, &mut ran));
}
Ok(())
}
}
pub enum RunError<'a> {
UnknownRecipes{recipes: Vec<&'a str>},
// Signal{recipe: &'a str, signal: i32},
Code{recipe: &'a str, code: i32},
}
impl<'a> Display for RunError<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
match self {
&RunError::UnknownRecipes{ref recipes} => {
if recipes.len() == 1 {
try!(write!(f, "Justfile does not contain recipe: {}", recipes[0]));
} else {
try!(write!(f, "Justfile does not contain recipes: {}", recipes.join(" ")));
};
},
&RunError::Code{recipe, code} => {
try!(write!(f, "Recipe \"{}\" failed with code {}", recipe, code));
},
}
Ok(())
} }
} }
@ -296,41 +395,6 @@ pub fn parse<'a>(text: &'a str) -> Result<Justfile, Error> {
let mut seen = HashSet::new(); let mut seen = HashSet::new();
let mut stack = vec![]; let mut stack = vec![];
fn resolve<'a>(
text: &'a str,
recipes: &BTreeMap<&str, Recipe<'a>>,
resolved: &mut HashSet<&'a str>,
seen: &mut HashSet<&'a str>,
stack: &mut Vec<&'a str>,
recipe: &Recipe<'a>,
) -> Result<(), Error<'a>> {
stack.push(recipe.name);
seen.insert(recipe.name);
for dependency_name in &recipe.dependencies {
match recipes.get(dependency_name) {
Some(dependency) => if !resolved.contains(dependency.name) {
if seen.contains(dependency.name) {
let first = stack[0];
stack.push(first);
return Err(error(text, recipe.line, ErrorKind::CircularDependency {
circle: stack.iter()
.skip_while(|name| **name != dependency.name)
.cloned().collect()
}));
}
return resolve(text, recipes, resolved, seen, stack, dependency);
},
None => return Err(error(text, recipe.line, ErrorKind::UnknownDependency {
name: recipe.name,
unknown: dependency_name
})),
}
}
resolved.insert(recipe.name);
stack.pop();
Ok(())
}
for (_, ref recipe) in &recipes { for (_, ref recipe) in &recipes {
try!(resolve(text, &recipes, &mut resolved, &mut seen, &mut stack, &recipe)); try!(resolve(text, &recipes, &mut resolved, &mut seen, &mut stack, &recipe));
} }

View File

@ -61,38 +61,25 @@ fn main() {
let justfile = j::parse(&text).unwrap_or_else(|error| die!("{}", error)); let justfile = j::parse(&text).unwrap_or_else(|error| die!("{}", error));
if let Some(recipes) = matches.values_of("recipe") {
let mut missing = vec![];
for recipe in recipes {
if !justfile.recipes.contains_key(recipe) {
missing.push(recipe);
}
}
if missing.len() > 0 {
die!("unknown recipe{}: {}", if missing.len() == 1 { "" } else { "s" }, missing.join(" "));
}
}
if matches.is_present("list") { if matches.is_present("list") {
if justfile.recipes.len() == 0 { if justfile.count() == 0 {
warn!("Justfile contains no recipes"); warn!("Justfile contains no recipes");
} else { } else {
warn!("{}", justfile.recipes.keys().cloned().collect::<Vec<_>>().join(" ")); warn!("{}", justfile.recipes().join(" "));
} }
std::process::exit(0); std::process::exit(0);
} }
if let Some(values) = matches.values_of("recipe") { let names = if let Some(names) = matches.values_of("recipe") {
let names = values.collect::<Vec<_>>(); names.collect::<Vec<_>>()
for name in names.iter() {
if !justfile.contains(name) {
die!("Justfile does not contain recipe \"{}\"", name);
}
}
justfile.run(&names)
} else if let Some(name) = justfile.first() { } else if let Some(name) = justfile.first() {
justfile.run(&[name]) vec![name]
} else { } else {
die!("Justfile contains no recipes"); die!("Justfile contains no recipes");
};
if let Err(run_error) = justfile.run(&names) {
warn!("{}", run_error);
std::process::exit(if let j::RunError::Code{code, ..} = run_error { code } else { -1 });
} }
} }