Give errors clearer names (#249)
This commit is contained in:
parent
a4bf6c3a4e
commit
86dc82f548
198
src/lib.rs
198
src/lib.rs
@ -196,16 +196,16 @@ impl<'a> Display for Expression<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return a `RunError::Signal` if the process was terminated by a signal,
|
/// Return a `RuntimeError::Signal` if the process was terminated by a signal,
|
||||||
/// otherwise return an `RunError::UnknownFailure`
|
/// otherwise return an `RuntimeError::UnknownFailure`
|
||||||
fn error_from_signal(
|
fn error_from_signal(
|
||||||
recipe: &str,
|
recipe: &str,
|
||||||
line_number: Option<usize>,
|
line_number: Option<usize>,
|
||||||
exit_status: process::ExitStatus
|
exit_status: process::ExitStatus
|
||||||
) -> RunError {
|
) -> RuntimeError {
|
||||||
match Platform::signal_from_exit_status(exit_status) {
|
match Platform::signal_from_exit_status(exit_status) {
|
||||||
Some(signal) => RunError::Signal{recipe: recipe, line_number: line_number, signal: signal},
|
Some(signal) => RuntimeError::Signal{recipe: recipe, line_number: line_number, signal: signal},
|
||||||
None => RunError::UnknownFailure{recipe: recipe, line_number: line_number},
|
None => RuntimeError::UnknownFailure{recipe: recipe, line_number: line_number},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -213,12 +213,12 @@ fn export_env<'a>(
|
|||||||
command: &mut process::Command,
|
command: &mut process::Command,
|
||||||
scope: &Map<&'a str, String>,
|
scope: &Map<&'a str, String>,
|
||||||
exports: &Set<&'a str>,
|
exports: &Set<&'a str>,
|
||||||
) -> Result<(), RunError<'a>> {
|
) -> Result<(), RuntimeError<'a>> {
|
||||||
for name in exports {
|
for name in exports {
|
||||||
if let Some(value) = scope.get(name) {
|
if let Some(value) = scope.get(name) {
|
||||||
command.env(name, value);
|
command.env(name, value);
|
||||||
} else {
|
} else {
|
||||||
return Err(RunError::InternalError {
|
return Err(RuntimeError::InternalError {
|
||||||
message: format!("scope does not contain exported variable `{}`", name),
|
message: format!("scope does not contain exported variable `{}`", name),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -232,7 +232,7 @@ fn run_backtick<'a>(
|
|||||||
scope: &Map<&'a str, String>,
|
scope: &Map<&'a str, String>,
|
||||||
exports: &Set<&'a str>,
|
exports: &Set<&'a str>,
|
||||||
quiet: bool,
|
quiet: bool,
|
||||||
) -> Result<String, RunError<'a>> {
|
) -> Result<String, RuntimeError<'a>> {
|
||||||
let mut cmd = process::Command::new(DEFAULT_SHELL);
|
let mut cmd = process::Command::new(DEFAULT_SHELL);
|
||||||
|
|
||||||
export_env(&mut cmd, scope, exports)?;
|
export_env(&mut cmd, scope, exports)?;
|
||||||
@ -246,7 +246,7 @@ fn run_backtick<'a>(
|
|||||||
process::Stdio::inherit()
|
process::Stdio::inherit()
|
||||||
});
|
});
|
||||||
|
|
||||||
output(cmd).map_err(|output_error| RunError::Backtick{token: token.clone(), output_error})
|
output(cmd).map_err(|output_error| RuntimeError::Backtick{token: token.clone(), output_error})
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Recipe<'a> {
|
impl<'a> Recipe<'a> {
|
||||||
@ -272,7 +272,7 @@ impl<'a> Recipe<'a> {
|
|||||||
scope: &Map<&'a str, String>,
|
scope: &Map<&'a str, String>,
|
||||||
exports: &Set<&'a str>,
|
exports: &Set<&'a str>,
|
||||||
options: &RunOptions,
|
options: &RunOptions,
|
||||||
) -> Result<(), RunError<'a>> {
|
) -> Result<(), RuntimeError<'a>> {
|
||||||
if options.verbose {
|
if options.verbose {
|
||||||
let color = options.color.stderr().banner();
|
let color = options.color.stderr().banner();
|
||||||
eprintln!("{}===> Running recipe `{}`...{}", color.prefix(), self.name, color.suffix());
|
eprintln!("{}===> Running recipe `{}`...{}", color.prefix(), self.name, color.suffix());
|
||||||
@ -285,7 +285,7 @@ impl<'a> Recipe<'a> {
|
|||||||
let value = if rest.is_empty() {
|
let value = if rest.is_empty() {
|
||||||
match parameter.default {
|
match parameter.default {
|
||||||
Some(ref default) => Cow::Borrowed(default.as_str()),
|
Some(ref default) => Cow::Borrowed(default.as_str()),
|
||||||
None => return Err(RunError::InternalError{
|
None => return Err(RuntimeError::InternalError{
|
||||||
message: "missing parameter without default".to_string()
|
message: "missing parameter without default".to_string()
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
@ -327,12 +327,12 @@ impl<'a> Recipe<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let tmp = tempdir::TempDir::new("just")
|
let tmp = tempdir::TempDir::new("just")
|
||||||
.map_err(|error| RunError::TmpdirIoError{recipe: self.name, io_error: error})?;
|
.map_err(|error| RuntimeError::TmpdirIoError{recipe: self.name, io_error: error})?;
|
||||||
let mut path = tmp.path().to_path_buf();
|
let mut path = tmp.path().to_path_buf();
|
||||||
path.push(self.name);
|
path.push(self.name);
|
||||||
{
|
{
|
||||||
let mut f = fs::File::create(&path)
|
let mut f = fs::File::create(&path)
|
||||||
.map_err(|error| RunError::TmpdirIoError{recipe: self.name, io_error: error})?;
|
.map_err(|error| RuntimeError::TmpdirIoError{recipe: self.name, io_error: error})?;
|
||||||
let mut text = String::new();
|
let mut text = String::new();
|
||||||
// add the shebang
|
// add the shebang
|
||||||
text += &evaluated_lines[0];
|
text += &evaluated_lines[0];
|
||||||
@ -348,26 +348,26 @@ impl<'a> Recipe<'a> {
|
|||||||
text += "\n";
|
text += "\n";
|
||||||
}
|
}
|
||||||
f.write_all(text.as_bytes())
|
f.write_all(text.as_bytes())
|
||||||
.map_err(|error| RunError::TmpdirIoError{recipe: self.name, io_error: error})?;
|
.map_err(|error| RuntimeError::TmpdirIoError{recipe: self.name, io_error: error})?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// make the script executable
|
// make the script executable
|
||||||
Platform::set_execute_permission(&path)
|
Platform::set_execute_permission(&path)
|
||||||
.map_err(|error| RunError::TmpdirIoError{recipe: self.name, io_error: error})?;
|
.map_err(|error| RuntimeError::TmpdirIoError{recipe: self.name, io_error: error})?;
|
||||||
|
|
||||||
let shebang_line = evaluated_lines.first()
|
let shebang_line = evaluated_lines.first()
|
||||||
.ok_or_else(|| RunError::InternalError {
|
.ok_or_else(|| RuntimeError::InternalError {
|
||||||
message: "evaluated_lines was empty".to_string()
|
message: "evaluated_lines was empty".to_string()
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let (shebang_command, shebang_argument) = split_shebang(shebang_line)
|
let (shebang_command, shebang_argument) = split_shebang(shebang_line)
|
||||||
.ok_or_else(|| RunError::InternalError {
|
.ok_or_else(|| RuntimeError::InternalError {
|
||||||
message: format!("bad shebang line: {}", shebang_line)
|
message: format!("bad shebang line: {}", shebang_line)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
// create a command to run the script
|
// create a command to run the script
|
||||||
let mut command = Platform::make_shebang_command(&path, shebang_command, shebang_argument)
|
let mut command = Platform::make_shebang_command(&path, shebang_command, shebang_argument)
|
||||||
.map_err(|output_error| RunError::Cygpath{recipe: self.name, output_error: output_error})?;
|
.map_err(|output_error| RuntimeError::Cygpath{recipe: self.name, output_error: output_error})?;
|
||||||
|
|
||||||
// export environment variables
|
// export environment variables
|
||||||
export_env(&mut command, scope, exports)?;
|
export_env(&mut command, scope, exports)?;
|
||||||
@ -376,12 +376,12 @@ impl<'a> Recipe<'a> {
|
|||||||
match command.status() {
|
match command.status() {
|
||||||
Ok(exit_status) => if let Some(code) = exit_status.code() {
|
Ok(exit_status) => if let Some(code) = exit_status.code() {
|
||||||
if code != 0 {
|
if code != 0 {
|
||||||
return Err(RunError::Code{recipe: self.name, line_number: None, code: code})
|
return Err(RuntimeError::Code{recipe: self.name, line_number: None, code: code})
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return Err(error_from_signal(self.name, None, exit_status))
|
return Err(error_from_signal(self.name, None, exit_status))
|
||||||
},
|
},
|
||||||
Err(io_error) => return Err(RunError::Shebang {
|
Err(io_error) => return Err(RuntimeError::Shebang {
|
||||||
recipe: self.name,
|
recipe: self.name,
|
||||||
command: shebang_command.to_string(),
|
command: shebang_command.to_string(),
|
||||||
argument: shebang_argument.map(String::from),
|
argument: shebang_argument.map(String::from),
|
||||||
@ -446,14 +446,14 @@ impl<'a> Recipe<'a> {
|
|||||||
match cmd.status() {
|
match cmd.status() {
|
||||||
Ok(exit_status) => if let Some(code) = exit_status.code() {
|
Ok(exit_status) => if let Some(code) = exit_status.code() {
|
||||||
if code != 0 {
|
if code != 0 {
|
||||||
return Err(RunError::Code{
|
return Err(RuntimeError::Code{
|
||||||
recipe: self.name, line_number: Some(line_number), code: code
|
recipe: self.name, line_number: Some(line_number), code: code
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return Err(error_from_signal(self.name, Some(line_number), exit_status));
|
return Err(error_from_signal(self.name, Some(line_number), exit_status));
|
||||||
},
|
},
|
||||||
Err(io_error) => return Err(RunError::IoError{
|
Err(io_error) => return Err(RuntimeError::IoError{
|
||||||
recipe: self.name, io_error: io_error}),
|
recipe: self.name, io_error: io_error}),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -502,7 +502,7 @@ fn resolve_recipes<'a>(
|
|||||||
recipes: &Map<&'a str, Recipe<'a>>,
|
recipes: &Map<&'a str, Recipe<'a>>,
|
||||||
assignments: &Map<&'a str, Expression<'a>>,
|
assignments: &Map<&'a str, Expression<'a>>,
|
||||||
text: &'a str,
|
text: &'a str,
|
||||||
) -> Result<(), CompileError<'a>> {
|
) -> Result<(), CompilationError<'a>> {
|
||||||
let mut resolver = Resolver {
|
let mut resolver = Resolver {
|
||||||
seen: empty(),
|
seen: empty(),
|
||||||
stack: empty(),
|
stack: empty(),
|
||||||
@ -533,14 +533,14 @@ fn resolve_recipes<'a>(
|
|||||||
// two lifetime parameters instead of one, with one being the lifetime
|
// two lifetime parameters instead of one, with one being the lifetime
|
||||||
// of the struct, and the second being the lifetime of the tokens
|
// of the struct, and the second being the lifetime of the tokens
|
||||||
// that it contains
|
// that it contains
|
||||||
let error = variable.error(ErrorKind::UndefinedVariable{variable: name});
|
let error = variable.error(CompilationErrorKind::UndefinedVariable{variable: name});
|
||||||
return Err(CompileError {
|
return Err(CompilationError {
|
||||||
text: text,
|
text: text,
|
||||||
index: error.index,
|
index: error.index,
|
||||||
line: error.line,
|
line: error.line,
|
||||||
column: error.column,
|
column: error.column,
|
||||||
width: error.width,
|
width: error.width,
|
||||||
kind: ErrorKind::UndefinedVariable {
|
kind: CompilationErrorKind::UndefinedVariable {
|
||||||
variable: &text[error.index..error.index + error.width.unwrap()],
|
variable: &text[error.index..error.index + error.width.unwrap()],
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -562,7 +562,7 @@ struct Resolver<'a: 'b, 'b> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, 'b> Resolver<'a, 'b> {
|
impl<'a, 'b> Resolver<'a, 'b> {
|
||||||
fn resolve(&mut self, recipe: &Recipe<'a>) -> Result<(), CompileError<'a>> {
|
fn resolve(&mut self, recipe: &Recipe<'a>) -> Result<(), CompilationError<'a>> {
|
||||||
if self.resolved.contains(recipe.name) {
|
if self.resolved.contains(recipe.name) {
|
||||||
return Ok(())
|
return Ok(())
|
||||||
}
|
}
|
||||||
@ -574,7 +574,7 @@ impl<'a, 'b> Resolver<'a, 'b> {
|
|||||||
if self.seen.contains(dependency.name) {
|
if self.seen.contains(dependency.name) {
|
||||||
let first = self.stack[0];
|
let first = self.stack[0];
|
||||||
self.stack.push(first);
|
self.stack.push(first);
|
||||||
return Err(dependency_token.error(ErrorKind::CircularRecipeDependency {
|
return Err(dependency_token.error(CompilationErrorKind::CircularRecipeDependency {
|
||||||
recipe: recipe.name,
|
recipe: recipe.name,
|
||||||
circle: self.stack.iter()
|
circle: self.stack.iter()
|
||||||
.skip_while(|name| **name != dependency.name)
|
.skip_while(|name| **name != dependency.name)
|
||||||
@ -583,7 +583,7 @@ impl<'a, 'b> Resolver<'a, 'b> {
|
|||||||
}
|
}
|
||||||
self.resolve(dependency)?;
|
self.resolve(dependency)?;
|
||||||
},
|
},
|
||||||
None => return Err(dependency_token.error(ErrorKind::UnknownDependency {
|
None => return Err(dependency_token.error(CompilationErrorKind::UnknownDependency {
|
||||||
recipe: recipe.name,
|
recipe: recipe.name,
|
||||||
unknown: dependency_token.lexeme
|
unknown: dependency_token.lexeme
|
||||||
})),
|
})),
|
||||||
@ -598,7 +598,7 @@ impl<'a, 'b> Resolver<'a, 'b> {
|
|||||||
fn resolve_assignments<'a>(
|
fn resolve_assignments<'a>(
|
||||||
assignments: &Map<&'a str, Expression<'a>>,
|
assignments: &Map<&'a str, Expression<'a>>,
|
||||||
assignment_tokens: &Map<&'a str, Token<'a>>,
|
assignment_tokens: &Map<&'a str, Token<'a>>,
|
||||||
) -> Result<(), CompileError<'a>> {
|
) -> Result<(), CompilationError<'a>> {
|
||||||
|
|
||||||
let mut resolver = AssignmentResolver {
|
let mut resolver = AssignmentResolver {
|
||||||
assignments: assignments,
|
assignments: assignments,
|
||||||
@ -624,7 +624,7 @@ struct AssignmentResolver<'a: 'b, 'b> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'a: 'b, 'b> AssignmentResolver<'a, 'b> {
|
impl<'a: 'b, 'b> AssignmentResolver<'a, 'b> {
|
||||||
fn resolve_assignment(&mut self, name: &'a str) -> Result<(), CompileError<'a>> {
|
fn resolve_assignment(&mut self, name: &'a str) -> Result<(), CompilationError<'a>> {
|
||||||
if self.evaluated.contains(name) {
|
if self.evaluated.contains(name) {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
@ -641,7 +641,7 @@ impl<'a: 'b, 'b> AssignmentResolver<'a, 'b> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resolve_expression(&mut self, expression: &Expression<'a>) -> Result<(), CompileError<'a>> {
|
fn resolve_expression(&mut self, expression: &Expression<'a>) -> Result<(), CompilationError<'a>> {
|
||||||
match *expression {
|
match *expression {
|
||||||
Expression::Variable{name, ref token} => {
|
Expression::Variable{name, ref token} => {
|
||||||
if self.evaluated.contains(name) {
|
if self.evaluated.contains(name) {
|
||||||
@ -649,14 +649,14 @@ impl<'a: 'b, 'b> AssignmentResolver<'a, 'b> {
|
|||||||
} else if self.seen.contains(name) {
|
} else if self.seen.contains(name) {
|
||||||
let token = &self.assignment_tokens[name];
|
let token = &self.assignment_tokens[name];
|
||||||
self.stack.push(name);
|
self.stack.push(name);
|
||||||
return Err(token.error(ErrorKind::CircularVariableDependency {
|
return Err(token.error(CompilationErrorKind::CircularVariableDependency {
|
||||||
variable: name,
|
variable: name,
|
||||||
circle: self.stack.clone(),
|
circle: self.stack.clone(),
|
||||||
}));
|
}));
|
||||||
} else if self.assignments.contains_key(name) {
|
} else if self.assignments.contains_key(name) {
|
||||||
self.resolve_assignment(name)?;
|
self.resolve_assignment(name)?;
|
||||||
} else {
|
} else {
|
||||||
return Err(token.error(ErrorKind::UndefinedVariable{variable: name}));
|
return Err(token.error(CompilationErrorKind::UndefinedVariable{variable: name}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Expression::Concatination{ref lhs, ref rhs} => {
|
Expression::Concatination{ref lhs, ref rhs} => {
|
||||||
@ -673,7 +673,7 @@ fn evaluate_assignments<'a>(
|
|||||||
assignments: &Map<&'a str, Expression<'a>>,
|
assignments: &Map<&'a str, Expression<'a>>,
|
||||||
overrides: &Map<&str, &str>,
|
overrides: &Map<&str, &str>,
|
||||||
quiet: bool,
|
quiet: bool,
|
||||||
) -> Result<Map<&'a str, String>, RunError<'a>> {
|
) -> Result<Map<&'a str, String>, RuntimeError<'a>> {
|
||||||
let mut evaluator = Evaluator {
|
let mut evaluator = Evaluator {
|
||||||
assignments: assignments,
|
assignments: assignments,
|
||||||
evaluated: empty(),
|
evaluated: empty(),
|
||||||
@ -704,7 +704,7 @@ impl<'a, 'b> Evaluator<'a, 'b> {
|
|||||||
&mut self,
|
&mut self,
|
||||||
line: &[Fragment<'a>],
|
line: &[Fragment<'a>],
|
||||||
arguments: &Map<&str, Cow<str>>
|
arguments: &Map<&str, Cow<str>>
|
||||||
) -> Result<String, RunError<'a>> {
|
) -> Result<String, RuntimeError<'a>> {
|
||||||
let mut evaluated = String::new();
|
let mut evaluated = String::new();
|
||||||
for fragment in line {
|
for fragment in line {
|
||||||
match *fragment {
|
match *fragment {
|
||||||
@ -717,7 +717,7 @@ impl<'a, 'b> Evaluator<'a, 'b> {
|
|||||||
Ok(evaluated)
|
Ok(evaluated)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn evaluate_assignment(&mut self, name: &'a str) -> Result<(), RunError<'a>> {
|
fn evaluate_assignment(&mut self, name: &'a str) -> Result<(), RuntimeError<'a>> {
|
||||||
if self.evaluated.contains_key(name) {
|
if self.evaluated.contains_key(name) {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
@ -730,7 +730,7 @@ impl<'a, 'b> Evaluator<'a, 'b> {
|
|||||||
self.evaluated.insert(name, value);
|
self.evaluated.insert(name, value);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return Err(RunError::InternalError {
|
return Err(RuntimeError::InternalError {
|
||||||
message: format!("attempted to evaluated unknown assignment {}", name)
|
message: format!("attempted to evaluated unknown assignment {}", name)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -742,7 +742,7 @@ impl<'a, 'b> Evaluator<'a, 'b> {
|
|||||||
&mut self,
|
&mut self,
|
||||||
expression: &Expression<'a>,
|
expression: &Expression<'a>,
|
||||||
arguments: &Map<&str, Cow<str>>
|
arguments: &Map<&str, Cow<str>>
|
||||||
) -> Result<String, RunError<'a>> {
|
) -> Result<String, RuntimeError<'a>> {
|
||||||
Ok(match *expression {
|
Ok(match *expression {
|
||||||
Expression::Variable{name, ..} => {
|
Expression::Variable{name, ..} => {
|
||||||
if self.evaluated.contains_key(name) {
|
if self.evaluated.contains_key(name) {
|
||||||
@ -755,7 +755,7 @@ impl<'a, 'b> Evaluator<'a, 'b> {
|
|||||||
} else if arguments.contains_key(name) {
|
} else if arguments.contains_key(name) {
|
||||||
arguments[name].to_string()
|
arguments[name].to_string()
|
||||||
} else {
|
} else {
|
||||||
return Err(RunError::InternalError {
|
return Err(RuntimeError::InternalError {
|
||||||
message: format!("attempted to evaluate undefined variable `{}`", name)
|
message: format!("attempted to evaluate undefined variable `{}`", name)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -774,17 +774,17 @@ impl<'a, 'b> Evaluator<'a, 'b> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
struct CompileError<'a> {
|
struct CompilationError<'a> {
|
||||||
text: &'a str,
|
text: &'a str,
|
||||||
index: usize,
|
index: usize,
|
||||||
line: usize,
|
line: usize,
|
||||||
column: usize,
|
column: usize,
|
||||||
width: Option<usize>,
|
width: Option<usize>,
|
||||||
kind: ErrorKind<'a>,
|
kind: CompilationErrorKind<'a>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
enum ErrorKind<'a> {
|
enum CompilationErrorKind<'a> {
|
||||||
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>},
|
||||||
DependencyHasParameters{recipe: &'a str, dependency: &'a str},
|
DependencyHasParameters{recipe: &'a str, dependency: &'a str},
|
||||||
@ -808,14 +808,14 @@ enum ErrorKind<'a> {
|
|||||||
UnterminatedString,
|
UnterminatedString,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn internal_error(message: String) -> CompileError<'static> {
|
fn internal_error(message: String) -> CompilationError<'static> {
|
||||||
CompileError {
|
CompilationError {
|
||||||
text: "",
|
text: "",
|
||||||
index: 0,
|
index: 0,
|
||||||
line: 0,
|
line: 0,
|
||||||
column: 0,
|
column: 0,
|
||||||
width: None,
|
width: None,
|
||||||
kind: ErrorKind::InternalError { message: message }
|
kind: CompilationErrorKind::InternalError { message: message }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -853,7 +853,7 @@ struct CookedString<'a> {
|
|||||||
cooked: String,
|
cooked: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cook_string<'a>(token: &Token<'a>) -> Result<CookedString<'a>, CompileError<'a>> {
|
fn cook_string<'a>(token: &Token<'a>) -> Result<CookedString<'a>, CompilationError<'a>> {
|
||||||
let raw = &token.lexeme[1..token.lexeme.len()-1];
|
let raw = &token.lexeme[1..token.lexeme.len()-1];
|
||||||
|
|
||||||
if let RawString = token.kind {
|
if let RawString = token.kind {
|
||||||
@ -869,7 +869,7 @@ fn cook_string<'a>(token: &Token<'a>) -> Result<CookedString<'a>, CompileError<'
|
|||||||
't' => cooked.push('\t'),
|
't' => cooked.push('\t'),
|
||||||
'\\' => cooked.push('\\'),
|
'\\' => cooked.push('\\'),
|
||||||
'"' => cooked.push('"'),
|
'"' => cooked.push('"'),
|
||||||
other => return Err(token.error(ErrorKind::InvalidEscapeSequence {
|
other => return Err(token.error(CompilationErrorKind::InvalidEscapeSequence {
|
||||||
character: other,
|
character: other,
|
||||||
})),
|
})),
|
||||||
}
|
}
|
||||||
@ -884,7 +884,7 @@ fn cook_string<'a>(token: &Token<'a>) -> Result<CookedString<'a>, CompileError<'
|
|||||||
}
|
}
|
||||||
Ok(CookedString{raw: raw, cooked: cooked})
|
Ok(CookedString{raw: raw, cooked: cooked})
|
||||||
} else {
|
} else {
|
||||||
Err(token.error(ErrorKind::InternalError{
|
Err(token.error(CompilationErrorKind::InternalError{
|
||||||
message: "cook_string() called on non-string token".to_string()
|
message: "cook_string() called on non-string token".to_string()
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
@ -993,9 +993,9 @@ fn write_token_error_context(f: &mut fmt::Formatter, token: &Token) -> Result<()
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Display for CompileError<'a> {
|
impl<'a> Display for CompilationError<'a> {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||||
use ErrorKind::*;
|
use CompilationErrorKind::*;
|
||||||
let error = Color::fmt(f).error();
|
let error = Color::fmt(f).error();
|
||||||
let message = Color::fmt(f).message();
|
let message = Color::fmt(f).message();
|
||||||
|
|
||||||
@ -1150,13 +1150,13 @@ impl<'a, 'b> Justfile<'a> where 'a: 'b {
|
|||||||
&'a self,
|
&'a self,
|
||||||
arguments: &[&'a str],
|
arguments: &[&'a str],
|
||||||
options: &RunOptions<'a>,
|
options: &RunOptions<'a>,
|
||||||
) -> Result<(), RunError<'a>> {
|
) -> Result<(), RuntimeError<'a>> {
|
||||||
let unknown_overrides = options.overrides.keys().cloned()
|
let unknown_overrides = options.overrides.keys().cloned()
|
||||||
.filter(|name| !self.assignments.contains_key(name))
|
.filter(|name| !self.assignments.contains_key(name))
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
if !unknown_overrides.is_empty() {
|
if !unknown_overrides.is_empty() {
|
||||||
return Err(RunError::UnknownOverrides{overrides: unknown_overrides});
|
return Err(RuntimeError::UnknownOverrides{overrides: unknown_overrides});
|
||||||
}
|
}
|
||||||
|
|
||||||
let scope = evaluate_assignments(&self.assignments, &options.overrides, options.quiet)?;
|
let scope = evaluate_assignments(&self.assignments, &options.overrides, options.quiet)?;
|
||||||
@ -1184,7 +1184,7 @@ impl<'a, 'b> Justfile<'a> where 'a: 'b {
|
|||||||
let argument_range = recipe.argument_range();
|
let argument_range = recipe.argument_range();
|
||||||
let argument_count = cmp::min(tail.len(), recipe.max_arguments());
|
let argument_count = cmp::min(tail.len(), recipe.max_arguments());
|
||||||
if !contains(&argument_range, argument_count) {
|
if !contains(&argument_range, argument_count) {
|
||||||
return Err(RunError::ArgumentCountMismatch {
|
return Err(RuntimeError::ArgumentCountMismatch {
|
||||||
recipe: recipe.name,
|
recipe: recipe.name,
|
||||||
found: tail.len(),
|
found: tail.len(),
|
||||||
min: recipe.min_arguments(),
|
min: recipe.min_arguments(),
|
||||||
@ -1206,7 +1206,7 @@ impl<'a, 'b> Justfile<'a> where 'a: 'b {
|
|||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
return Err(RunError::UnknownRecipes{recipes: missing, suggestion: suggestion});
|
return Err(RuntimeError::UnknownRecipes{recipes: missing, suggestion: suggestion});
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut ran = empty();
|
let mut ran = empty();
|
||||||
@ -1224,7 +1224,7 @@ impl<'a, 'b> Justfile<'a> where 'a: 'b {
|
|||||||
scope: &Map<&'c str, String>,
|
scope: &Map<&'c str, String>,
|
||||||
ran: &mut Set<&'a str>,
|
ran: &mut Set<&'a str>,
|
||||||
options: &RunOptions<'a>,
|
options: &RunOptions<'a>,
|
||||||
) -> Result<(), RunError> {
|
) -> Result<(), RuntimeError> {
|
||||||
for dependency_name in &recipe.dependencies {
|
for dependency_name in &recipe.dependencies {
|
||||||
if !ran.contains(dependency_name) {
|
if !ran.contains(dependency_name) {
|
||||||
self.run_recipe(&self.recipes[dependency_name], &[], scope, ran, options)?;
|
self.run_recipe(&self.recipes[dependency_name], &[], scope, ran, options)?;
|
||||||
@ -1261,7 +1261,7 @@ impl<'a> Display for Justfile<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
enum RunError<'a> {
|
enum RuntimeError<'a> {
|
||||||
ArgumentCountMismatch{recipe: &'a str, found: usize, min: usize, max: usize},
|
ArgumentCountMismatch{recipe: &'a str, found: usize, min: usize, max: usize},
|
||||||
Backtick{token: Token<'a>, output_error: OutputError},
|
Backtick{token: Token<'a>, output_error: OutputError},
|
||||||
Code{recipe: &'a str, line_number: Option<usize>, code: i32},
|
Code{recipe: &'a str, line_number: Option<usize>, code: i32},
|
||||||
@ -1276,9 +1276,9 @@ enum RunError<'a> {
|
|||||||
UnknownRecipes{recipes: Vec<&'a str>, suggestion: Option<&'a str>},
|
UnknownRecipes{recipes: Vec<&'a str>, suggestion: Option<&'a str>},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> RunError<'a> {
|
impl<'a> RuntimeError<'a> {
|
||||||
fn code(&self) -> Option<i32> {
|
fn code(&self) -> Option<i32> {
|
||||||
use RunError::*;
|
use RuntimeError::*;
|
||||||
match *self {
|
match *self {
|
||||||
Code{code, ..} | Backtick{output_error: OutputError::Code(code), ..} => Some(code),
|
Code{code, ..} | Backtick{output_error: OutputError::Code(code), ..} => Some(code),
|
||||||
_ => None,
|
_ => None,
|
||||||
@ -1286,9 +1286,9 @@ impl<'a> RunError<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Display for RunError<'a> {
|
impl<'a> Display for RuntimeError<'a> {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||||
use RunError::*;
|
use RuntimeError::*;
|
||||||
let color = if f.alternate() { Color::always() } else { Color::never() };
|
let color = if f.alternate() { Color::always() } else { Color::never() };
|
||||||
let error = color.error();
|
let error = color.error();
|
||||||
let message = color.message();
|
let message = color.message();
|
||||||
@ -1456,8 +1456,8 @@ struct Token<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Token<'a> {
|
impl<'a> Token<'a> {
|
||||||
fn error(&self, kind: ErrorKind<'a>) -> CompileError<'a> {
|
fn error(&self, kind: CompilationErrorKind<'a>) -> CompilationError<'a> {
|
||||||
CompileError {
|
CompilationError {
|
||||||
text: self.text,
|
text: self.text,
|
||||||
index: self.index + self.prefix.len(),
|
index: self.index + self.prefix.len(),
|
||||||
line: self.line,
|
line: self.line,
|
||||||
@ -1523,7 +1523,7 @@ fn token(pattern: &str) -> Regex {
|
|||||||
re(&s)
|
re(&s)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tokenize(text: &str) -> Result<Vec<Token>, CompileError> {
|
fn tokenize(text: &str) -> Result<Vec<Token>, CompilationError> {
|
||||||
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":" );
|
||||||
@ -1567,7 +1567,7 @@ fn tokenize(text: &str) -> Result<Vec<Token>, CompileError> {
|
|||||||
|
|
||||||
macro_rules! error {
|
macro_rules! error {
|
||||||
($kind:expr) => {{
|
($kind:expr) => {{
|
||||||
Err(CompileError {
|
Err(CompilationError {
|
||||||
text: text,
|
text: text,
|
||||||
index: index,
|
index: index,
|
||||||
line: line,
|
line: line,
|
||||||
@ -1589,7 +1589,7 @@ fn tokenize(text: &str) -> Result<Vec<Token>, CompileError> {
|
|||||||
// indent: was no indentation, now there is
|
// indent: was no indentation, now there is
|
||||||
(&State::Start, Some(current)) => {
|
(&State::Start, Some(current)) => {
|
||||||
if mixed_whitespace(current) {
|
if mixed_whitespace(current) {
|
||||||
return error!(ErrorKind::MixedLeadingWhitespace{whitespace: current})
|
return error!(CompilationErrorKind::MixedLeadingWhitespace{whitespace: current})
|
||||||
}
|
}
|
||||||
//indent = Some(current);
|
//indent = Some(current);
|
||||||
state.push(State::Indent(current));
|
state.push(State::Indent(current));
|
||||||
@ -1604,7 +1604,7 @@ fn tokenize(text: &str) -> Result<Vec<Token>, CompileError> {
|
|||||||
// was indentation and still is, check if the new indentation matches
|
// was indentation and still is, check if the new indentation matches
|
||||||
(&State::Indent(previous), Some(current)) => {
|
(&State::Indent(previous), Some(current)) => {
|
||||||
if !current.starts_with(previous) {
|
if !current.starts_with(previous) {
|
||||||
return error!(ErrorKind::InconsistentLeadingWhitespace{
|
return error!(CompilationErrorKind::InconsistentLeadingWhitespace{
|
||||||
expected: previous,
|
expected: previous,
|
||||||
found: current
|
found: current
|
||||||
});
|
});
|
||||||
@ -1613,7 +1613,7 @@ fn tokenize(text: &str) -> Result<Vec<Token>, CompileError> {
|
|||||||
}
|
}
|
||||||
// at column 0 in some other state: this should never happen
|
// at column 0 in some other state: this should never happen
|
||||||
(&State::Text, _) | (&State::Interpolation, _) => {
|
(&State::Text, _) | (&State::Interpolation, _) => {
|
||||||
return error!(ErrorKind::InternalError{
|
return error!(CompilationErrorKind::InternalError{
|
||||||
message: "unexpected state at column 0".to_string()
|
message: "unexpected state at column 0".to_string()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -1648,7 +1648,7 @@ fn tokenize(text: &str) -> Result<Vec<Token>, CompileError> {
|
|||||||
(column, state.last().unwrap(), LINE.captures(rest)) {
|
(column, state.last().unwrap(), LINE.captures(rest)) {
|
||||||
let line = captures.get(0).unwrap().as_str();
|
let line = captures.get(0).unwrap().as_str();
|
||||||
if !line.starts_with(indent) {
|
if !line.starts_with(indent) {
|
||||||
return error!(ErrorKind::InternalError{message: "unexpected indent".to_string()});
|
return error!(CompilationErrorKind::InternalError{message: "unexpected indent".to_string()});
|
||||||
}
|
}
|
||||||
state.push(State::Text);
|
state.push(State::Text);
|
||||||
(&line[0..indent.len()], "", Line)
|
(&line[0..indent.len()], "", Line)
|
||||||
@ -1666,7 +1666,7 @@ fn tokenize(text: &str) -> Result<Vec<Token>, CompileError> {
|
|||||||
state.pop();
|
state.pop();
|
||||||
(captures.get(1).unwrap().as_str(), captures.get(2).unwrap().as_str(), Eol)
|
(captures.get(1).unwrap().as_str(), captures.get(2).unwrap().as_str(), Eol)
|
||||||
} else {
|
} else {
|
||||||
return error!(ErrorKind::InternalError{
|
return error!(CompilationErrorKind::InternalError{
|
||||||
message: format!("Could not match token in text state: \"{}\"", rest)
|
message: format!("Could not match token in text state: \"{}\"", rest)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -1681,7 +1681,7 @@ fn tokenize(text: &str) -> Result<Vec<Token>, CompileError> {
|
|||||||
(captures.get(1).unwrap().as_str(), captures.get(2).unwrap().as_str(), Name)
|
(captures.get(1).unwrap().as_str(), captures.get(2).unwrap().as_str(), Name)
|
||||||
} else if let Some(captures) = EOL.captures(rest) {
|
} else if let Some(captures) = EOL.captures(rest) {
|
||||||
if state.last().unwrap() == &State::Interpolation {
|
if state.last().unwrap() == &State::Interpolation {
|
||||||
return error!(ErrorKind::InternalError {
|
return error!(CompilationErrorKind::InternalError {
|
||||||
message: "hit EOL while still in interpolation state".to_string()
|
message: "hit EOL while still in interpolation state".to_string()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -1701,18 +1701,18 @@ fn tokenize(text: &str) -> Result<Vec<Token>, CompileError> {
|
|||||||
} else if let Some(captures) = RAW_STRING.captures(rest) {
|
} else if let Some(captures) = RAW_STRING.captures(rest) {
|
||||||
(captures.get(1).unwrap().as_str(), captures.get(2).unwrap().as_str(), RawString)
|
(captures.get(1).unwrap().as_str(), captures.get(2).unwrap().as_str(), RawString)
|
||||||
} else if UNTERMINATED_RAW_STRING.is_match(rest) {
|
} else if UNTERMINATED_RAW_STRING.is_match(rest) {
|
||||||
return error!(ErrorKind::UnterminatedString);
|
return error!(CompilationErrorKind::UnterminatedString);
|
||||||
} else if let Some(captures) = STRING.captures(rest) {
|
} else if let Some(captures) = STRING.captures(rest) {
|
||||||
let prefix = captures.get(1).unwrap().as_str();
|
let prefix = captures.get(1).unwrap().as_str();
|
||||||
let contents = &rest[prefix.len()+1..];
|
let contents = &rest[prefix.len()+1..];
|
||||||
if contents.is_empty() {
|
if contents.is_empty() {
|
||||||
return error!(ErrorKind::UnterminatedString);
|
return error!(CompilationErrorKind::UnterminatedString);
|
||||||
}
|
}
|
||||||
let mut len = 0;
|
let mut len = 0;
|
||||||
let mut escape = false;
|
let mut escape = false;
|
||||||
for c in contents.chars() {
|
for c in contents.chars() {
|
||||||
if c == '\n' || c == '\r' {
|
if c == '\n' || c == '\r' {
|
||||||
return error!(ErrorKind::UnterminatedString);
|
return error!(CompilationErrorKind::UnterminatedString);
|
||||||
} else if !escape && c == '"' {
|
} else if !escape && c == '"' {
|
||||||
break;
|
break;
|
||||||
} else if !escape && c == '\\' {
|
} else if !escape && c == '\\' {
|
||||||
@ -1725,13 +1725,13 @@ fn tokenize(text: &str) -> Result<Vec<Token>, CompileError> {
|
|||||||
let start = prefix.len();
|
let start = prefix.len();
|
||||||
let content_end = start + len + 1;
|
let content_end = start + len + 1;
|
||||||
if escape || content_end >= rest.len() {
|
if escape || content_end >= rest.len() {
|
||||||
return error!(ErrorKind::UnterminatedString);
|
return error!(CompilationErrorKind::UnterminatedString);
|
||||||
}
|
}
|
||||||
(prefix, &rest[start..content_end + 1], StringToken)
|
(prefix, &rest[start..content_end + 1], StringToken)
|
||||||
} else if rest.starts_with("#!") {
|
} else if rest.starts_with("#!") {
|
||||||
return error!(ErrorKind::OuterShebang)
|
return error!(CompilationErrorKind::OuterShebang)
|
||||||
} else {
|
} else {
|
||||||
return error!(ErrorKind::UnknownStartOfToken)
|
return error!(CompilationErrorKind::UnknownStartOfToken)
|
||||||
};
|
};
|
||||||
|
|
||||||
tokens.push(Token {
|
tokens.push(Token {
|
||||||
@ -1750,7 +1750,7 @@ fn tokenize(text: &str) -> Result<Vec<Token>, CompileError> {
|
|||||||
let last = tokens.last().unwrap();
|
let last = tokens.last().unwrap();
|
||||||
match last.kind {
|
match last.kind {
|
||||||
Eof => {},
|
Eof => {},
|
||||||
_ => return Err(last.error(ErrorKind::InternalError{
|
_ => return Err(last.error(CompilationErrorKind::InternalError{
|
||||||
message: format!("zero length token: {:?}", last)
|
message: format!("zero length token: {:?}", last)
|
||||||
})),
|
})),
|
||||||
}
|
}
|
||||||
@ -1785,7 +1785,7 @@ fn tokenize(text: &str) -> Result<Vec<Token>, CompileError> {
|
|||||||
Ok(tokens)
|
Ok(tokens)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compile(text: &str) -> Result<Justfile, CompileError> {
|
fn compile(text: &str) -> Result<Justfile, CompilationError> {
|
||||||
let tokens = tokenize(text)?;
|
let tokens = tokenize(text)?;
|
||||||
let parser = Parser {
|
let parser = Parser {
|
||||||
text: text,
|
text: text,
|
||||||
@ -1857,8 +1857,8 @@ impl<'a> Parser<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn unexpected_token(&self, found: &Token<'a>, expected: &[TokenKind]) -> CompileError<'a> {
|
fn unexpected_token(&self, found: &Token<'a>, expected: &[TokenKind]) -> CompilationError<'a> {
|
||||||
found.error(ErrorKind::UnexpectedToken {
|
found.error(CompilationErrorKind::UnexpectedToken {
|
||||||
expected: expected.to_vec(),
|
expected: expected.to_vec(),
|
||||||
found: found.kind,
|
found: found.kind,
|
||||||
})
|
})
|
||||||
@ -1869,9 +1869,9 @@ impl<'a> Parser<'a> {
|
|||||||
name: Token<'a>,
|
name: Token<'a>,
|
||||||
doc: Option<Token<'a>>,
|
doc: Option<Token<'a>>,
|
||||||
quiet: bool,
|
quiet: bool,
|
||||||
) -> Result<(), CompileError<'a>> {
|
) -> Result<(), CompilationError<'a>> {
|
||||||
if let Some(recipe) = self.recipes.get(name.lexeme) {
|
if let Some(recipe) = self.recipes.get(name.lexeme) {
|
||||||
return Err(name.error(ErrorKind::DuplicateRecipe {
|
return Err(name.error(CompilationErrorKind::DuplicateRecipe {
|
||||||
recipe: recipe.name,
|
recipe: recipe.name,
|
||||||
first: recipe.line_number
|
first: recipe.line_number
|
||||||
}));
|
}));
|
||||||
@ -1895,13 +1895,13 @@ impl<'a> Parser<'a> {
|
|||||||
let variadic = plus.is_some();
|
let variadic = plus.is_some();
|
||||||
|
|
||||||
if parsed_variadic_parameter {
|
if parsed_variadic_parameter {
|
||||||
return Err(parameter.error(ErrorKind::ParameterFollowsVariadicParameter {
|
return Err(parameter.error(CompilationErrorKind::ParameterFollowsVariadicParameter {
|
||||||
parameter: parameter.lexeme,
|
parameter: parameter.lexeme,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
if parameters.iter().any(|p| p.name == parameter.lexeme) {
|
if parameters.iter().any(|p| p.name == parameter.lexeme) {
|
||||||
return Err(parameter.error(ErrorKind::DuplicateParameter {
|
return Err(parameter.error(CompilationErrorKind::DuplicateParameter {
|
||||||
recipe: name.lexeme, parameter: parameter.lexeme
|
recipe: name.lexeme, parameter: parameter.lexeme
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@ -1919,7 +1919,7 @@ impl<'a> Parser<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if parsed_parameter_with_default && default.is_none() {
|
if parsed_parameter_with_default && default.is_none() {
|
||||||
return Err(parameter.error(ErrorKind::RequiredParameterFollowsDefaultParameter{
|
return Err(parameter.error(CompilationErrorKind::RequiredParameterFollowsDefaultParameter{
|
||||||
parameter: parameter.lexeme,
|
parameter: parameter.lexeme,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@ -1949,7 +1949,7 @@ impl<'a> Parser<'a> {
|
|||||||
let mut dependency_tokens = vec![];
|
let mut dependency_tokens = vec![];
|
||||||
while let Some(dependency) = self.accept(Name) {
|
while let Some(dependency) = self.accept(Name) {
|
||||||
if dependencies.contains(&dependency.lexeme) {
|
if dependencies.contains(&dependency.lexeme) {
|
||||||
return Err(dependency.error(ErrorKind::DuplicateDependency {
|
return Err(dependency.error(CompilationErrorKind::DuplicateDependency {
|
||||||
recipe: name.lexeme,
|
recipe: name.lexeme,
|
||||||
dependency: dependency.lexeme
|
dependency: dependency.lexeme
|
||||||
}));
|
}));
|
||||||
@ -1972,7 +1972,7 @@ impl<'a> Parser<'a> {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if let Some(token) = self.expect(Line) {
|
if let Some(token) = self.expect(Line) {
|
||||||
return Err(token.error(ErrorKind::InternalError{
|
return Err(token.error(CompilationErrorKind::InternalError{
|
||||||
message: format!("Expected a line but got {}", token.kind)
|
message: format!("Expected a line but got {}", token.kind)
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
@ -1989,7 +1989,7 @@ impl<'a> Parser<'a> {
|
|||||||
&& !lines.last().and_then(|line| line.last())
|
&& !lines.last().and_then(|line| line.last())
|
||||||
.map(Fragment::continuation).unwrap_or(false)
|
.map(Fragment::continuation).unwrap_or(false)
|
||||||
&& (token.lexeme.starts_with(' ') || token.lexeme.starts_with('\t')) {
|
&& (token.lexeme.starts_with(' ') || token.lexeme.starts_with('\t')) {
|
||||||
return Err(token.error(ErrorKind::ExtraLeadingWhitespace));
|
return Err(token.error(CompilationErrorKind::ExtraLeadingWhitespace));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fragments.push(Fragment::Text{text: token});
|
fragments.push(Fragment::Text{text: token});
|
||||||
@ -2025,7 +2025,7 @@ impl<'a> Parser<'a> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn expression(&mut self, interpolation: bool) -> Result<Expression<'a>, CompileError<'a>> {
|
fn expression(&mut self, interpolation: bool) -> Result<Expression<'a>, CompilationError<'a>> {
|
||||||
let first = self.tokens.next().unwrap();
|
let first = self.tokens.next().unwrap();
|
||||||
let lhs = match first.kind {
|
let lhs = match first.kind {
|
||||||
Name => Expression::Variable {name: first.lexeme, token: first},
|
Name => Expression::Variable {name: first.lexeme, token: first},
|
||||||
@ -2055,9 +2055,9 @@ impl<'a> Parser<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn assignment(&mut self, name: Token<'a>, export: bool) -> Result<(), CompileError<'a>> {
|
fn assignment(&mut self, name: Token<'a>, export: bool) -> Result<(), CompilationError<'a>> {
|
||||||
if self.assignments.contains_key(name.lexeme) {
|
if self.assignments.contains_key(name.lexeme) {
|
||||||
return Err(name.error(ErrorKind::DuplicateVariable {variable: name.lexeme}));
|
return Err(name.error(CompilationErrorKind::DuplicateVariable {variable: name.lexeme}));
|
||||||
}
|
}
|
||||||
if export {
|
if export {
|
||||||
self.exports.insert(name.lexeme);
|
self.exports.insert(name.lexeme);
|
||||||
@ -2068,7 +2068,7 @@ impl<'a> Parser<'a> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn justfile(mut self) -> Result<Justfile<'a>, CompileError<'a>> {
|
fn justfile(mut self) -> Result<Justfile<'a>, CompilationError<'a>> {
|
||||||
let mut doc = None;
|
let mut doc = None;
|
||||||
loop {
|
loop {
|
||||||
match self.tokens.next() {
|
match self.tokens.next() {
|
||||||
@ -2080,7 +2080,7 @@ impl<'a> Parser<'a> {
|
|||||||
}
|
}
|
||||||
Comment => {
|
Comment => {
|
||||||
if let Some(token) = self.expect_eol() {
|
if let Some(token) = self.expect_eol() {
|
||||||
return Err(token.error(ErrorKind::InternalError {
|
return Err(token.error(CompilationErrorKind::InternalError {
|
||||||
message: format!("found comment followed by {}", token.kind),
|
message: format!("found comment followed by {}", token.kind),
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@ -2112,13 +2112,13 @@ impl<'a> Parser<'a> {
|
|||||||
},
|
},
|
||||||
_ => return Err(self.unexpected_token(&token, &[Name, At])),
|
_ => return Err(self.unexpected_token(&token, &[Name, At])),
|
||||||
},
|
},
|
||||||
None => return Err(CompileError {
|
None => return Err(CompilationError {
|
||||||
text: self.text,
|
text: self.text,
|
||||||
index: 0,
|
index: 0,
|
||||||
line: 0,
|
line: 0,
|
||||||
column: 0,
|
column: 0,
|
||||||
width: None,
|
width: None,
|
||||||
kind: ErrorKind::InternalError {
|
kind: CompilationErrorKind::InternalError {
|
||||||
message: "unexpected end of token stream".to_string()
|
message: "unexpected end of token stream".to_string()
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
@ -2126,7 +2126,7 @@ impl<'a> Parser<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some(token) = self.tokens.next() {
|
if let Some(token) = self.tokens.next() {
|
||||||
return Err(token.error(ErrorKind::InternalError{
|
return Err(token.error(CompilationErrorKind::InternalError{
|
||||||
message: format!("unexpected token remaining after parsing completed: {:?}", token.kind)
|
message: format!("unexpected token remaining after parsing completed: {:?}", token.kind)
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
@ -2136,7 +2136,7 @@ impl<'a> Parser<'a> {
|
|||||||
for recipe in self.recipes.values() {
|
for recipe in self.recipes.values() {
|
||||||
for parameter in &recipe.parameters {
|
for parameter in &recipe.parameters {
|
||||||
if self.assignments.contains_key(parameter.token.lexeme) {
|
if self.assignments.contains_key(parameter.token.lexeme) {
|
||||||
return Err(parameter.token.error(ErrorKind::ParameterShadowsVariable {
|
return Err(parameter.token.error(CompilationErrorKind::ParameterShadowsVariable {
|
||||||
parameter: parameter.token.lexeme
|
parameter: parameter.token.lexeme
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@ -2144,7 +2144,7 @@ impl<'a> Parser<'a> {
|
|||||||
|
|
||||||
for dependency in &recipe.dependency_tokens {
|
for dependency in &recipe.dependency_tokens {
|
||||||
if !self.recipes[dependency.lexeme].parameters.is_empty() {
|
if !self.recipes[dependency.lexeme].parameters.is_empty() {
|
||||||
return Err(dependency.error(ErrorKind::DependencyHasParameters {
|
return Err(dependency.error(CompilationErrorKind::DependencyHasParameters {
|
||||||
recipe: recipe.name,
|
recipe: recipe.name,
|
||||||
dependency: dependency.lexeme,
|
dependency: dependency.lexeme,
|
||||||
}));
|
}));
|
||||||
|
170
src/unit.rs
170
src/unit.rs
@ -2,8 +2,8 @@ extern crate tempdir;
|
|||||||
extern crate brev;
|
extern crate brev;
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
And, CompileError, ErrorKind, Justfile, Or,
|
And, CompilationError, CompilationErrorKind, Justfile, Or,
|
||||||
OutputError, RunError, RunOptions, Token,
|
OutputError, RuntimeError, RunOptions, Token,
|
||||||
compile, contains, tokenize
|
compile, contains, tokenize
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -24,7 +24,7 @@ fn tokenize_success(text: &str, expected_summary: &str) {
|
|||||||
assert_eq!(text, roundtrip);
|
assert_eq!(text, roundtrip);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tokenize_error(text: &str, expected: CompileError) {
|
fn tokenize_error(text: &str, expected: CompilationError) {
|
||||||
if let Err(error) = tokenize(text) {
|
if let Err(error) = tokenize(text) {
|
||||||
assert_eq!(error.text, expected.text);
|
assert_eq!(error.text, expected.text);
|
||||||
assert_eq!(error.index, expected.index);
|
assert_eq!(error.index, expected.index);
|
||||||
@ -78,7 +78,7 @@ fn parse_summary(input: &str, output: &str) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_error(text: &str, expected: CompileError) {
|
fn parse_error(text: &str, expected: CompilationError) {
|
||||||
if let Err(error) = compile(text) {
|
if let Err(error) = compile(text) {
|
||||||
assert_eq!(error.text, expected.text);
|
assert_eq!(error.text, expected.text);
|
||||||
assert_eq!(error.index, expected.index);
|
assert_eq!(error.index, expected.index);
|
||||||
@ -215,13 +215,13 @@ fn tokenize_space_then_tab() {
|
|||||||
1
|
1
|
||||||
\t2
|
\t2
|
||||||
";
|
";
|
||||||
tokenize_error(text, CompileError {
|
tokenize_error(text, CompilationError {
|
||||||
text: text,
|
text: text,
|
||||||
index: 9,
|
index: 9,
|
||||||
line: 3,
|
line: 3,
|
||||||
column: 0,
|
column: 0,
|
||||||
width: None,
|
width: None,
|
||||||
kind: ErrorKind::InconsistentLeadingWhitespace{expected: " ", found: "\t"},
|
kind: CompilationErrorKind::InconsistentLeadingWhitespace{expected: " ", found: "\t"},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -232,39 +232,39 @@ fn tokenize_tabs_then_tab_space() {
|
|||||||
\t\t 1
|
\t\t 1
|
||||||
\t 2
|
\t 2
|
||||||
";
|
";
|
||||||
tokenize_error(text, CompileError {
|
tokenize_error(text, CompilationError {
|
||||||
text: text,
|
text: text,
|
||||||
index: 12,
|
index: 12,
|
||||||
line: 3,
|
line: 3,
|
||||||
column: 0,
|
column: 0,
|
||||||
width: None,
|
width: None,
|
||||||
kind: ErrorKind::InconsistentLeadingWhitespace{expected: "\t\t", found: "\t "},
|
kind: CompilationErrorKind::InconsistentLeadingWhitespace{expected: "\t\t", found: "\t "},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn tokenize_outer_shebang() {
|
fn tokenize_outer_shebang() {
|
||||||
let text = "#!/usr/bin/env bash";
|
let text = "#!/usr/bin/env bash";
|
||||||
tokenize_error(text, CompileError {
|
tokenize_error(text, CompilationError {
|
||||||
text: text,
|
text: text,
|
||||||
index: 0,
|
index: 0,
|
||||||
line: 0,
|
line: 0,
|
||||||
column: 0,
|
column: 0,
|
||||||
width: None,
|
width: None,
|
||||||
kind: ErrorKind::OuterShebang
|
kind: CompilationErrorKind::OuterShebang
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn tokenize_unknown() {
|
fn tokenize_unknown() {
|
||||||
let text = "~";
|
let text = "~";
|
||||||
tokenize_error(text, CompileError {
|
tokenize_error(text, CompilationError {
|
||||||
text: text,
|
text: text,
|
||||||
index: 0,
|
index: 0,
|
||||||
line: 0,
|
line: 0,
|
||||||
column: 0,
|
column: 0,
|
||||||
width: None,
|
width: None,
|
||||||
kind: ErrorKind::UnknownStartOfToken
|
kind: CompilationErrorKind::UnknownStartOfToken
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -425,104 +425,104 @@ r#"a:
|
|||||||
#[test]
|
#[test]
|
||||||
fn missing_colon() {
|
fn missing_colon() {
|
||||||
let text = "a b c\nd e f";
|
let text = "a b c\nd e f";
|
||||||
parse_error(text, CompileError {
|
parse_error(text, CompilationError {
|
||||||
text: text,
|
text: text,
|
||||||
index: 5,
|
index: 5,
|
||||||
line: 0,
|
line: 0,
|
||||||
column: 5,
|
column: 5,
|
||||||
width: Some(1),
|
width: Some(1),
|
||||||
kind: ErrorKind::UnexpectedToken{expected: vec![Name, Plus, Colon], found: Eol},
|
kind: CompilationErrorKind::UnexpectedToken{expected: vec![Name, Plus, Colon], found: Eol},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn missing_default_eol() {
|
fn missing_default_eol() {
|
||||||
let text = "hello arg=\n";
|
let text = "hello arg=\n";
|
||||||
parse_error(text, CompileError {
|
parse_error(text, CompilationError {
|
||||||
text: text,
|
text: text,
|
||||||
index: 10,
|
index: 10,
|
||||||
line: 0,
|
line: 0,
|
||||||
column: 10,
|
column: 10,
|
||||||
width: Some(1),
|
width: Some(1),
|
||||||
kind: ErrorKind::UnexpectedToken{expected: vec![StringToken, RawString], found: Eol},
|
kind: CompilationErrorKind::UnexpectedToken{expected: vec![StringToken, RawString], found: Eol},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn missing_default_eof() {
|
fn missing_default_eof() {
|
||||||
let text = "hello arg=";
|
let text = "hello arg=";
|
||||||
parse_error(text, CompileError {
|
parse_error(text, CompilationError {
|
||||||
text: text,
|
text: text,
|
||||||
index: 10,
|
index: 10,
|
||||||
line: 0,
|
line: 0,
|
||||||
column: 10,
|
column: 10,
|
||||||
width: Some(0),
|
width: Some(0),
|
||||||
kind: ErrorKind::UnexpectedToken{expected: vec![StringToken, RawString], found: Eof},
|
kind: CompilationErrorKind::UnexpectedToken{expected: vec![StringToken, RawString], found: Eof},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn missing_default_colon() {
|
fn missing_default_colon() {
|
||||||
let text = "hello arg=:";
|
let text = "hello arg=:";
|
||||||
parse_error(text, CompileError {
|
parse_error(text, CompilationError {
|
||||||
text: text,
|
text: text,
|
||||||
index: 10,
|
index: 10,
|
||||||
line: 0,
|
line: 0,
|
||||||
column: 10,
|
column: 10,
|
||||||
width: Some(1),
|
width: Some(1),
|
||||||
kind: ErrorKind::UnexpectedToken{expected: vec![StringToken, RawString], found: Colon},
|
kind: CompilationErrorKind::UnexpectedToken{expected: vec![StringToken, RawString], found: Colon},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn missing_default_backtick() {
|
fn missing_default_backtick() {
|
||||||
let text = "hello arg=`hello`";
|
let text = "hello arg=`hello`";
|
||||||
parse_error(text, CompileError {
|
parse_error(text, CompilationError {
|
||||||
text: text,
|
text: text,
|
||||||
index: 10,
|
index: 10,
|
||||||
line: 0,
|
line: 0,
|
||||||
column: 10,
|
column: 10,
|
||||||
width: Some(7),
|
width: Some(7),
|
||||||
kind: ErrorKind::UnexpectedToken{expected: vec![StringToken, RawString], found: Backtick},
|
kind: CompilationErrorKind::UnexpectedToken{expected: vec![StringToken, RawString], found: Backtick},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parameter_after_variadic() {
|
fn parameter_after_variadic() {
|
||||||
let text = "foo +a bbb:";
|
let text = "foo +a bbb:";
|
||||||
parse_error(text, CompileError {
|
parse_error(text, CompilationError {
|
||||||
text: text,
|
text: text,
|
||||||
index: 7,
|
index: 7,
|
||||||
line: 0,
|
line: 0,
|
||||||
column: 7,
|
column: 7,
|
||||||
width: Some(3),
|
width: Some(3),
|
||||||
kind: ErrorKind::ParameterFollowsVariadicParameter{parameter: "bbb"}
|
kind: CompilationErrorKind::ParameterFollowsVariadicParameter{parameter: "bbb"}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn required_after_default() {
|
fn required_after_default() {
|
||||||
let text = "hello arg='foo' bar:";
|
let text = "hello arg='foo' bar:";
|
||||||
parse_error(text, CompileError {
|
parse_error(text, CompilationError {
|
||||||
text: text,
|
text: text,
|
||||||
index: 16,
|
index: 16,
|
||||||
line: 0,
|
line: 0,
|
||||||
column: 16,
|
column: 16,
|
||||||
width: Some(3),
|
width: Some(3),
|
||||||
kind: ErrorKind::RequiredParameterFollowsDefaultParameter{parameter: "bar"},
|
kind: CompilationErrorKind::RequiredParameterFollowsDefaultParameter{parameter: "bar"},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn missing_eol() {
|
fn missing_eol() {
|
||||||
let text = "a b c: z =";
|
let text = "a b c: z =";
|
||||||
parse_error(text, CompileError {
|
parse_error(text, CompilationError {
|
||||||
text: text,
|
text: text,
|
||||||
index: 9,
|
index: 9,
|
||||||
line: 0,
|
line: 0,
|
||||||
column: 9,
|
column: 9,
|
||||||
width: Some(1),
|
width: Some(1),
|
||||||
kind: ErrorKind::UnexpectedToken{expected: vec![Name, Eol, Eof], found: Equals},
|
kind: CompilationErrorKind::UnexpectedToken{expected: vec![Name, Eol, Eof], found: Equals},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -534,143 +534,143 @@ fn eof_test() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn duplicate_parameter() {
|
fn duplicate_parameter() {
|
||||||
let text = "a b b:";
|
let text = "a b b:";
|
||||||
parse_error(text, CompileError {
|
parse_error(text, CompilationError {
|
||||||
text: text,
|
text: text,
|
||||||
index: 4,
|
index: 4,
|
||||||
line: 0,
|
line: 0,
|
||||||
column: 4,
|
column: 4,
|
||||||
width: Some(1),
|
width: Some(1),
|
||||||
kind: ErrorKind::DuplicateParameter{recipe: "a", parameter: "b"}
|
kind: CompilationErrorKind::DuplicateParameter{recipe: "a", parameter: "b"}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parameter_shadows_varible() {
|
fn parameter_shadows_varible() {
|
||||||
let text = "foo = \"h\"\na foo:";
|
let text = "foo = \"h\"\na foo:";
|
||||||
parse_error(text, CompileError {
|
parse_error(text, CompilationError {
|
||||||
text: text,
|
text: text,
|
||||||
index: 12,
|
index: 12,
|
||||||
line: 1,
|
line: 1,
|
||||||
column: 2,
|
column: 2,
|
||||||
width: Some(3),
|
width: Some(3),
|
||||||
kind: ErrorKind::ParameterShadowsVariable{parameter: "foo"}
|
kind: CompilationErrorKind::ParameterShadowsVariable{parameter: "foo"}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn dependency_has_parameters() {
|
fn dependency_has_parameters() {
|
||||||
let text = "foo arg:\nb: foo";
|
let text = "foo arg:\nb: foo";
|
||||||
parse_error(text, CompileError {
|
parse_error(text, CompilationError {
|
||||||
text: text,
|
text: text,
|
||||||
index: 12,
|
index: 12,
|
||||||
line: 1,
|
line: 1,
|
||||||
column: 3,
|
column: 3,
|
||||||
width: Some(3),
|
width: Some(3),
|
||||||
kind: ErrorKind::DependencyHasParameters{recipe: "b", dependency: "foo"}
|
kind: CompilationErrorKind::DependencyHasParameters{recipe: "b", dependency: "foo"}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn duplicate_dependency() {
|
fn duplicate_dependency() {
|
||||||
let text = "a b c: b c z z";
|
let text = "a b c: b c z z";
|
||||||
parse_error(text, CompileError {
|
parse_error(text, CompilationError {
|
||||||
text: text,
|
text: text,
|
||||||
index: 13,
|
index: 13,
|
||||||
line: 0,
|
line: 0,
|
||||||
column: 13,
|
column: 13,
|
||||||
width: Some(1),
|
width: Some(1),
|
||||||
kind: ErrorKind::DuplicateDependency{recipe: "a", dependency: "z"}
|
kind: CompilationErrorKind::DuplicateDependency{recipe: "a", dependency: "z"}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn duplicate_recipe() {
|
fn duplicate_recipe() {
|
||||||
let text = "a:\nb:\na:";
|
let text = "a:\nb:\na:";
|
||||||
parse_error(text, CompileError {
|
parse_error(text, CompilationError {
|
||||||
text: text,
|
text: text,
|
||||||
index: 6,
|
index: 6,
|
||||||
line: 2,
|
line: 2,
|
||||||
column: 0,
|
column: 0,
|
||||||
width: Some(1),
|
width: Some(1),
|
||||||
kind: ErrorKind::DuplicateRecipe{recipe: "a", first: 0}
|
kind: CompilationErrorKind::DuplicateRecipe{recipe: "a", first: 0}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn circular_recipe_dependency() {
|
fn circular_recipe_dependency() {
|
||||||
let text = "a: b\nb: a";
|
let text = "a: b\nb: a";
|
||||||
parse_error(text, CompileError {
|
parse_error(text, CompilationError {
|
||||||
text: text,
|
text: text,
|
||||||
index: 8,
|
index: 8,
|
||||||
line: 1,
|
line: 1,
|
||||||
column: 3,
|
column: 3,
|
||||||
width: Some(1),
|
width: Some(1),
|
||||||
kind: ErrorKind::CircularRecipeDependency{recipe: "b", circle: vec!["a", "b", "a"]}
|
kind: CompilationErrorKind::CircularRecipeDependency{recipe: "b", circle: vec!["a", "b", "a"]}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn circular_variable_dependency() {
|
fn circular_variable_dependency() {
|
||||||
let text = "a = b\nb = a";
|
let text = "a = b\nb = a";
|
||||||
parse_error(text, CompileError {
|
parse_error(text, CompilationError {
|
||||||
text: text,
|
text: text,
|
||||||
index: 0,
|
index: 0,
|
||||||
line: 0,
|
line: 0,
|
||||||
column: 0,
|
column: 0,
|
||||||
width: Some(1),
|
width: Some(1),
|
||||||
kind: ErrorKind::CircularVariableDependency{variable: "a", circle: vec!["a", "b", "a"]}
|
kind: CompilationErrorKind::CircularVariableDependency{variable: "a", circle: vec!["a", "b", "a"]}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn duplicate_variable() {
|
fn duplicate_variable() {
|
||||||
let text = "a = \"0\"\na = \"0\"";
|
let text = "a = \"0\"\na = \"0\"";
|
||||||
parse_error(text, CompileError {
|
parse_error(text, CompilationError {
|
||||||
text: text,
|
text: text,
|
||||||
index: 8,
|
index: 8,
|
||||||
line: 1,
|
line: 1,
|
||||||
column: 0,
|
column: 0,
|
||||||
width: Some(1),
|
width: Some(1),
|
||||||
kind: ErrorKind::DuplicateVariable{variable: "a"}
|
kind: CompilationErrorKind::DuplicateVariable{variable: "a"}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn unterminated_string() {
|
fn unterminated_string() {
|
||||||
let text = r#"a = ""#;
|
let text = r#"a = ""#;
|
||||||
parse_error(text, CompileError {
|
parse_error(text, CompilationError {
|
||||||
text: text,
|
text: text,
|
||||||
index: 3,
|
index: 3,
|
||||||
line: 0,
|
line: 0,
|
||||||
column: 3,
|
column: 3,
|
||||||
width: None,
|
width: None,
|
||||||
kind: ErrorKind::UnterminatedString,
|
kind: CompilationErrorKind::UnterminatedString,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn unterminated_string_with_escapes() {
|
fn unterminated_string_with_escapes() {
|
||||||
let text = r#"a = "\n\t\r\"\\"#;
|
let text = r#"a = "\n\t\r\"\\"#;
|
||||||
parse_error(text, CompileError {
|
parse_error(text, CompilationError {
|
||||||
text: text,
|
text: text,
|
||||||
index: 3,
|
index: 3,
|
||||||
line: 0,
|
line: 0,
|
||||||
column: 3,
|
column: 3,
|
||||||
width: None,
|
width: None,
|
||||||
kind: ErrorKind::UnterminatedString,
|
kind: CompilationErrorKind::UnterminatedString,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn unterminated_raw_string() {
|
fn unterminated_raw_string() {
|
||||||
let text = "r a='asdf";
|
let text = "r a='asdf";
|
||||||
parse_error(text, CompileError {
|
parse_error(text, CompilationError {
|
||||||
text: text,
|
text: text,
|
||||||
index: 4,
|
index: 4,
|
||||||
line: 0,
|
line: 0,
|
||||||
column: 4,
|
column: 4,
|
||||||
width: None,
|
width: None,
|
||||||
kind: ErrorKind::UnterminatedString,
|
kind: CompilationErrorKind::UnterminatedString,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -703,52 +703,52 @@ fn parameters() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn self_recipe_dependency() {
|
fn self_recipe_dependency() {
|
||||||
let text = "a: a";
|
let text = "a: a";
|
||||||
parse_error(text, CompileError {
|
parse_error(text, CompilationError {
|
||||||
text: text,
|
text: text,
|
||||||
index: 3,
|
index: 3,
|
||||||
line: 0,
|
line: 0,
|
||||||
column: 3,
|
column: 3,
|
||||||
width: Some(1),
|
width: Some(1),
|
||||||
kind: ErrorKind::CircularRecipeDependency{recipe: "a", circle: vec!["a", "a"]}
|
kind: CompilationErrorKind::CircularRecipeDependency{recipe: "a", circle: vec!["a", "a"]}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn self_variable_dependency() {
|
fn self_variable_dependency() {
|
||||||
let text = "a = a";
|
let text = "a = a";
|
||||||
parse_error(text, CompileError {
|
parse_error(text, CompilationError {
|
||||||
text: text,
|
text: text,
|
||||||
index: 0,
|
index: 0,
|
||||||
line: 0,
|
line: 0,
|
||||||
column: 0,
|
column: 0,
|
||||||
width: Some(1),
|
width: Some(1),
|
||||||
kind: ErrorKind::CircularVariableDependency{variable: "a", circle: vec!["a", "a"]}
|
kind: CompilationErrorKind::CircularVariableDependency{variable: "a", circle: vec!["a", "a"]}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn unknown_dependency() {
|
fn unknown_dependency() {
|
||||||
let text = "a: b";
|
let text = "a: b";
|
||||||
parse_error(text, CompileError {
|
parse_error(text, CompilationError {
|
||||||
text: text,
|
text: text,
|
||||||
index: 3,
|
index: 3,
|
||||||
line: 0,
|
line: 0,
|
||||||
column: 3,
|
column: 3,
|
||||||
width: Some(1),
|
width: Some(1),
|
||||||
kind: ErrorKind::UnknownDependency{recipe: "a", unknown: "b"}
|
kind: CompilationErrorKind::UnknownDependency{recipe: "a", unknown: "b"}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn mixed_leading_whitespace() {
|
fn mixed_leading_whitespace() {
|
||||||
let text = "a:\n\t echo hello";
|
let text = "a:\n\t echo hello";
|
||||||
parse_error(text, CompileError {
|
parse_error(text, CompilationError {
|
||||||
text: text,
|
text: text,
|
||||||
index: 3,
|
index: 3,
|
||||||
line: 1,
|
line: 1,
|
||||||
column: 0,
|
column: 0,
|
||||||
width: None,
|
width: None,
|
||||||
kind: ErrorKind::MixedLeadingWhitespace{whitespace: "\t "}
|
kind: CompilationErrorKind::MixedLeadingWhitespace{whitespace: "\t "}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -780,7 +780,7 @@ fn range() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn unknown_recipes() {
|
fn unknown_recipes() {
|
||||||
match parse_success("a:\nb:\nc:").run(&["a", "x", "y", "z"], &Default::default()).unwrap_err() {
|
match parse_success("a:\nb:\nc:").run(&["a", "x", "y", "z"], &Default::default()).unwrap_err() {
|
||||||
RunError::UnknownRecipes{recipes, suggestion} => {
|
RuntimeError::UnknownRecipes{recipes, suggestion} => {
|
||||||
assert_eq!(recipes, &["x", "y", "z"]);
|
assert_eq!(recipes, &["x", "y", "z"]);
|
||||||
assert_eq!(suggestion, None);
|
assert_eq!(suggestion, None);
|
||||||
}
|
}
|
||||||
@ -791,13 +791,13 @@ fn unknown_recipes() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn extra_whitespace() {
|
fn extra_whitespace() {
|
||||||
let text = "a:\n blah\n blarg";
|
let text = "a:\n blah\n blarg";
|
||||||
parse_error(text, CompileError {
|
parse_error(text, CompilationError {
|
||||||
text: text,
|
text: text,
|
||||||
index: 10,
|
index: 10,
|
||||||
line: 2,
|
line: 2,
|
||||||
column: 1,
|
column: 1,
|
||||||
width: Some(6),
|
width: Some(6),
|
||||||
kind: ErrorKind::ExtraLeadingWhitespace
|
kind: CompilationErrorKind::ExtraLeadingWhitespace
|
||||||
});
|
});
|
||||||
|
|
||||||
// extra leading whitespace is okay in a shebang recipe
|
// extra leading whitespace is okay in a shebang recipe
|
||||||
@ -807,78 +807,78 @@ fn extra_whitespace() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn interpolation_outside_of_recipe() {
|
fn interpolation_outside_of_recipe() {
|
||||||
let text = "{{";
|
let text = "{{";
|
||||||
parse_error(text, CompileError {
|
parse_error(text, CompilationError {
|
||||||
text: text,
|
text: text,
|
||||||
index: 0,
|
index: 0,
|
||||||
line: 0,
|
line: 0,
|
||||||
column: 0,
|
column: 0,
|
||||||
width: Some(2),
|
width: Some(2),
|
||||||
kind: ErrorKind::UnexpectedToken{expected: vec![Name, At], found: InterpolationStart},
|
kind: CompilationErrorKind::UnexpectedToken{expected: vec![Name, At], found: InterpolationStart},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn unclosed_interpolation_delimiter() {
|
fn unclosed_interpolation_delimiter() {
|
||||||
let text = "a:\n echo {{ foo";
|
let text = "a:\n echo {{ foo";
|
||||||
parse_error(text, CompileError {
|
parse_error(text, CompilationError {
|
||||||
text: text,
|
text: text,
|
||||||
index: 15,
|
index: 15,
|
||||||
line: 1,
|
line: 1,
|
||||||
column: 12,
|
column: 12,
|
||||||
width: Some(0),
|
width: Some(0),
|
||||||
kind: ErrorKind::UnexpectedToken{expected: vec![Plus, Eol, InterpolationEnd], found: Dedent},
|
kind: CompilationErrorKind::UnexpectedToken{expected: vec![Plus, Eol, InterpolationEnd], found: Dedent},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn unknown_expression_variable() {
|
fn unknown_expression_variable() {
|
||||||
let text = "x = yy";
|
let text = "x = yy";
|
||||||
parse_error(text, CompileError {
|
parse_error(text, CompilationError {
|
||||||
text: text,
|
text: text,
|
||||||
index: 4,
|
index: 4,
|
||||||
line: 0,
|
line: 0,
|
||||||
column: 4,
|
column: 4,
|
||||||
width: Some(2),
|
width: Some(2),
|
||||||
kind: ErrorKind::UndefinedVariable{variable: "yy"},
|
kind: CompilationErrorKind::UndefinedVariable{variable: "yy"},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn unknown_interpolation_variable() {
|
fn unknown_interpolation_variable() {
|
||||||
let text = "x:\n {{ hello}}";
|
let text = "x:\n {{ hello}}";
|
||||||
parse_error(text, CompileError {
|
parse_error(text, CompilationError {
|
||||||
text: text,
|
text: text,
|
||||||
index: 9,
|
index: 9,
|
||||||
line: 1,
|
line: 1,
|
||||||
column: 6,
|
column: 6,
|
||||||
width: Some(5),
|
width: Some(5),
|
||||||
kind: ErrorKind::UndefinedVariable{variable: "hello"},
|
kind: CompilationErrorKind::UndefinedVariable{variable: "hello"},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn unknown_second_interpolation_variable() {
|
fn unknown_second_interpolation_variable() {
|
||||||
let text = "wtf=\"x\"\nx:\n echo\n foo {{wtf}} {{ lol }}";
|
let text = "wtf=\"x\"\nx:\n echo\n foo {{wtf}} {{ lol }}";
|
||||||
parse_error(text, CompileError {
|
parse_error(text, CompilationError {
|
||||||
text: text,
|
text: text,
|
||||||
index: 33,
|
index: 33,
|
||||||
line: 3,
|
line: 3,
|
||||||
column: 16,
|
column: 16,
|
||||||
width: Some(3),
|
width: Some(3),
|
||||||
kind: ErrorKind::UndefinedVariable{variable: "lol"},
|
kind: CompilationErrorKind::UndefinedVariable{variable: "lol"},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn plus_following_parameter() {
|
fn plus_following_parameter() {
|
||||||
let text = "a b c+:";
|
let text = "a b c+:";
|
||||||
parse_error(text, CompileError {
|
parse_error(text, CompilationError {
|
||||||
text: text,
|
text: text,
|
||||||
index: 5,
|
index: 5,
|
||||||
line: 0,
|
line: 0,
|
||||||
column: 5,
|
column: 5,
|
||||||
width: Some(1),
|
width: Some(1),
|
||||||
kind: ErrorKind::UnexpectedToken{expected: vec![Name], found: Plus},
|
kind: CompilationErrorKind::UnexpectedToken{expected: vec![Name], found: Plus},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -919,7 +919,7 @@ a:
|
|||||||
";
|
";
|
||||||
|
|
||||||
match parse_success(text).run(&["a"], &Default::default()).unwrap_err() {
|
match parse_success(text).run(&["a"], &Default::default()).unwrap_err() {
|
||||||
RunError::Code{recipe, line_number, code} => {
|
RuntimeError::Code{recipe, line_number, code} => {
|
||||||
assert_eq!(recipe, "a");
|
assert_eq!(recipe, "a");
|
||||||
assert_eq!(code, 200);
|
assert_eq!(code, 200);
|
||||||
assert_eq!(line_number, None);
|
assert_eq!(line_number, None);
|
||||||
@ -932,7 +932,7 @@ a:
|
|||||||
fn code_error() {
|
fn code_error() {
|
||||||
match parse_success("fail:\n @exit 100")
|
match parse_success("fail:\n @exit 100")
|
||||||
.run(&["fail"], &Default::default()).unwrap_err() {
|
.run(&["fail"], &Default::default()).unwrap_err() {
|
||||||
RunError::Code{recipe, line_number, code} => {
|
RuntimeError::Code{recipe, line_number, code} => {
|
||||||
assert_eq!(recipe, "fail");
|
assert_eq!(recipe, "fail");
|
||||||
assert_eq!(code, 100);
|
assert_eq!(code, 100);
|
||||||
assert_eq!(line_number, Some(2));
|
assert_eq!(line_number, Some(2));
|
||||||
@ -948,7 +948,7 @@ a return code:
|
|||||||
@x() { {{return}} {{code + "0"}}; }; x"#;
|
@x() { {{return}} {{code + "0"}}; }; x"#;
|
||||||
|
|
||||||
match parse_success(text).run(&["a", "return", "15"], &Default::default()).unwrap_err() {
|
match parse_success(text).run(&["a", "return", "15"], &Default::default()).unwrap_err() {
|
||||||
RunError::Code{recipe, line_number, code} => {
|
RuntimeError::Code{recipe, line_number, code} => {
|
||||||
assert_eq!(recipe, "a");
|
assert_eq!(recipe, "a");
|
||||||
assert_eq!(code, 150);
|
assert_eq!(code, 150);
|
||||||
assert_eq!(line_number, Some(3));
|
assert_eq!(line_number, Some(3));
|
||||||
@ -960,7 +960,7 @@ a return code:
|
|||||||
#[test]
|
#[test]
|
||||||
fn missing_some_arguments() {
|
fn missing_some_arguments() {
|
||||||
match parse_success("a b c d:").run(&["a", "b", "c"], &Default::default()).unwrap_err() {
|
match parse_success("a b c d:").run(&["a", "b", "c"], &Default::default()).unwrap_err() {
|
||||||
RunError::ArgumentCountMismatch{recipe, found, min, max} => {
|
RuntimeError::ArgumentCountMismatch{recipe, found, min, max} => {
|
||||||
assert_eq!(recipe, "a");
|
assert_eq!(recipe, "a");
|
||||||
assert_eq!(found, 2);
|
assert_eq!(found, 2);
|
||||||
assert_eq!(min, 3);
|
assert_eq!(min, 3);
|
||||||
@ -973,7 +973,7 @@ fn missing_some_arguments() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn missing_some_arguments_variadic() {
|
fn missing_some_arguments_variadic() {
|
||||||
match parse_success("a b c +d:").run(&["a", "B", "C"], &Default::default()).unwrap_err() {
|
match parse_success("a b c +d:").run(&["a", "B", "C"], &Default::default()).unwrap_err() {
|
||||||
RunError::ArgumentCountMismatch{recipe, found, min, max} => {
|
RuntimeError::ArgumentCountMismatch{recipe, found, min, max} => {
|
||||||
assert_eq!(recipe, "a");
|
assert_eq!(recipe, "a");
|
||||||
assert_eq!(found, 2);
|
assert_eq!(found, 2);
|
||||||
assert_eq!(min, 3);
|
assert_eq!(min, 3);
|
||||||
@ -987,7 +987,7 @@ fn missing_some_arguments_variadic() {
|
|||||||
fn missing_all_arguments() {
|
fn missing_all_arguments() {
|
||||||
match parse_success("a b c d:\n echo {{b}}{{c}}{{d}}")
|
match parse_success("a b c d:\n echo {{b}}{{c}}{{d}}")
|
||||||
.run(&["a"], &Default::default()).unwrap_err() {
|
.run(&["a"], &Default::default()).unwrap_err() {
|
||||||
RunError::ArgumentCountMismatch{recipe, found, min, max} => {
|
RuntimeError::ArgumentCountMismatch{recipe, found, min, max} => {
|
||||||
assert_eq!(recipe, "a");
|
assert_eq!(recipe, "a");
|
||||||
assert_eq!(found, 0);
|
assert_eq!(found, 0);
|
||||||
assert_eq!(min, 3);
|
assert_eq!(min, 3);
|
||||||
@ -1000,7 +1000,7 @@ fn missing_all_arguments() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn missing_some_defaults() {
|
fn missing_some_defaults() {
|
||||||
match parse_success("a b c d='hello':").run(&["a", "b"], &Default::default()).unwrap_err() {
|
match parse_success("a b c d='hello':").run(&["a", "b"], &Default::default()).unwrap_err() {
|
||||||
RunError::ArgumentCountMismatch{recipe, found, min, max} => {
|
RuntimeError::ArgumentCountMismatch{recipe, found, min, max} => {
|
||||||
assert_eq!(recipe, "a");
|
assert_eq!(recipe, "a");
|
||||||
assert_eq!(found, 1);
|
assert_eq!(found, 1);
|
||||||
assert_eq!(min, 2);
|
assert_eq!(min, 2);
|
||||||
@ -1013,7 +1013,7 @@ fn missing_some_defaults() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn missing_all_defaults() {
|
fn missing_all_defaults() {
|
||||||
match parse_success("a b c='r' d='h':").run(&["a"], &Default::default()).unwrap_err() {
|
match parse_success("a b c='r' d='h':").run(&["a"], &Default::default()).unwrap_err() {
|
||||||
RunError::ArgumentCountMismatch{recipe, found, min, max} => {
|
RuntimeError::ArgumentCountMismatch{recipe, found, min, max} => {
|
||||||
assert_eq!(recipe, "a");
|
assert_eq!(recipe, "a");
|
||||||
assert_eq!(found, 0);
|
assert_eq!(found, 0);
|
||||||
assert_eq!(min, 1);
|
assert_eq!(min, 1);
|
||||||
@ -1027,7 +1027,7 @@ fn missing_all_defaults() {
|
|||||||
fn backtick_code() {
|
fn backtick_code() {
|
||||||
match parse_success("a:\n echo {{`f() { return 100; }; f`}}")
|
match parse_success("a:\n echo {{`f() { return 100; }; f`}}")
|
||||||
.run(&["a"], &Default::default()).unwrap_err() {
|
.run(&["a"], &Default::default()).unwrap_err() {
|
||||||
RunError::Backtick{token, output_error: OutputError::Code(code)} => {
|
RuntimeError::Backtick{token, output_error: OutputError::Code(code)} => {
|
||||||
assert_eq!(code, 100);
|
assert_eq!(code, 100);
|
||||||
assert_eq!(token.lexeme, "`f() { return 100; }; f`");
|
assert_eq!(token.lexeme, "`f() { return 100; }; f`");
|
||||||
},
|
},
|
||||||
@ -1042,7 +1042,7 @@ fn unknown_overrides() {
|
|||||||
options.overrides.insert("baz", "bob");
|
options.overrides.insert("baz", "bob");
|
||||||
match parse_success("a:\n echo {{`f() { return 100; }; f`}}")
|
match parse_success("a:\n echo {{`f() { return 100; }; f`}}")
|
||||||
.run(&["a"], &options).unwrap_err() {
|
.run(&["a"], &options).unwrap_err() {
|
||||||
RunError::UnknownOverrides{overrides} => {
|
RuntimeError::UnknownOverrides{overrides} => {
|
||||||
assert_eq!(overrides, &["baz", "foo"]);
|
assert_eq!(overrides, &["baz", "foo"]);
|
||||||
},
|
},
|
||||||
other => panic!("expected a code run error, but got: {}", other),
|
other => panic!("expected a code run error, but got: {}", other),
|
||||||
@ -1065,7 +1065,7 @@ recipe:
|
|||||||
};
|
};
|
||||||
|
|
||||||
match parse_success(text).run(&["recipe"], &options).unwrap_err() {
|
match parse_success(text).run(&["recipe"], &options).unwrap_err() {
|
||||||
RunError::Backtick{token, output_error: OutputError::Code(_)} => {
|
RuntimeError::Backtick{token, output_error: OutputError::Code(_)} => {
|
||||||
assert_eq!(token.lexeme, "`echo $exported_variable`");
|
assert_eq!(token.lexeme, "`echo $exported_variable`");
|
||||||
},
|
},
|
||||||
other => panic!("expected a backtick code errror, but got: {}", other),
|
other => panic!("expected a backtick code errror, but got: {}", other),
|
||||||
@ -1090,7 +1090,7 @@ wut:
|
|||||||
};
|
};
|
||||||
|
|
||||||
match parse_success(text).run(&["wut"], &options).unwrap_err() {
|
match parse_success(text).run(&["wut"], &options).unwrap_err() {
|
||||||
RunError::Code{code: _, line_number, recipe} => {
|
RuntimeError::Code{code: _, line_number, recipe} => {
|
||||||
assert_eq!(recipe, "wut");
|
assert_eq!(recipe, "wut");
|
||||||
assert_eq!(line_number, Some(8));
|
assert_eq!(line_number, Some(8));
|
||||||
},
|
},
|
||||||
|
Loading…
Reference in New Issue
Block a user