line evaluation is done
This commit is contained in:
parent
5bbd28c49d
commit
4bb926abc4
4
notes
4
notes
@ -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
|
||||||
|
136
src/lib.rs
136
src/lib.rs
@ -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,
|
||||||
|
15
src/tests.rs
15
src/tests.rs
@ -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),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
Loading…
Reference in New Issue
Block a user