line evaluation is done

This commit is contained in:
Casey Rodarmor 2016-10-27 09:44:07 -07:00
parent 5bbd28c49d
commit 4bb926abc4
3 changed files with 80 additions and 75 deletions

4
notes
View File

@ -1,6 +1,9 @@
notes notes
----- -----
- need to actually produce recipe lines
- test rendered recipe lines
- assignment - assignment
. add tokenizing test that covers interpolation . add tokenizing test that covers interpolation
. use the same rules as rust: https://doc.rust-lang.org/reference.html#string-literals . use the same rules as rust: https://doc.rust-lang.org/reference.html#string-literals
@ -14,7 +17,6 @@ notes
- disallow unused arguments and variables - disallow unused arguments and variables
- allow exporting environment variables - allow exporting environment variables
- write some tests to test the binary itself and all command line flags - write some tests to test the binary itself and all command line flags
- remove unhandled token stuff
- test that run runs first recipe by default - test that run runs first recipe by default
- parse arguments on command line: - parse arguments on command line:
. ugly but conservative: j build --set a=hello . ugly but conservative: j build --set a=hello

View File

@ -54,11 +54,8 @@ fn re(pattern: &str) -> Regex {
struct Recipe<'a> { struct Recipe<'a> {
line_number: usize, line_number: usize,
name: &'a str, name: &'a str,
lines: Vec<String>, lines: Vec<Vec<Fragment<'a>>>,
// fragments: Vec<Vec<Fragment<'a>>>, evaluated_lines: Vec<String>,
// variables: BTreeSet<&'a str>,
// variable_tokens: Vec<Token<'a>>,
new_lines: Vec<Vec<Fragmant<'a>>>,
dependencies: Vec<&'a str>, dependencies: Vec<&'a str>,
dependency_tokens: Vec<Token<'a>>, dependency_tokens: Vec<Token<'a>>,
arguments: Vec<&'a str>, arguments: Vec<&'a str>,
@ -67,7 +64,7 @@ struct Recipe<'a> {
} }
#[derive(PartialEq, Debug)] #[derive(PartialEq, Debug)]
enum Fragmant<'a> { enum Fragment<'a> {
Text{text: Token<'a>}, Text{text: Token<'a>},
Expression{expression: Expression<'a>}, Expression{expression: Expression<'a>},
} }
@ -149,7 +146,7 @@ impl<'a> Recipe<'a> {
); );
let mut text = String::new(); let mut text = String::new();
// add the shebang // add the shebang
text += &self.lines[0]; text += &self.evaluated_lines[0];
text += "\n"; text += "\n";
// add blank lines so that lines in the generated script // add blank lines so that lines in the generated script
// have the same line number as the corresponding lines // have the same line number as the corresponding lines
@ -157,7 +154,7 @@ impl<'a> Recipe<'a> {
for _ in 1..(self.line_number + 2) { for _ in 1..(self.line_number + 2) {
text += "\n" text += "\n"
} }
for line in &self.lines[1..] { for line in &self.evaluated_lines[1..] {
text += &line; text += &line;
text += "\n"; text += "\n";
} }
@ -193,7 +190,7 @@ impl<'a> Recipe<'a> {
Err(io_error) => Err(RunError::TmpdirIoError{recipe: self.name, io_error: io_error}) Err(io_error) => Err(RunError::TmpdirIoError{recipe: self.name, io_error: io_error})
}); });
} else { } else {
for command in &self.lines { for command in &self.evaluated_lines {
let mut command = &command[0..]; let mut command = &command[0..];
if !command.starts_with('@') { if !command.starts_with('@') {
warn!("{}", command); warn!("{}", command);
@ -233,7 +230,7 @@ impl<'a> Display for Recipe<'a> {
try!(write!(f, " {}", dependency)) try!(write!(f, " {}", dependency))
} }
for (i, pieces) in self.new_lines.iter().enumerate() { for (i, pieces) in self.lines.iter().enumerate() {
if i == 0 { if i == 0 {
try!(writeln!(f, "")); try!(writeln!(f, ""));
} }
@ -242,11 +239,11 @@ impl<'a> Display for Recipe<'a> {
try!(write!(f, " ")); try!(write!(f, " "));
} }
match piece { match piece {
&Fragmant::Text{ref text} => try!(write!(f, "{}", text.lexeme)), &Fragment::Text{ref text} => try!(write!(f, "{}", text.lexeme)),
&Fragmant::Expression{ref expression} => try!(write!(f, "{}{}{}", "{{", expression, "}}")), &Fragment::Expression{ref expression} => try!(write!(f, "{}{}{}", "{{", expression, "}}")),
} }
} }
if i + 1 < self.new_lines.len() { if i + 1 < self.lines.len() {
try!(write!(f, "\n")); try!(write!(f, "\n"));
} }
} }
@ -313,6 +310,7 @@ impl<'a, 'b> Resolver<'a, 'b> {
fn evaluate<'a>( fn evaluate<'a>(
assignments: &BTreeMap<&'a str, Expression<'a>>, assignments: &BTreeMap<&'a str, Expression<'a>>,
assignment_tokens: &BTreeMap<&'a str, Token<'a>>, assignment_tokens: &BTreeMap<&'a str, Token<'a>>,
recipes: &mut BTreeMap<&'a str, Recipe<'a>>,
) -> Result<BTreeMap<&'a str, String>, Error<'a>> { ) -> Result<BTreeMap<&'a str, String>, Error<'a>> {
let mut evaluator = Evaluator{ let mut evaluator = Evaluator{
seen: HashSet::new(), seen: HashSet::new(),
@ -324,6 +322,22 @@ fn evaluate<'a>(
for name in assignments.keys() { for name in assignments.keys() {
try!(evaluator.evaluate_assignment(name)); try!(evaluator.evaluate_assignment(name));
} }
for recipe in recipes.values_mut() {
for fragments in &recipe.lines {
let mut line = String::new();
for fragment in fragments {
match fragment {
&Fragment::Text{ref text} => line += text.lexeme,
&Fragment::Expression{ref expression} => {
line += &try!(evaluator.evaluate_expression(&expression));
}
}
}
recipe.evaluated_lines.push(line);
}
}
Ok(evaluator.evaluated) Ok(evaluator.evaluated)
} }
@ -356,7 +370,7 @@ impl<'a, 'b> Evaluator<'a, 'b> {
Ok(()) Ok(())
} }
fn evaluate_expression(&mut self, expression: &Expression<'a>,) -> Result<String, Error<'a>> { fn evaluate_expression(&mut self, expression: &Expression<'a>) -> Result<String, Error<'a>> {
Ok(match *expression { Ok(match *expression {
Expression::Variable{name, ref token} => { Expression::Variable{name, ref token} => {
if self.evaluated.contains_key(name) { if self.evaluated.contains_key(name) {
@ -745,20 +759,21 @@ fn token(pattern: &str) -> Regex {
fn tokenize<'a>(text: &'a str) -> Result<Vec<Token>, Error> { fn tokenize<'a>(text: &'a str) -> Result<Vec<Token>, Error> {
lazy_static! { lazy_static! {
static ref EOF: Regex = token(r"(?-m)$" ); static ref EOF: Regex = token(r"(?-m)$" );
static ref NAME: Regex = token(r"([a-zA-Z0-9_-]+)" ); static ref NAME: Regex = token(r"([a-zA-Z0-9_-]+)" );
static ref COLON: Regex = token(r":" ); static ref COLON: Regex = token(r":" );
static ref EQUALS: Regex = token(r"=" ); static ref EQUALS: Regex = token(r"=" );
static ref PLUS: Regex = token(r"[+]" ); static ref PLUS: Regex = token(r"[+]" );
static ref COMMENT: Regex = token(r"#([^!].*)?$" ); static ref COMMENT: Regex = token(r"#([^!].*)?$" );
static ref STRING: Regex = token("\"[a-z0-9]\"" ); static ref STRING: Regex = token("\"[a-z0-9]\"" );
static ref EOL: Regex = token(r"\n|\r\n" ); static ref EOL: Regex = token(r"\n|\r\n" );
static ref INTERPOLATION_END: Regex = token(r"[}][}]" ); static ref INTERPOLATION_END: Regex = token(r"[}][}]" );
static ref LINE: Regex = re(r"^(?m)[ \t]+[^ \t\n\r].*$"); static ref INTERPOLATION_START_TOKEN: Regex = token(r"[{][{]" );
static ref INDENT: Regex = re(r"^([ \t]*)[^ \t\n\r]" ); static ref LINE: Regex = re(r"^(?m)[ \t]+[^ \t\n\r].*$");
static ref INTERPOLATION_START: Regex = re(r"^[{][{]" ); static ref INDENT: Regex = re(r"^([ \t]*)[^ \t\n\r]" );
static ref LEADING_TEXT: Regex = re(r"^(?m)(.+?)[{][{]" ); static ref INTERPOLATION_START: Regex = re(r"^[{][{]" );
static ref TEXT: Regex = re(r"^(?m)(.+)" ); static ref LEADING_TEXT: Regex = re(r"^(?m)(.+?)[{][{]" );
static ref TEXT: Regex = re(r"^(?m)(.+)" );
} }
#[derive(PartialEq)] #[derive(PartialEq)]
@ -769,30 +784,16 @@ fn tokenize<'a>(text: &'a str) -> Result<Vec<Token>, Error> {
Interpolation, Interpolation,
} }
/*
struct Stack<'a> {
states: Vec<StateKind<'a>>
}
impl<'a> State<'a> {
fn current(&self) -> State {
self.states.last()
}
}
*/
fn indentation(text: &str) -> Option<&str> { fn indentation(text: &str) -> Option<&str> {
INDENT.captures(text).map(|captures| captures.at(1).unwrap()) INDENT.captures(text).map(|captures| captures.at(1).unwrap())
} }
let mut tokens = vec![]; let mut tokens = vec![];
let mut rest = text; let mut rest = text;
let mut index = 0; let mut index = 0;
let mut line = 0; let mut line = 0;
let mut column = 0; let mut column = 0;
// let mut indent: Option<&str> = None; let mut state = vec![State::Start];
// let mut state = StateKind::Start;
let mut state = vec![State::Start];
macro_rules! error { macro_rules! error {
($kind:expr) => {{ ($kind:expr) => {{
@ -880,14 +881,7 @@ fn tokenize<'a>(text: &'a str) -> Result<Vec<Token>, Error> {
if !line.starts_with(indent) { if !line.starts_with(indent) {
return error!(ErrorKind::InternalError{message: "unexpected indent".to_string()}); return error!(ErrorKind::InternalError{message: "unexpected indent".to_string()});
} }
//let (prefix, lexeme) = line.split_at(indent.len());
state.push(State::Text); state.push(State::Text);
//(prefix, lexeme, Line)
// state we can produce text, {{, or eol tokens
// will produce text, name, {{, tokens }}, until end of line
(&line[0..indent.len()], "", Line) (&line[0..indent.len()], "", Line)
} else if let Some(captures) = EOF.captures(rest) { } else if let Some(captures) = EOF.captures(rest) {
(captures.at(1).unwrap(), captures.at(2).unwrap(), Eof) (captures.at(1).unwrap(), captures.at(2).unwrap(), Eof)
@ -907,12 +901,12 @@ fn tokenize<'a>(text: &'a str) -> Result<Vec<Token>, Error> {
message: format!("Could not match token in text state: \"{}\"", rest) message: format!("Could not match token in text state: \"{}\"", rest)
}); });
} }
} else if let Some(captures) = INTERPOLATION_START_TOKEN.captures(rest) {
(captures.at(1).unwrap(), captures.at(2).unwrap(), InterpolationStart)
} else if let Some(captures) = INTERPOLATION_END.captures(rest) { } else if let Some(captures) = INTERPOLATION_END.captures(rest) {
if state.last().unwrap() != &State::Interpolation { if state.last().unwrap() == &State::Interpolation {
// improve error state.pop();
panic!("interpolation end outside of interpolation state");
} }
state.pop();
(captures.at(1).unwrap(), captures.at(2).unwrap(), InterpolationEnd) (captures.at(1).unwrap(), captures.at(2).unwrap(), InterpolationEnd)
} else if let Some(captures) = NAME.captures(rest) { } else if let Some(captures) = NAME.captures(rest) {
(captures.at(1).unwrap(), captures.at(2).unwrap(), Name) (captures.at(1).unwrap(), captures.at(2).unwrap(), Name)
@ -1086,7 +1080,7 @@ impl<'a> Parser<'a> {
return Err(self.unexpected_token(&token, &[Name, Eol, Eof])); return Err(self.unexpected_token(&token, &[Name, Eol, Eof]));
} }
let mut new_lines = vec![]; let mut lines = vec![];
let mut shebang = false; let mut shebang = false;
if self.accepted(Indent) { if self.accepted(Indent) {
@ -1104,7 +1098,7 @@ impl<'a> Parser<'a> {
while !(self.accepted(Eol) || self.peek(Dedent)) { while !(self.accepted(Eol) || self.peek(Dedent)) {
if let Some(token) = self.accept(Text) { if let Some(token) = self.accept(Text) {
if pieces.is_empty() { if pieces.is_empty() {
if new_lines.is_empty() { if lines.is_empty() {
if token.lexeme.starts_with("#!") { if token.lexeme.starts_with("#!") {
shebang = true; shebang = true;
} }
@ -1112,18 +1106,18 @@ impl<'a> Parser<'a> {
return Err(token.error(ErrorKind::ExtraLeadingWhitespace)); return Err(token.error(ErrorKind::ExtraLeadingWhitespace));
} }
} }
pieces.push(Fragmant::Text{text: token}); pieces.push(Fragment::Text{text: token});
} else if let Some(token) = self.expect(InterpolationStart) { } else if let Some(token) = self.expect(InterpolationStart) {
return Err(self.unexpected_token(&token, &[Text, InterpolationStart, Eol])); return Err(self.unexpected_token(&token, &[Text, InterpolationStart, Eol]));
} else { } else {
pieces.push(Fragmant::Expression{expression: try!(self.expression(true))}); pieces.push(Fragment::Expression{expression: try!(self.expression(true))});
if let Some(token) = self.expect(InterpolationEnd) { if let Some(token) = self.expect(InterpolationEnd) {
return Err(self.unexpected_token(&token, &[InterpolationEnd])); return Err(self.unexpected_token(&token, &[InterpolationEnd]));
} }
} }
} }
new_lines.push(pieces); lines.push(pieces);
} }
} }
@ -1229,8 +1223,8 @@ impl<'a> Parser<'a> {
// fragments: fragments, // fragments: fragments,
// variables: variables, // variables: variables,
// variable_tokens: variable_tokens, // variable_tokens: variable_tokens,
lines: vec![], evaluated_lines: vec![],
new_lines: new_lines, lines: lines,
// lines: lines, // lines: lines,
shebang: shebang, shebang: shebang,
}) })
@ -1290,9 +1284,7 @@ impl<'a> Parser<'a> {
Comment => return Err(token.error(ErrorKind::InternalError { Comment => return Err(token.error(ErrorKind::InternalError {
message: "found comment in token stream".to_string() message: "found comment in token stream".to_string()
})), })),
_ => return Err(token.error(ErrorKind::InternalError { _ => return return Err(self.unexpected_token(&token, &[Name])),
message: format!("unhandled token class: {:?}", token.class)
})),
}, },
None => return Err(Error { None => return Err(Error {
text: self.text, text: self.text,
@ -1324,9 +1316,9 @@ impl<'a> Parser<'a> {
} }
} }
for line in &recipe.new_lines { for line in &recipe.lines {
for piece in line { for piece in line {
if let &Fragmant::Expression{ref expression} = piece { if let &Fragment::Expression{ref expression} = piece {
for variable in expression.variables() { for variable in expression.variables() {
let name = variable.lexeme; let name = variable.lexeme;
if !(assignments.contains_key(&name) || recipe.arguments.contains(&name)) { if !(assignments.contains_key(&name) || recipe.arguments.contains(&name)) {
@ -1357,7 +1349,7 @@ impl<'a> Parser<'a> {
} }
} }
let values = try!(evaluate(&assignments, &assignment_tokens)); let values = try!(evaluate(&assignments, &assignment_tokens, &mut recipes));
Ok(Justfile{ Ok(Justfile{
recipes: recipes, recipes: recipes,

View File

@ -524,6 +524,19 @@ fn bad_interpolation_variable_name() {
}); });
} }
#[test]
fn interpolation_outside_of_recipe() {
let text = "{{";
parse_error(text, Error {
text: text,
index: 0,
line: 0,
column: 0,
width: Some(2),
kind: ErrorKind::UnexpectedToken{expected: vec![Name], found: InterpolationStart},
});
}
#[test] #[test]
fn unclosed_interpolation_delimiter() { fn unclosed_interpolation_delimiter() {
let text = "a:\n echo {{ foo"; let text = "a:\n echo {{ foo";
@ -610,7 +623,6 @@ fn run_arguments_not_supported() {
} }
} }
/*
#[test] #[test]
fn run_shebang() { fn run_shebang() {
// this test exists to make sure that shebang recipes // this test exists to make sure that shebang recipes
@ -648,4 +660,3 @@ fn code_error() {
other @ _ => panic!("expected a code run error, but got: {}", other), other @ _ => panic!("expected a code run error, but got: {}", other),
} }
} }
*/