More progress on integrating ariadne's error reporting
This commit is contained in:
@@ -38,19 +38,11 @@ pub fn alias(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<
|
|||||||
} else {
|
} else {
|
||||||
for (arg, span) in argv {
|
for (arg, span) in argv {
|
||||||
if arg == "command" || arg == "builtin" {
|
if arg == "command" || arg == "builtin" {
|
||||||
return Err(ShErr::full(
|
return Err(ShErr::at(ShErrKind::ExecFail, span, format!("alias: Cannot assign alias to reserved name '{arg}'")));
|
||||||
ShErrKind::ExecFail,
|
|
||||||
format!("alias: Cannot assign alias to reserved name '{arg}'"),
|
|
||||||
span,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let Some((name, body)) = arg.split_once('=') else {
|
let Some((name, body)) = arg.split_once('=') else {
|
||||||
return Err(ShErr::full(
|
return Err(ShErr::at(ShErrKind::SyntaxErr, span, "alias: Expected an assignment in alias args"));
|
||||||
ShErrKind::SyntaxErr,
|
|
||||||
"alias: Expected an assignment in alias args",
|
|
||||||
span,
|
|
||||||
));
|
|
||||||
};
|
};
|
||||||
write_logic(|l| l.insert_alias(name, body));
|
write_logic(|l| l.insert_alias(name, body));
|
||||||
}
|
}
|
||||||
@@ -88,11 +80,7 @@ pub fn unalias(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResul
|
|||||||
} else {
|
} else {
|
||||||
for (arg, span) in argv {
|
for (arg, span) in argv {
|
||||||
if read_logic(|l| l.get_alias(&arg)).is_none() {
|
if read_logic(|l| l.get_alias(&arg)).is_none() {
|
||||||
return Err(ShErr::full(
|
return Err(ShErr::at(ShErrKind::SyntaxErr, span, format!("unalias: alias '{arg}' not found")));
|
||||||
ShErrKind::SyntaxErr,
|
|
||||||
format!("unalias: alias '{arg}' not found"),
|
|
||||||
span,
|
|
||||||
));
|
|
||||||
};
|
};
|
||||||
write_logic(|l| l.remove_alias(&arg))
|
write_logic(|l| l.remove_alias(&arg))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -84,6 +84,7 @@ fn arr_pop_inner(node: Node, io_stack: &mut IoStack, job: &mut JobBldr, end: End
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn arr_push_inner(node: Node, io_stack: &mut IoStack, job: &mut JobBldr, end: End) -> ShResult<()> {
|
fn arr_push_inner(node: Node, io_stack: &mut IoStack, job: &mut JobBldr, end: End) -> ShResult<()> {
|
||||||
|
let blame = node.get_span().clone();
|
||||||
let NdRule::Command {
|
let NdRule::Command {
|
||||||
assignments: _,
|
assignments: _,
|
||||||
argv,
|
argv,
|
||||||
@@ -99,7 +100,7 @@ fn arr_push_inner(node: Node, io_stack: &mut IoStack, job: &mut JobBldr, end: En
|
|||||||
|
|
||||||
let mut argv = argv.into_iter();
|
let mut argv = argv.into_iter();
|
||||||
let Some((name, _)) = argv.next() else {
|
let Some((name, _)) = argv.next() else {
|
||||||
return Err(ShErr::simple(ShErrKind::ExecFail, "push: missing array name".to_string()));
|
return Err(ShErr::at(ShErrKind::ExecFail, blame, "push: missing array name".to_string()));
|
||||||
};
|
};
|
||||||
|
|
||||||
for (val, _) in argv {
|
for (val, _) in argv {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use ariadne::{Fmt, Label, Span};
|
use ariadne::Fmt;
|
||||||
|
use yansi::Color;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
jobs::JobBldr,
|
jobs::JobBldr,
|
||||||
@@ -12,7 +13,6 @@ use super::setup_builtin;
|
|||||||
|
|
||||||
pub fn cd(node: Node, job: &mut JobBldr) -> ShResult<()> {
|
pub fn cd(node: Node, job: &mut JobBldr) -> ShResult<()> {
|
||||||
let span = node.get_span();
|
let span = node.get_span();
|
||||||
let src = span.source();
|
|
||||||
let NdRule::Command {
|
let NdRule::Command {
|
||||||
assignments: _,
|
assignments: _,
|
||||||
argv,
|
argv,
|
||||||
@@ -32,39 +32,28 @@ pub fn cd(node: Node, job: &mut JobBldr) -> ShResult<()> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if !new_dir.exists() {
|
if !new_dir.exists() {
|
||||||
let color = next_color();
|
|
||||||
let mut err = ShErr::new(
|
let mut err = ShErr::new(
|
||||||
ShErrKind::ExecFail,
|
ShErrKind::ExecFail,
|
||||||
span.clone(),
|
span.clone(),
|
||||||
).with_label(src.clone(), Label::new(cd_span.clone()).with_color(color).with_message("Failed to change directory"));
|
).labeled(cd_span.clone(), "Failed to change directory");
|
||||||
if let Some(span) = arg_span {
|
if let Some(span) = arg_span {
|
||||||
let color = next_color();
|
err = err.labeled(span, format!("No such file or directory '{}'", new_dir.display().fg(next_color())));
|
||||||
err = err.with_label(src.clone(), Label::new(span).with_color(color).with_message(format!("No such file or directory '{}'", new_dir.display().fg(color))));
|
|
||||||
}
|
}
|
||||||
return Err(err);
|
return Err(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
if !new_dir.is_dir() {
|
if !new_dir.is_dir() {
|
||||||
return Err(ShErr::full(
|
return Err(ShErr::new(ShErrKind::ExecFail, span.clone())
|
||||||
ShErrKind::ExecFail,
|
.labeled(cd_span.clone(), format!("cd: Not a directory '{}'", new_dir.display().fg(next_color()))));
|
||||||
format!("cd: Not a directory '{}'", new_dir.display()),
|
|
||||||
span,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Err(e) = env::set_current_dir(new_dir) {
|
if let Err(e) = env::set_current_dir(new_dir) {
|
||||||
return Err(ShErr::full(
|
return Err(ShErr::new(ShErrKind::ExecFail, span.clone())
|
||||||
ShErrKind::ExecFail,
|
.labeled(cd_span.clone(), format!("cd: Failed to change directory: '{}'", e.fg(Color::Red))));
|
||||||
format!("cd: Failed to change directory: {}", e),
|
|
||||||
span,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
let new_dir = env::current_dir().map_err(|e| {
|
let new_dir = env::current_dir().map_err(|e| {
|
||||||
ShErr::full(
|
ShErr::new(ShErrKind::ExecFail, span.clone())
|
||||||
ShErrKind::ExecFail,
|
.labeled(cd_span.clone(), format!("cd: Failed to get current directory: '{}'", e.fg(Color::Red)))
|
||||||
format!("cd: Failed to get current directory: {}", e),
|
|
||||||
span,
|
|
||||||
)
|
|
||||||
})?;
|
})?;
|
||||||
unsafe { env::set_var("PWD", new_dir) };
|
unsafe { env::set_var("PWD", new_dir) };
|
||||||
|
|
||||||
|
|||||||
@@ -206,11 +206,7 @@ pub fn complete_builtin(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -
|
|||||||
|
|
||||||
if argv.is_empty() {
|
if argv.is_empty() {
|
||||||
state::set_status(1);
|
state::set_status(1);
|
||||||
return Err(ShErr::full(
|
return Err(ShErr::at(ShErrKind::ExecFail, blame, "complete: no command specified"));
|
||||||
ShErrKind::ExecFail,
|
|
||||||
"complete: no command specified",
|
|
||||||
blame,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let comp_spec = BashCompSpec::from_comp_opts(comp_opts).with_source(src);
|
let comp_spec = BashCompSpec::from_comp_opts(comp_opts).with_source(src);
|
||||||
@@ -285,11 +281,8 @@ pub fn get_comp_opts(opts: Vec<Opt>) -> ShResult<CompOpts> {
|
|||||||
"dirnames" => comp_opts.opt_flags |= CompOptFlags::DIRNAMES,
|
"dirnames" => comp_opts.opt_flags |= CompOptFlags::DIRNAMES,
|
||||||
"space" => comp_opts.opt_flags |= CompOptFlags::SPACE,
|
"space" => comp_opts.opt_flags |= CompOptFlags::SPACE,
|
||||||
_ => {
|
_ => {
|
||||||
return Err(ShErr::full(
|
let span: crate::parse::lex::Span = Default::default();
|
||||||
ShErrKind::InvalidOpt,
|
return Err(ShErr::at(ShErrKind::InvalidOpt, span, format!("complete: invalid option: {}", opt_flag)));
|
||||||
format!("complete: invalid option: {}", opt_flag),
|
|
||||||
Default::default(),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -47,26 +47,18 @@ fn print_dirs() -> ShResult<()> {
|
|||||||
|
|
||||||
fn change_directory(target: &PathBuf, blame: Span) -> ShResult<()> {
|
fn change_directory(target: &PathBuf, blame: Span) -> ShResult<()> {
|
||||||
if !target.is_dir() {
|
if !target.is_dir() {
|
||||||
return Err(ShErr::full(
|
return Err(
|
||||||
ShErrKind::ExecFail,
|
ShErr::at(ShErrKind::ExecFail, blame, format!("not a directory: {}", target.display()))
|
||||||
format!("not a directory: {}", target.display()),
|
);
|
||||||
blame,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Err(e) = env::set_current_dir(target) {
|
if let Err(e) = env::set_current_dir(target) {
|
||||||
return Err(ShErr::full(
|
return Err(
|
||||||
ShErrKind::ExecFail,
|
ShErr::at(ShErrKind::ExecFail, blame, format!("Failed to change directory: {}", e))
|
||||||
format!("Failed to change directory: {}", e),
|
);
|
||||||
blame,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
let new_dir = env::current_dir().map_err(|e| {
|
let new_dir = env::current_dir().map_err(|e| {
|
||||||
ShErr::full(
|
ShErr::at(ShErrKind::ExecFail, blame, format!("Failed to get current directory: {}", e))
|
||||||
ShErrKind::ExecFail,
|
|
||||||
format!("Failed to get current directory: {}", e),
|
|
||||||
blame,
|
|
||||||
)
|
|
||||||
})?;
|
})?;
|
||||||
unsafe { env::set_var("PWD", new_dir) };
|
unsafe { env::set_var("PWD", new_dir) };
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -82,32 +74,24 @@ fn parse_stack_idx(arg: &str, blame: Span, cmd: &str) -> ShResult<StackIdx> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if digits.is_empty() {
|
if digits.is_empty() {
|
||||||
return Err(ShErr::full(
|
return Err(
|
||||||
ShErrKind::ExecFail,
|
ShErr::at(ShErrKind::ExecFail, blame, format!(
|
||||||
format!(
|
|
||||||
"{cmd}: missing index after '{}'",
|
"{cmd}: missing index after '{}'",
|
||||||
if from_top { "+" } else { "-" }
|
if from_top { "+" } else { "-" }
|
||||||
),
|
))
|
||||||
blame,
|
);
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for ch in digits.chars() {
|
for ch in digits.chars() {
|
||||||
if !ch.is_ascii_digit() {
|
if !ch.is_ascii_digit() {
|
||||||
return Err(ShErr::full(
|
return Err(
|
||||||
ShErrKind::ExecFail,
|
ShErr::at(ShErrKind::ExecFail, blame, format!("{cmd}: invalid argument: {arg}"))
|
||||||
format!("{cmd}: invalid argument: {arg}"),
|
);
|
||||||
blame,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let n = digits.parse::<usize>().map_err(|e| {
|
let n = digits.parse::<usize>().map_err(|e| {
|
||||||
ShErr::full(
|
ShErr::at(ShErrKind::ExecFail, blame, format!("{cmd}: invalid index: {e}"))
|
||||||
ShErrKind::ExecFail,
|
|
||||||
format!("{cmd}: invalid index: {e}"),
|
|
||||||
blame,
|
|
||||||
)
|
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
if from_top {
|
if from_top {
|
||||||
@@ -142,26 +126,20 @@ pub fn pushd(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<
|
|||||||
} else if arg == "-n" {
|
} else if arg == "-n" {
|
||||||
no_cd = true;
|
no_cd = true;
|
||||||
} else if arg.starts_with('-') {
|
} else if arg.starts_with('-') {
|
||||||
return Err(ShErr::full(
|
return Err(
|
||||||
ShErrKind::ExecFail,
|
ShErr::at(ShErrKind::ExecFail, blame, format!("pushd: invalid option: {arg}"))
|
||||||
format!("pushd: invalid option: {arg}"),
|
);
|
||||||
blame.clone(),
|
|
||||||
));
|
|
||||||
} else {
|
} else {
|
||||||
if dir.is_some() {
|
if dir.is_some() {
|
||||||
return Err(ShErr::full(
|
return Err(
|
||||||
ShErrKind::ExecFail,
|
ShErr::at(ShErrKind::ExecFail, blame, "pushd: too many arguments")
|
||||||
"pushd: too many arguments".to_string(),
|
);
|
||||||
blame.clone(),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
let target = PathBuf::from(&arg);
|
let target = PathBuf::from(&arg);
|
||||||
if !target.is_dir() {
|
if !target.is_dir() {
|
||||||
return Err(ShErr::full(
|
return Err(
|
||||||
ShErrKind::ExecFail,
|
ShErr::at(ShErrKind::ExecFail, blame, format!("pushd: not a directory: {arg}"))
|
||||||
format!("pushd: not a directory: {arg}"),
|
);
|
||||||
blame.clone(),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
dir = Some(target);
|
dir = Some(target);
|
||||||
}
|
}
|
||||||
@@ -228,11 +206,9 @@ pub fn popd(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<(
|
|||||||
} else if arg == "-n" {
|
} else if arg == "-n" {
|
||||||
no_cd = true;
|
no_cd = true;
|
||||||
} else if arg.starts_with('-') {
|
} else if arg.starts_with('-') {
|
||||||
return Err(ShErr::full(
|
return Err(
|
||||||
ShErrKind::ExecFail,
|
ShErr::at(ShErrKind::ExecFail, blame, format!("popd: invalid option: {arg}"))
|
||||||
format!("popd: invalid option: {arg}"),
|
);
|
||||||
blame.clone(),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -245,11 +221,9 @@ pub fn popd(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<(
|
|||||||
if let Some(dir) = dir {
|
if let Some(dir) = dir {
|
||||||
change_directory(&dir, blame.clone())?;
|
change_directory(&dir, blame.clone())?;
|
||||||
} else {
|
} else {
|
||||||
return Err(ShErr::full(
|
return Err(
|
||||||
ShErrKind::ExecFail,
|
ShErr::at(ShErrKind::ExecFail, blame, "popd: directory stack empty")
|
||||||
"popd: directory stack empty".to_string(),
|
);
|
||||||
blame.clone(),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -259,11 +233,9 @@ pub fn popd(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<(
|
|||||||
let dirs = m.dirs_mut();
|
let dirs = m.dirs_mut();
|
||||||
let idx = n - 1;
|
let idx = n - 1;
|
||||||
if idx >= dirs.len() {
|
if idx >= dirs.len() {
|
||||||
return Err(ShErr::full(
|
return Err(
|
||||||
ShErrKind::ExecFail,
|
ShErr::at(ShErrKind::ExecFail, blame.clone(), format!("popd: directory index out of range: +{n}"))
|
||||||
format!("popd: directory index out of range: +{n}"),
|
);
|
||||||
blame.clone(),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
dirs.remove(idx);
|
dirs.remove(idx);
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -273,11 +245,7 @@ pub fn popd(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<(
|
|||||||
write_meta(|m| -> ShResult<()> {
|
write_meta(|m| -> ShResult<()> {
|
||||||
let dirs = m.dirs_mut();
|
let dirs = m.dirs_mut();
|
||||||
let actual = dirs.len().checked_sub(n + 1).ok_or_else(|| {
|
let actual = dirs.len().checked_sub(n + 1).ok_or_else(|| {
|
||||||
ShErr::full(
|
ShErr::at(ShErrKind::ExecFail, blame.clone(), format!("popd: directory index out of range: -{n}"))
|
||||||
ShErrKind::ExecFail,
|
|
||||||
format!("popd: directory index out of range: -{n}"),
|
|
||||||
blame.clone(),
|
|
||||||
)
|
|
||||||
})?;
|
})?;
|
||||||
dirs.remove(actual);
|
dirs.remove(actual);
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -297,11 +265,9 @@ pub fn popd(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<(
|
|||||||
change_directory(&dir, blame.clone())?;
|
change_directory(&dir, blame.clone())?;
|
||||||
print_dirs()?;
|
print_dirs()?;
|
||||||
} else {
|
} else {
|
||||||
return Err(ShErr::full(
|
return Err(
|
||||||
ShErrKind::ExecFail,
|
ShErr::at(ShErrKind::ExecFail, blame, "popd: directory stack empty")
|
||||||
"popd: directory stack empty".to_string(),
|
);
|
||||||
blame.clone(),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -340,18 +306,14 @@ pub fn dirs(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<(
|
|||||||
target_idx = Some(parse_stack_idx(&arg, blame.clone(), "dirs")?);
|
target_idx = Some(parse_stack_idx(&arg, blame.clone(), "dirs")?);
|
||||||
}
|
}
|
||||||
_ if arg.starts_with('-') => {
|
_ if arg.starts_with('-') => {
|
||||||
return Err(ShErr::full(
|
return Err(
|
||||||
ShErrKind::ExecFail,
|
ShErr::at(ShErrKind::ExecFail, blame, format!("dirs: invalid option: {arg}"))
|
||||||
format!("dirs: invalid option: {arg}"),
|
);
|
||||||
blame.clone(),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
return Err(ShErr::full(
|
return Err(
|
||||||
ShErrKind::ExecFail,
|
ShErr::at(ShErrKind::ExecFail, blame, format!("dirs: unexpected argument: {arg}"))
|
||||||
format!("dirs: unexpected argument: {arg}"),
|
);
|
||||||
blame.clone(),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -396,17 +358,15 @@ pub fn dirs(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<(
|
|||||||
if let Some(dir) = target {
|
if let Some(dir) = target {
|
||||||
dirs = vec![dir.clone()];
|
dirs = vec![dir.clone()];
|
||||||
} else {
|
} else {
|
||||||
return Err(ShErr::full(
|
return Err(
|
||||||
ShErrKind::ExecFail,
|
ShErr::at(ShErrKind::ExecFail, blame, format!(
|
||||||
format!(
|
|
||||||
"dirs: directory index out of range: {}",
|
"dirs: directory index out of range: {}",
|
||||||
match idx {
|
match idx {
|
||||||
StackIdx::FromTop(n) => format!("+{n}"),
|
StackIdx::FromTop(n) => format!("+{n}"),
|
||||||
StackIdx::FromBottom(n) => format!("-{n}"),
|
StackIdx::FromBottom(n) => format!("-{n}"),
|
||||||
}
|
}
|
||||||
),
|
))
|
||||||
blame.clone(),
|
);
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
use ariadne::Label;
|
|
||||||
use nix::{errno::Errno, unistd::execvpe};
|
use nix::{errno::Errno, unistd::execvpe};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@@ -43,13 +42,9 @@ pub fn exec_builtin(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> Sh
|
|||||||
let cmd_str = cmd.to_str().unwrap().to_string();
|
let cmd_str = cmd.to_str().unwrap().to_string();
|
||||||
match e {
|
match e {
|
||||||
Errno::ENOENT => Err(
|
Errno::ENOENT => Err(
|
||||||
ShErr::full(ShErrKind::CmdNotFound, "", span.clone())
|
ShErr::new(ShErrKind::CmdNotFound, span.clone())
|
||||||
.with_label(
|
.labeled(span, format!("exec: command not found: {}", cmd_str))
|
||||||
span.span_source().clone(),
|
|
||||||
Label::new(span.clone())
|
|
||||||
.with_message(format!("exec: command not found: {}", cmd_str))
|
|
||||||
)
|
|
||||||
),
|
),
|
||||||
_ => Err(ShErr::full(ShErrKind::Errno(e), format!("{e}"), span)),
|
_ => Err(ShErr::at(ShErrKind::Errno(e), span, format!("{e}"))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,11 +21,7 @@ pub fn flowctl(node: Node, kind: ShErrKind) -> ShResult<()> {
|
|||||||
let (arg, span) = argv.into_iter().next().unwrap();
|
let (arg, span) = argv.into_iter().next().unwrap();
|
||||||
|
|
||||||
let Ok(status) = arg.parse::<i32>() else {
|
let Ok(status) = arg.parse::<i32>() else {
|
||||||
return Err(ShErr::full(
|
return Err(ShErr::at(ShErrKind::SyntaxErr, span, format!("{cmd}: Expected a number")));
|
||||||
ShErrKind::SyntaxErr,
|
|
||||||
format!("{cmd}: Expected a number"),
|
|
||||||
span,
|
|
||||||
));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
code = status;
|
code = status;
|
||||||
|
|||||||
@@ -33,17 +33,13 @@ pub fn continue_job(node: Node, job: &mut JobBldr, behavior: JobBehavior) -> ShR
|
|||||||
let mut argv = argv.into_iter();
|
let mut argv = argv.into_iter();
|
||||||
|
|
||||||
if read_jobs(|j| j.get_fg().is_some()) {
|
if read_jobs(|j| j.get_fg().is_some()) {
|
||||||
return Err(ShErr::full(
|
return Err(ShErr::at(ShErrKind::InternalErr, blame, format!("Somehow called '{}' with an existing foreground job", cmd)));
|
||||||
ShErrKind::InternalErr,
|
|
||||||
format!("Somehow called '{}' with an existing foreground job", cmd),
|
|
||||||
blame,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let curr_job_id = if let Some(id) = read_jobs(|j| j.curr_job()) {
|
let curr_job_id = if let Some(id) = read_jobs(|j| j.curr_job()) {
|
||||||
id
|
id
|
||||||
} else {
|
} else {
|
||||||
return Err(ShErr::full(ShErrKind::ExecFail, "No jobs found", blame));
|
return Err(ShErr::at(ShErrKind::ExecFail, blame, "No jobs found"));
|
||||||
};
|
};
|
||||||
|
|
||||||
let tabid = match argv.next() {
|
let tabid = match argv.next() {
|
||||||
@@ -57,11 +53,7 @@ pub fn continue_job(node: Node, job: &mut JobBldr, behavior: JobBehavior) -> ShR
|
|||||||
if query_result.is_some() {
|
if query_result.is_some() {
|
||||||
Ok(j.remove_job(id.clone()).unwrap())
|
Ok(j.remove_job(id.clone()).unwrap())
|
||||||
} else {
|
} else {
|
||||||
Err(ShErr::full(
|
Err(ShErr::at(ShErrKind::ExecFail, blame.clone(), format!("Job id `{}' not found", tabid)))
|
||||||
ShErrKind::ExecFail,
|
|
||||||
format!("Job id `{}' not found", tabid),
|
|
||||||
blame,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
@@ -96,11 +88,7 @@ fn parse_job_id(arg: &str, blame: Span) -> ShResult<usize> {
|
|||||||
});
|
});
|
||||||
match result {
|
match result {
|
||||||
Some(id) => Ok(id),
|
Some(id) => Ok(id),
|
||||||
None => Err(ShErr::full(
|
None => Err(ShErr::at(ShErrKind::InternalErr, blame, "Found a job but no table id in parse_job_id()")),
|
||||||
ShErrKind::InternalErr,
|
|
||||||
"Found a job but no table id in parse_job_id()",
|
|
||||||
blame,
|
|
||||||
)),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if arg.chars().all(|ch| ch.is_ascii_digit()) {
|
} else if arg.chars().all(|ch| ch.is_ascii_digit()) {
|
||||||
@@ -120,18 +108,10 @@ fn parse_job_id(arg: &str, blame: Span) -> ShResult<usize> {
|
|||||||
|
|
||||||
match result {
|
match result {
|
||||||
Some(id) => Ok(id),
|
Some(id) => Ok(id),
|
||||||
None => Err(ShErr::full(
|
None => Err(ShErr::at(ShErrKind::InternalErr, blame, "Found a job but no table id in parse_job_id()")),
|
||||||
ShErrKind::InternalErr,
|
|
||||||
"Found a job but no table id in parse_job_id()",
|
|
||||||
blame,
|
|
||||||
)),
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Err(ShErr::full(
|
Err(ShErr::at(ShErrKind::SyntaxErr, blame, format!("Invalid arg: {}", arg)))
|
||||||
ShErrKind::SyntaxErr,
|
|
||||||
format!("Invalid arg: {}", arg),
|
|
||||||
blame,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,11 +131,7 @@ pub fn jobs(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<(
|
|||||||
for (arg, span) in argv {
|
for (arg, span) in argv {
|
||||||
let mut chars = arg.chars().peekable();
|
let mut chars = arg.chars().peekable();
|
||||||
if chars.peek().is_none_or(|ch| *ch != '-') {
|
if chars.peek().is_none_or(|ch| *ch != '-') {
|
||||||
return Err(ShErr::full(
|
return Err(ShErr::at(ShErrKind::SyntaxErr, span, "Invalid flag in jobs call"));
|
||||||
ShErrKind::SyntaxErr,
|
|
||||||
"Invalid flag in jobs call",
|
|
||||||
span,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
chars.next();
|
chars.next();
|
||||||
for ch in chars {
|
for ch in chars {
|
||||||
@@ -166,11 +142,7 @@ pub fn jobs(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<(
|
|||||||
'r' => JobCmdFlags::RUNNING,
|
'r' => JobCmdFlags::RUNNING,
|
||||||
's' => JobCmdFlags::STOPPED,
|
's' => JobCmdFlags::STOPPED,
|
||||||
_ => {
|
_ => {
|
||||||
return Err(ShErr::full(
|
return Err(ShErr::at(ShErrKind::SyntaxErr, span, "Invalid flag in jobs call"));
|
||||||
ShErrKind::SyntaxErr,
|
|
||||||
"Invalid flag in jobs call",
|
|
||||||
span,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
flags |= flag
|
flags |= flag
|
||||||
@@ -199,11 +171,7 @@ pub fn disown(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult
|
|||||||
let curr_job_id = if let Some(id) = read_jobs(|j| j.curr_job()) {
|
let curr_job_id = if let Some(id) = read_jobs(|j| j.curr_job()) {
|
||||||
id
|
id
|
||||||
} else {
|
} else {
|
||||||
return Err(ShErr::full(
|
return Err(ShErr::at(ShErrKind::ExecFail, blame, "disown: No jobs to disown"));
|
||||||
ShErrKind::ExecFail,
|
|
||||||
"disown: No jobs to disown",
|
|
||||||
blame,
|
|
||||||
));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut tabid = curr_job_id;
|
let mut tabid = curr_job_id;
|
||||||
|
|||||||
@@ -22,11 +22,7 @@ pub fn shift(node: Node, job: &mut JobBldr) -> ShResult<()> {
|
|||||||
|
|
||||||
if let Some((arg, span)) = argv.next() {
|
if let Some((arg, span)) = argv.next() {
|
||||||
let Ok(count) = arg.parse::<usize>() else {
|
let Ok(count) = arg.parse::<usize>() else {
|
||||||
return Err(ShErr::full(
|
return Err(ShErr::at(ShErrKind::ExecFail, span, "Expected a number in shift args"));
|
||||||
ShErrKind::ExecFail,
|
|
||||||
"Expected a number in shift args",
|
|
||||||
span,
|
|
||||||
));
|
|
||||||
};
|
};
|
||||||
for _ in 0..count {
|
for _ in 0..count {
|
||||||
write_vars(|v| v.cur_scope_mut().fpop_arg());
|
write_vars(|v| v.cur_scope_mut().fpop_arg());
|
||||||
|
|||||||
@@ -23,18 +23,10 @@ pub fn source(node: Node, job: &mut JobBldr) -> ShResult<()> {
|
|||||||
for (arg, span) in argv {
|
for (arg, span) in argv {
|
||||||
let path = PathBuf::from(arg);
|
let path = PathBuf::from(arg);
|
||||||
if !path.exists() {
|
if !path.exists() {
|
||||||
return Err(ShErr::full(
|
return Err(ShErr::at(ShErrKind::ExecFail, span, format!("source: File '{}' not found", path.display())));
|
||||||
ShErrKind::ExecFail,
|
|
||||||
format!("source: File '{}' not found", path.display()),
|
|
||||||
span,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
if !path.is_file() {
|
if !path.is_file() {
|
||||||
return Err(ShErr::full(
|
return Err(ShErr::at(ShErrKind::ExecFail, span, format!("source: Given path '{}' is not a file", path.display())));
|
||||||
ShErrKind::ExecFail,
|
|
||||||
format!("source: Given path '{}' is not a file", path.display()),
|
|
||||||
span,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
source_file(path)?;
|
source_file(path)?;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -138,11 +138,7 @@ pub fn double_bracket_test(node: Node) -> ShResult<bool> {
|
|||||||
let operand = operand.expand()?.get_words().join(" ");
|
let operand = operand.expand()?.get_words().join(" ");
|
||||||
conjunct_op = conjunct;
|
conjunct_op = conjunct;
|
||||||
let TestOp::Unary(op) = TestOp::from_str(operator.as_str())? else {
|
let TestOp::Unary(op) = TestOp::from_str(operator.as_str())? else {
|
||||||
return Err(ShErr::full(
|
return Err(ShErr::at(ShErrKind::SyntaxErr, err_span, "Invalid unary operator"));
|
||||||
ShErrKind::SyntaxErr,
|
|
||||||
"Invalid unary operator",
|
|
||||||
err_span,
|
|
||||||
));
|
|
||||||
};
|
};
|
||||||
match op {
|
match op {
|
||||||
UnaryOp::Exists => {
|
UnaryOp::Exists => {
|
||||||
@@ -245,11 +241,7 @@ pub fn double_bracket_test(node: Node) -> ShResult<bool> {
|
|||||||
let test_op = operator.as_str().parse::<TestOp>()?;
|
let test_op = operator.as_str().parse::<TestOp>()?;
|
||||||
match test_op {
|
match test_op {
|
||||||
TestOp::Unary(_) => {
|
TestOp::Unary(_) => {
|
||||||
return Err(ShErr::full(
|
return Err(ShErr::at(ShErrKind::SyntaxErr, err_span, "Expected a binary operator in this test call; found a unary operator"));
|
||||||
ShErrKind::SyntaxErr,
|
|
||||||
"Expected a binary operator in this test call; found a unary operator",
|
|
||||||
err_span,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
TestOp::StringEq => {
|
TestOp::StringEq => {
|
||||||
let pattern = crate::expand::glob_to_regex(rhs.trim(), true);
|
let pattern = crate::expand::glob_to_regex(rhs.trim(), true);
|
||||||
@@ -265,11 +257,7 @@ pub fn double_bracket_test(node: Node) -> ShResult<bool> {
|
|||||||
| TestOp::IntGe
|
| TestOp::IntGe
|
||||||
| TestOp::IntLe
|
| TestOp::IntLe
|
||||||
| TestOp::IntEq => {
|
| TestOp::IntEq => {
|
||||||
let err = ShErr::full(
|
let err = ShErr::at(ShErrKind::SyntaxErr, err_span.clone(), format!("Expected an integer with '{}' operator", operator));
|
||||||
ShErrKind::SyntaxErr,
|
|
||||||
format!("Expected an integer with '{}' operator", operator),
|
|
||||||
err_span.clone(),
|
|
||||||
);
|
|
||||||
let Ok(lhs) = lhs.trim().parse::<i32>() else {
|
let Ok(lhs) = lhs.trim().parse::<i32>() else {
|
||||||
return Err(err);
|
return Err(err);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -75,20 +75,12 @@ pub fn unset(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<
|
|||||||
let argv = argv.unwrap();
|
let argv = argv.unwrap();
|
||||||
|
|
||||||
if argv.is_empty() {
|
if argv.is_empty() {
|
||||||
return Err(ShErr::full(
|
return Err(ShErr::at(ShErrKind::SyntaxErr, blame, "unset: Expected at least one argument"));
|
||||||
ShErrKind::SyntaxErr,
|
|
||||||
"unset: Expected at least one argument",
|
|
||||||
blame,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (arg, span) in argv {
|
for (arg, span) in argv {
|
||||||
if !read_vars(|v| v.var_exists(&arg)) {
|
if !read_vars(|v| v.var_exists(&arg)) {
|
||||||
return Err(ShErr::full(
|
return Err(ShErr::at(ShErrKind::ExecFail, span, format!("unset: No such variable '{arg}'")));
|
||||||
ShErrKind::ExecFail,
|
|
||||||
format!("unset: No such variable '{arg}'"),
|
|
||||||
span,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
write_vars(|v| v.unset_var(&arg))?;
|
write_vars(|v| v.unset_var(&arg))?;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -704,7 +704,11 @@ pub fn term_ctlr() -> Pid {
|
|||||||
/// Calls attach_tty() on the shell's process group to retake control of the
|
/// Calls attach_tty() on the shell's process group to retake control of the
|
||||||
/// terminal
|
/// terminal
|
||||||
pub fn take_term() -> ShResult<()> {
|
pub fn take_term() -> ShResult<()> {
|
||||||
|
// take the terminal back
|
||||||
attach_tty(getpgrp())?;
|
attach_tty(getpgrp())?;
|
||||||
|
|
||||||
|
// send SIGWINCH to tell readline to update its window size in case it changed while we were in the background
|
||||||
|
killpg(getpgrp(), Signal::SIGWINCH)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::collections::HashMap;
|
use std::collections::{HashMap, VecDeque};
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
use ariadne::Color;
|
use ariadne::Color;
|
||||||
use ariadne::{Report, ReportKind};
|
use ariadne::{Report, ReportKind};
|
||||||
@@ -13,7 +13,9 @@ use crate::{
|
|||||||
|
|
||||||
pub type ShResult<T> = Result<T, ShErr>;
|
pub type ShResult<T> = Result<T, ShErr>;
|
||||||
|
|
||||||
pub struct ColorRng;
|
pub struct ColorRng {
|
||||||
|
last_color: Option<Color>,
|
||||||
|
}
|
||||||
|
|
||||||
impl ColorRng {
|
impl ColorRng {
|
||||||
fn get_colors() -> &'static [Color] {
|
fn get_colors() -> &'static [Color] {
|
||||||
@@ -51,7 +53,7 @@ impl Iterator for ColorRng {
|
|||||||
}
|
}
|
||||||
|
|
||||||
thread_local! {
|
thread_local! {
|
||||||
static COLOR_RNG: RefCell<ColorRng> = const { RefCell::new(ColorRng) };
|
static COLOR_RNG: RefCell<ColorRng> = const { RefCell::new(ColorRng { last_color: None }) };
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn next_color() -> Color {
|
pub fn next_color() -> Color {
|
||||||
@@ -144,8 +146,18 @@ impl ShErr {
|
|||||||
pub fn simple(kind: ShErrKind, msg: impl Into<String>) -> Self {
|
pub fn simple(kind: ShErrKind, msg: impl Into<String>) -> Self {
|
||||||
Self { kind, src_span: None, labels: vec![], sources: vec![], notes: vec![msg.into()] }
|
Self { kind, src_span: None, labels: vec![], sources: vec![], notes: vec![msg.into()] }
|
||||||
}
|
}
|
||||||
pub fn full(kind: ShErrKind, msg: impl Into<String>, span: Span) -> Self {
|
pub fn at(kind: ShErrKind, span: Span, msg: impl Into<String>) -> Self {
|
||||||
Self { kind, src_span: Some(span), labels: vec![], sources: vec![], notes: vec![msg.into()] }
|
let color = next_color();
|
||||||
|
let src = span.span_source().clone();
|
||||||
|
let msg: String = msg.into();
|
||||||
|
Self::new(kind, span.clone())
|
||||||
|
.with_label(src, ariadne::Label::new(span).with_color(color).with_message(msg))
|
||||||
|
}
|
||||||
|
pub fn labeled(self, span: Span, msg: impl Into<String>) -> Self {
|
||||||
|
let color = next_color();
|
||||||
|
let src = span.span_source().clone();
|
||||||
|
let msg: String = msg.into();
|
||||||
|
self.with_label(src, ariadne::Label::new(span).with_color(color).with_message(msg))
|
||||||
}
|
}
|
||||||
pub fn blame(self, span: Span) -> Self {
|
pub fn blame(self, span: Span) -> Self {
|
||||||
let ShErr { kind, src_span: _, labels, sources, notes } = self;
|
let ShErr { kind, src_span: _, labels, sources, notes } = self;
|
||||||
@@ -172,7 +184,7 @@ impl ShErr {
|
|||||||
labels.push(label);
|
labels.push(label);
|
||||||
Self { kind, src_span, labels, sources, notes }
|
Self { kind, src_span, labels, sources, notes }
|
||||||
}
|
}
|
||||||
pub fn with_context(self, ctx: Vec<(SpanSource, ariadne::Label<Span>)>) -> Self {
|
pub fn with_context(self, ctx: VecDeque<(SpanSource, ariadne::Label<Span>)>) -> Self {
|
||||||
let ShErr { kind, src_span, mut labels, mut sources, notes } = self;
|
let ShErr { kind, src_span, mut labels, mut sources, notes } = self;
|
||||||
for (src, label) in ctx {
|
for (src, label) in ctx {
|
||||||
sources.push(src);
|
sources.push(src);
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
|
|
||||||
|
use ariadne::Span as AriadneSpan;
|
||||||
|
|
||||||
use crate::parse::lex::{Span, Tk, TkRule};
|
use crate::parse::lex::{Span, Tk, TkRule};
|
||||||
use crate::parse::{Redir, RedirType};
|
use crate::parse::{Node, Redir, RedirType};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
pub trait VecDequeExt<T> {
|
pub trait VecDequeExt<T> {
|
||||||
@@ -27,6 +29,10 @@ pub trait RedirVecUtils<Redir> {
|
|||||||
fn split_by_channel(self) -> (Vec<Redir>, Vec<Redir>);
|
fn split_by_channel(self) -> (Vec<Redir>, Vec<Redir>);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait NodeVecUtils<Node> {
|
||||||
|
fn get_span(&self) -> Option<Span>;
|
||||||
|
}
|
||||||
|
|
||||||
impl<T> VecDequeExt<T> for VecDeque<T> {
|
impl<T> VecDequeExt<T> for VecDeque<T> {
|
||||||
fn to_vec(self) -> Vec<T> {
|
fn to_vec(self) -> Vec<T> {
|
||||||
self.into_iter().collect::<Vec<T>>()
|
self.into_iter().collect::<Vec<T>>()
|
||||||
@@ -124,3 +130,17 @@ impl RedirVecUtils<Redir> for Vec<Redir> {
|
|||||||
(input, output)
|
(input, output)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl NodeVecUtils<Node> for Vec<Node> {
|
||||||
|
fn get_span(&self) -> Option<Span> {
|
||||||
|
if let Some(first_nd) = self.first()
|
||||||
|
&& let Some(last_nd) = self.last() {
|
||||||
|
let first_start = first_nd.get_span().range().start;
|
||||||
|
let last_end = last_nd.get_span().range().end;
|
||||||
|
if first_start <= last_end {
|
||||||
|
return Some(Span::new(first_start..last_end, first_nd.get_span().source().content()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,7 +2,8 @@ use std::{
|
|||||||
cell::Cell, collections::{HashSet, VecDeque}, os::unix::fs::PermissionsExt
|
cell::Cell, collections::{HashSet, VecDeque}, os::unix::fs::PermissionsExt
|
||||||
};
|
};
|
||||||
|
|
||||||
use ariadne::{Fmt, Label};
|
|
||||||
|
use ariadne::{Fmt, Label, Span as AriadneSpan};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
builtin::{
|
builtin::{
|
||||||
@@ -294,10 +295,10 @@ impl Dispatcher {
|
|||||||
let name = name.span.as_str().strip_suffix("()").unwrap();
|
let name = name.span.as_str().strip_suffix("()").unwrap();
|
||||||
|
|
||||||
if KEYWORDS.contains(&name) {
|
if KEYWORDS.contains(&name) {
|
||||||
return Err(ShErr::full(
|
return Err(ShErr::at(
|
||||||
ShErrKind::SyntaxErr,
|
ShErrKind::SyntaxErr,
|
||||||
format!("function: Forbidden function name `{name}`"),
|
|
||||||
blame,
|
blame,
|
||||||
|
format!("function: Forbidden function name `{name}`"),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -342,6 +343,8 @@ impl Dispatcher {
|
|||||||
}
|
}
|
||||||
fn exec_func(&mut self, func: Node) -> ShResult<()> {
|
fn exec_func(&mut self, func: Node) -> ShResult<()> {
|
||||||
let mut blame = func.get_span().clone();
|
let mut blame = func.get_span().clone();
|
||||||
|
let func_name = func.get_command().unwrap().to_string();
|
||||||
|
let func_ctx = func.get_context(format!("in call to function '{}'",func_name.fg(next_color())));
|
||||||
let NdRule::Command {
|
let NdRule::Command {
|
||||||
assignments,
|
assignments,
|
||||||
mut argv,
|
mut argv,
|
||||||
@@ -358,24 +361,25 @@ impl Dispatcher {
|
|||||||
});
|
});
|
||||||
if depth > max_depth {
|
if depth > max_depth {
|
||||||
RECURSE_DEPTH.with(|d| d.set(d.get() - 1));
|
RECURSE_DEPTH.with(|d| d.set(d.get() - 1));
|
||||||
return Err(ShErr::full(
|
return Err(ShErr::at(
|
||||||
ShErrKind::InternalErr,
|
ShErrKind::InternalErr,
|
||||||
format!("maximum recursion depth ({max_depth}) exceeded"),
|
|
||||||
blame,
|
blame,
|
||||||
|
format!("maximum recursion depth ({max_depth}) exceeded"),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let env_vars = self.set_assignments(assignments, AssignBehavior::Export)?;
|
let env_vars = self.set_assignments(assignments, AssignBehavior::Export)?;
|
||||||
|
let func_name = argv.remove(0).to_string();
|
||||||
let _var_guard = VarCtxGuard::new(env_vars.into_iter().collect());
|
let _var_guard = VarCtxGuard::new(env_vars.into_iter().collect());
|
||||||
|
|
||||||
self.io_stack.append_to_frame(func.redirs);
|
self.io_stack.append_to_frame(func.redirs);
|
||||||
|
|
||||||
let func_name = argv.remove(0).span.as_str().to_string();
|
|
||||||
blame.rename(func_name.clone());
|
blame.rename(func_name.clone());
|
||||||
|
|
||||||
let argv = prepare_argv(argv)?;
|
let argv = prepare_argv(argv)?;
|
||||||
let result = if let Some(ref mut func_body) = read_logic(|l| l.get_func(&func_name)) {
|
let result = if let Some(ref mut func_body) = read_logic(|l| l.get_func(&func_name)) {
|
||||||
let _guard = ScopeGuard::exclusive_scope(Some(argv));
|
let _guard = ScopeGuard::exclusive_scope(Some(argv));
|
||||||
|
func_body.body_mut().propagate_context(func_ctx);
|
||||||
func_body.body_mut().flags = func.flags;
|
func_body.body_mut().flags = func.flags;
|
||||||
|
|
||||||
if let Err(e) = self.exec_brc_grp(func_body.body().clone()) {
|
if let Err(e) = self.exec_brc_grp(func_body.body().clone()) {
|
||||||
@@ -384,16 +388,16 @@ impl Dispatcher {
|
|||||||
state::set_status(*code);
|
state::set_status(*code);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
_ => Err(e),
|
_ => Err(e)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Err(ShErr::full(
|
Err(ShErr::at(
|
||||||
ShErrKind::InternalErr,
|
ShErrKind::InternalErr,
|
||||||
format!("Failed to find function '{}'", func_name),
|
|
||||||
blame,
|
blame,
|
||||||
|
format!("Failed to find function '{}'", func_name),
|
||||||
))
|
))
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -747,6 +751,7 @@ impl Dispatcher {
|
|||||||
}
|
}
|
||||||
fn dispatch_builtin(&mut self, mut cmd: Node) -> ShResult<()> {
|
fn dispatch_builtin(&mut self, mut cmd: Node) -> ShResult<()> {
|
||||||
let cmd_raw = cmd.get_command().unwrap().to_string();
|
let cmd_raw = cmd.get_command().unwrap().to_string();
|
||||||
|
let context = cmd.context.clone();
|
||||||
let NdRule::Command { assignments, argv } = &mut cmd.class else {
|
let NdRule::Command { assignments, argv } = &mut cmd.class else {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
@@ -773,7 +778,7 @@ impl Dispatcher {
|
|||||||
}
|
}
|
||||||
return self.exec_cmd(cmd);
|
return self.exec_cmd(cmd);
|
||||||
}
|
}
|
||||||
match cmd_raw.as_str() {
|
let result = match cmd_raw.as_str() {
|
||||||
"echo" => echo(cmd, io_stack_mut, curr_job_mut),
|
"echo" => echo(cmd, io_stack_mut, curr_job_mut),
|
||||||
"cd" => cd(cmd, curr_job_mut),
|
"cd" => cd(cmd, curr_job_mut),
|
||||||
"export" => export(cmd, io_stack_mut, curr_job_mut),
|
"export" => export(cmd, io_stack_mut, curr_job_mut),
|
||||||
@@ -819,6 +824,12 @@ impl Dispatcher {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
_ => unimplemented!("Have not yet added support for builtin '{}'", cmd_raw),
|
_ => unimplemented!("Have not yet added support for builtin '{}'", cmd_raw),
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Err(e) = result {
|
||||||
|
Err(e.with_context(context))
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn exec_cmd(&mut self, cmd: Node) -> ShResult<()> {
|
fn exec_cmd(&mut self, cmd: Node) -> ShResult<()> {
|
||||||
@@ -859,20 +870,13 @@ impl Dispatcher {
|
|||||||
let cmd_str = cmd.to_str().unwrap().to_string();
|
let cmd_str = cmd.to_str().unwrap().to_string();
|
||||||
match e {
|
match e {
|
||||||
Errno::ENOENT => {
|
Errno::ENOENT => {
|
||||||
let source = span.span_source().clone();
|
ShErr::new(ShErrKind::CmdNotFound, span.clone())
|
||||||
let color = next_color();
|
.labeled(span, format!("{cmd_str}: command not found"))
|
||||||
ShErr::full(ShErrKind::CmdNotFound, "", span.clone())
|
|
||||||
.with_label(
|
|
||||||
source,
|
|
||||||
Label::new(span)
|
|
||||||
.with_color(color)
|
|
||||||
.with_message(format!("{}: command not found", cmd_str.fg(color)))
|
|
||||||
)
|
|
||||||
.with_context(context)
|
.with_context(context)
|
||||||
.print_error();
|
.print_error();
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
ShErr::full(ShErrKind::Errno(e), format!("{e}"), span)
|
ShErr::at(ShErrKind::Errno(e), span, format!("{e}"))
|
||||||
.with_context(context)
|
.with_context(context)
|
||||||
.print_error();
|
.print_error();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -354,10 +354,10 @@ impl LexStream {
|
|||||||
if !found_fd && !self.flags.contains(LexFlags::LEX_UNFINISHED) {
|
if !found_fd && !self.flags.contains(LexFlags::LEX_UNFINISHED) {
|
||||||
let span_start = self.cursor;
|
let span_start = self.cursor;
|
||||||
self.cursor = pos;
|
self.cursor = pos;
|
||||||
return Some(Err(ShErr::full(
|
return Some(Err(ShErr::at(
|
||||||
ShErrKind::ParseErr,
|
ShErrKind::ParseErr,
|
||||||
"Invalid redirection",
|
|
||||||
Span::new(span_start..pos, self.source.clone()),
|
Span::new(span_start..pos, self.source.clone()),
|
||||||
|
"Invalid redirection",
|
||||||
)));
|
)));
|
||||||
} else {
|
} else {
|
||||||
tk = self.get_token(self.cursor..pos, TkRule::Redir);
|
tk = self.get_token(self.cursor..pos, TkRule::Redir);
|
||||||
@@ -471,10 +471,10 @@ impl LexStream {
|
|||||||
}
|
}
|
||||||
if !paren_count == 0 && !self.flags.contains(LexFlags::LEX_UNFINISHED) {
|
if !paren_count == 0 && !self.flags.contains(LexFlags::LEX_UNFINISHED) {
|
||||||
self.cursor = pos;
|
self.cursor = pos;
|
||||||
return Err(ShErr::full(
|
return Err(ShErr::at(
|
||||||
ShErrKind::ParseErr,
|
ShErrKind::ParseErr,
|
||||||
"Unclosed subshell",
|
|
||||||
Span::new(paren_pos..paren_pos + 1, self.source.clone()),
|
Span::new(paren_pos..paren_pos + 1, self.source.clone()),
|
||||||
|
"Unclosed subshell",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -539,10 +539,10 @@ impl LexStream {
|
|||||||
}
|
}
|
||||||
if !paren_count == 0 && !self.flags.contains(LexFlags::LEX_UNFINISHED) {
|
if !paren_count == 0 && !self.flags.contains(LexFlags::LEX_UNFINISHED) {
|
||||||
self.cursor = pos;
|
self.cursor = pos;
|
||||||
return Err(ShErr::full(
|
return Err(ShErr::at(
|
||||||
ShErrKind::ParseErr,
|
ShErrKind::ParseErr,
|
||||||
"Unclosed subshell",
|
|
||||||
Span::new(paren_pos..paren_pos + 1, self.source.clone()),
|
Span::new(paren_pos..paren_pos + 1, self.source.clone()),
|
||||||
|
"Unclosed subshell",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -575,10 +575,10 @@ impl LexStream {
|
|||||||
}
|
}
|
||||||
if !paren_count == 0 && !self.flags.contains(LexFlags::LEX_UNFINISHED) {
|
if !paren_count == 0 && !self.flags.contains(LexFlags::LEX_UNFINISHED) {
|
||||||
self.cursor = pos;
|
self.cursor = pos;
|
||||||
return Err(ShErr::full(
|
return Err(ShErr::at(
|
||||||
ShErrKind::ParseErr,
|
ShErrKind::ParseErr,
|
||||||
"Unclosed subshell",
|
|
||||||
Span::new(paren_pos..paren_pos + 1, self.source.clone()),
|
Span::new(paren_pos..paren_pos + 1, self.source.clone()),
|
||||||
|
"Unclosed subshell",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -610,10 +610,10 @@ impl LexStream {
|
|||||||
}
|
}
|
||||||
if paren_count != 0 && !self.flags.contains(LexFlags::LEX_UNFINISHED) {
|
if paren_count != 0 && !self.flags.contains(LexFlags::LEX_UNFINISHED) {
|
||||||
self.cursor = pos;
|
self.cursor = pos;
|
||||||
return Err(ShErr::full(
|
return Err(ShErr::at(
|
||||||
ShErrKind::ParseErr,
|
ShErrKind::ParseErr,
|
||||||
"Unclosed subshell",
|
|
||||||
Span::new(paren_pos..paren_pos + 1, self.source.clone()),
|
Span::new(paren_pos..paren_pos + 1, self.source.clone()),
|
||||||
|
"Unclosed subshell",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
let mut subsh_tk = self.get_token(self.cursor..pos, TkRule::Str);
|
let mut subsh_tk = self.get_token(self.cursor..pos, TkRule::Str);
|
||||||
@@ -677,10 +677,10 @@ impl LexStream {
|
|||||||
let mut new_tk = self.get_token(self.cursor..pos, TkRule::Str);
|
let mut new_tk = self.get_token(self.cursor..pos, TkRule::Str);
|
||||||
if self.quote_state.in_quote() && !self.flags.contains(LexFlags::LEX_UNFINISHED) {
|
if self.quote_state.in_quote() && !self.flags.contains(LexFlags::LEX_UNFINISHED) {
|
||||||
self.cursor = pos;
|
self.cursor = pos;
|
||||||
return Err(ShErr::full(
|
return Err(ShErr::at(
|
||||||
ShErrKind::ParseErr,
|
ShErrKind::ParseErr,
|
||||||
"Unterminated quote",
|
|
||||||
new_tk.span,
|
new_tk.span,
|
||||||
|
"Unterminated quote",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -750,10 +750,10 @@ impl Iterator for LexStream {
|
|||||||
if self.in_brc_grp() && !self.flags.contains(LexFlags::LEX_UNFINISHED) {
|
if self.in_brc_grp() && !self.flags.contains(LexFlags::LEX_UNFINISHED) {
|
||||||
let start = self.brc_grp_start.unwrap_or(self.cursor.saturating_sub(1));
|
let start = self.brc_grp_start.unwrap_or(self.cursor.saturating_sub(1));
|
||||||
self.flags |= LexFlags::STALE;
|
self.flags |= LexFlags::STALE;
|
||||||
return Err(ShErr::full(
|
return Err(ShErr::at(
|
||||||
ShErrKind::ParseErr,
|
ShErrKind::ParseErr,
|
||||||
"Unclosed brace group",
|
|
||||||
Span::new(start..self.cursor, self.source.clone()),
|
Span::new(start..self.cursor, self.source.clone()),
|
||||||
|
"Unclosed brace group",
|
||||||
))
|
))
|
||||||
.into();
|
.into();
|
||||||
}
|
}
|
||||||
@@ -789,10 +789,10 @@ impl Iterator for LexStream {
|
|||||||
if self.cursor == self.source.len() {
|
if self.cursor == self.source.len() {
|
||||||
if self.in_brc_grp() && !self.flags.contains(LexFlags::LEX_UNFINISHED) {
|
if self.in_brc_grp() && !self.flags.contains(LexFlags::LEX_UNFINISHED) {
|
||||||
let start = self.brc_grp_start.unwrap_or(self.cursor.saturating_sub(1));
|
let start = self.brc_grp_start.unwrap_or(self.cursor.saturating_sub(1));
|
||||||
return Err(ShErr::full(
|
return Err(ShErr::at(
|
||||||
ShErrKind::ParseErr,
|
ShErrKind::ParseErr,
|
||||||
"Unclosed brace group",
|
|
||||||
Span::new(start..self.cursor, self.source.clone()),
|
Span::new(start..self.cursor, self.source.clone()),
|
||||||
|
"Unclosed brace group",
|
||||||
))
|
))
|
||||||
.into();
|
.into();
|
||||||
}
|
}
|
||||||
|
|||||||
148
src/parse/mod.rs
148
src/parse/mod.rs
@@ -1,6 +1,6 @@
|
|||||||
use std::{fmt::Debug, str::FromStr, sync::Arc};
|
use std::{collections::VecDeque, fmt::Debug, str::FromStr, sync::Arc};
|
||||||
|
|
||||||
use ariadne::{Fmt, Label};
|
use ariadne::{Fmt, Label, Span as AriadneSpan};
|
||||||
use bitflags::bitflags;
|
use bitflags::bitflags;
|
||||||
use fmt::Display;
|
use fmt::Display;
|
||||||
use lex::{LexFlags, LexStream, Span, SpanSource, Tk, TkFlags, TkRule};
|
use lex::{LexFlags, LexStream, Span, SpanSource, Tk, TkFlags, TkRule};
|
||||||
@@ -9,7 +9,7 @@ use yansi::Color;
|
|||||||
use crate::{
|
use crate::{
|
||||||
libsh::{
|
libsh::{
|
||||||
error::{Note, ShErr, ShErrKind, ShResult, next_color},
|
error::{Note, ShErr, ShErrKind, ShResult, next_color},
|
||||||
utils::TkVecUtils,
|
utils::{NodeVecUtils, TkVecUtils},
|
||||||
},
|
},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
procio::IoMode,
|
procio::IoMode,
|
||||||
@@ -56,7 +56,7 @@ impl ParsedSrc {
|
|||||||
src,
|
src,
|
||||||
ast: Ast::new(vec![]),
|
ast: Ast::new(vec![]),
|
||||||
lex_flags: LexFlags::empty(),
|
lex_flags: LexFlags::empty(),
|
||||||
context: vec![],
|
context: VecDeque::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn with_lex_flags(mut self, flags: LexFlags) -> Self {
|
pub fn with_lex_flags(mut self, flags: LexFlags) -> Self {
|
||||||
@@ -97,7 +97,7 @@ impl ParsedSrc {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Default, Clone, Debug)]
|
||||||
pub struct Ast(Vec<Node>);
|
pub struct Ast(Vec<Node>);
|
||||||
|
|
||||||
impl Ast {
|
impl Ast {
|
||||||
@@ -112,7 +112,7 @@ impl Ast {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type LabelCtx = Vec<(SpanSource, Label<Span>)>;
|
pub type LabelCtx = VecDeque<(SpanSource, Label<Span>)>;
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Node {
|
pub struct Node {
|
||||||
@@ -135,6 +135,108 @@ impl Node {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pub fn get_context(&self, msg: String) -> (SpanSource, Label<Span>) {
|
||||||
|
let color = next_color();
|
||||||
|
let span = self.get_span().clone();
|
||||||
|
(
|
||||||
|
span.clone().source().clone(),
|
||||||
|
Label::new(span).with_color(color).with_message(msg)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
fn walk_tree<F: Fn(&mut Node)>(&mut self, f: &F) {
|
||||||
|
f(self);
|
||||||
|
|
||||||
|
match self.class {
|
||||||
|
NdRule::IfNode {
|
||||||
|
ref mut cond_nodes,
|
||||||
|
ref mut else_block,
|
||||||
|
} => {
|
||||||
|
for node in cond_nodes {
|
||||||
|
let CondNode { cond, body } = node;
|
||||||
|
cond.walk_tree(f);
|
||||||
|
for body_node in body {
|
||||||
|
body_node.walk_tree(f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for else_node in else_block {
|
||||||
|
else_node.walk_tree(f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NdRule::LoopNode {
|
||||||
|
kind: _,
|
||||||
|
ref mut cond_node,
|
||||||
|
} => {
|
||||||
|
let CondNode { cond, body } = cond_node;
|
||||||
|
cond.walk_tree(f);
|
||||||
|
for body_node in body {
|
||||||
|
body_node.walk_tree(f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NdRule::ForNode {
|
||||||
|
vars: _,
|
||||||
|
arr: _,
|
||||||
|
ref mut body,
|
||||||
|
} => {
|
||||||
|
for body_node in body {
|
||||||
|
body_node.walk_tree(f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NdRule::CaseNode {
|
||||||
|
pattern: _,
|
||||||
|
ref mut case_blocks,
|
||||||
|
} => {
|
||||||
|
for block in case_blocks {
|
||||||
|
let CaseNode { pattern: _, body } = block;
|
||||||
|
for body_node in body {
|
||||||
|
body_node.walk_tree(f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NdRule::Command {
|
||||||
|
ref mut assignments,
|
||||||
|
argv: _,
|
||||||
|
} => {
|
||||||
|
for assign_node in assignments {
|
||||||
|
assign_node.walk_tree(f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NdRule::Pipeline {
|
||||||
|
ref mut cmds,
|
||||||
|
pipe_err: _,
|
||||||
|
} => {
|
||||||
|
for cmd_node in cmds {
|
||||||
|
cmd_node.walk_tree(f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NdRule::Conjunction { ref mut elements } => {
|
||||||
|
for node in elements.iter_mut() {
|
||||||
|
let ConjunctNode { cmd, operator: _ } = node;
|
||||||
|
cmd.walk_tree(f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NdRule::Assignment {
|
||||||
|
kind: _,
|
||||||
|
var: _,
|
||||||
|
val: _,
|
||||||
|
} => (), // No nodes to check
|
||||||
|
NdRule::BraceGrp { ref mut body } => {
|
||||||
|
for body_node in body {
|
||||||
|
body_node.walk_tree(f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NdRule::FuncDef {
|
||||||
|
name: _,
|
||||||
|
ref mut body,
|
||||||
|
} => {
|
||||||
|
body.walk_tree(f);
|
||||||
|
}
|
||||||
|
NdRule::Test { cases: _ } => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn propagate_context(&mut self, ctx: (SpanSource, Label<Span>)) {
|
||||||
|
self.walk_tree(&|nd| nd.context.push_back(ctx.clone()));
|
||||||
|
}
|
||||||
pub fn get_span(&self) -> Span {
|
pub fn get_span(&self) -> Span {
|
||||||
let Some(first_tk) = self.tokens.first() else {
|
let Some(first_tk) = self.tokens.first() else {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
@@ -565,7 +667,7 @@ impl Debug for ParseStream {
|
|||||||
|
|
||||||
impl ParseStream {
|
impl ParseStream {
|
||||||
pub fn new(tokens: Vec<Tk>) -> Self {
|
pub fn new(tokens: Vec<Tk>) -> Self {
|
||||||
Self { tokens, context: vec![] }
|
Self { tokens, context: VecDeque::new() }
|
||||||
}
|
}
|
||||||
pub fn with_context(tokens: Vec<Tk>, context: LabelCtx) -> Self {
|
pub fn with_context(tokens: Vec<Tk>, context: LabelCtx) -> Self {
|
||||||
Self { tokens, context }
|
Self { tokens, context }
|
||||||
@@ -726,7 +828,7 @@ impl ParseStream {
|
|||||||
src.rename(name_raw.clone());
|
src.rename(name_raw.clone());
|
||||||
let color = next_color();
|
let color = next_color();
|
||||||
// Push a placeholder context so child nodes inherit it
|
// Push a placeholder context so child nodes inherit it
|
||||||
self.context.push((
|
self.context.push_back((
|
||||||
src.clone(),
|
src.clone(),
|
||||||
Label::new(name_tk.span.clone().with_name(name_raw.clone()))
|
Label::new(name_tk.span.clone().with_name(name_raw.clone()))
|
||||||
.with_message(format!("in function '{}' defined here", name_raw.clone().fg(color)))
|
.with_message(format!("in function '{}' defined here", name_raw.clone().fg(color)))
|
||||||
@@ -734,7 +836,7 @@ impl ParseStream {
|
|||||||
));
|
));
|
||||||
|
|
||||||
let Some(brc_grp) = self.parse_brc_grp(true /* from_func_def */)? else {
|
let Some(brc_grp) = self.parse_brc_grp(true /* from_func_def */)? else {
|
||||||
self.context.pop();
|
self.context.pop_back();
|
||||||
return Err(parse_err_full(
|
return Err(parse_err_full(
|
||||||
"Expected a brace group after function name",
|
"Expected a brace group after function name",
|
||||||
&node_tks.get_span().unwrap(),
|
&node_tks.get_span().unwrap(),
|
||||||
@@ -743,14 +845,7 @@ impl ParseStream {
|
|||||||
};
|
};
|
||||||
body = Box::new(brc_grp);
|
body = Box::new(brc_grp);
|
||||||
// Replace placeholder with full-span label
|
// Replace placeholder with full-span label
|
||||||
self.context.pop();
|
self.context.pop_back();
|
||||||
let full_span = body.get_span();
|
|
||||||
self.context.push((
|
|
||||||
src,
|
|
||||||
Label::new(full_span.with_name(name_raw.clone()))
|
|
||||||
.with_message(format!("in function '{}' called here", name_raw.fg(color)))
|
|
||||||
.with_color(color),
|
|
||||||
));
|
|
||||||
|
|
||||||
let node = Node {
|
let node = Node {
|
||||||
class: NdRule::FuncDef { name, body },
|
class: NdRule::FuncDef { name, body },
|
||||||
@@ -760,7 +855,7 @@ impl ParseStream {
|
|||||||
context: self.context.clone()
|
context: self.context.clone()
|
||||||
};
|
};
|
||||||
|
|
||||||
self.context.pop();
|
self.context.pop_back();
|
||||||
Ok(Some(node))
|
Ok(Some(node))
|
||||||
}
|
}
|
||||||
fn panic_mode(&mut self, node_tks: &mut Vec<Tk>) {
|
fn panic_mode(&mut self, node_tks: &mut Vec<Tk>) {
|
||||||
@@ -906,10 +1001,10 @@ impl ParseStream {
|
|||||||
let path_tk = self.next_tk();
|
let path_tk = self.next_tk();
|
||||||
|
|
||||||
if path_tk.clone().is_none_or(|tk| tk.class == TkRule::EOI) {
|
if path_tk.clone().is_none_or(|tk| tk.class == TkRule::EOI) {
|
||||||
return Err(ShErr::full(
|
return Err(ShErr::at(
|
||||||
ShErrKind::ParseErr,
|
ShErrKind::ParseErr,
|
||||||
"Expected a filename after this redirection",
|
|
||||||
tk.span.clone(),
|
tk.span.clone(),
|
||||||
|
"Expected a filename after this redirection",
|
||||||
));
|
));
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1412,6 +1507,14 @@ impl ParseStream {
|
|||||||
// If we have assignments but no command word,
|
// If we have assignments but no command word,
|
||||||
// return the assignment-only command without parsing more tokens
|
// return the assignment-only command without parsing more tokens
|
||||||
self.commit(node_tks.len());
|
self.commit(node_tks.len());
|
||||||
|
let mut context = self.context.clone();
|
||||||
|
let assignments_span = assignments.get_span().unwrap();
|
||||||
|
context.push_back((
|
||||||
|
assignments_span.source().clone(),
|
||||||
|
Label::new(assignments_span)
|
||||||
|
.with_message("in variable assignment defined here".to_string())
|
||||||
|
.with_color(next_color())
|
||||||
|
));
|
||||||
return Ok(Some(Node {
|
return Ok(Some(Node {
|
||||||
class: NdRule::Command { assignments, argv },
|
class: NdRule::Command { assignments, argv },
|
||||||
tokens: node_tks,
|
tokens: node_tks,
|
||||||
@@ -1448,10 +1551,11 @@ impl ParseStream {
|
|||||||
let path_tk = tk_iter.next();
|
let path_tk = tk_iter.next();
|
||||||
|
|
||||||
if path_tk.is_none_or(|tk| tk.class == TkRule::EOI) {
|
if path_tk.is_none_or(|tk| tk.class == TkRule::EOI) {
|
||||||
return Err(ShErr::full(
|
self.panic_mode(&mut node_tks);
|
||||||
|
return Err(ShErr::at(
|
||||||
ShErrKind::ParseErr,
|
ShErrKind::ParseErr,
|
||||||
"Expected a filename after this redirection",
|
|
||||||
tk.span.clone(),
|
tk.span.clone(),
|
||||||
|
"Expected a filename after this redirection",
|
||||||
));
|
));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ fn cmd_not_found() {
|
|||||||
.next()
|
.next()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let err = ShErr::full(ShErrKind::CmdNotFound("foo".into()), "", token.span);
|
let err = ShErr::at(ShErrKind::CmdNotFound, token.span, "");
|
||||||
|
|
||||||
let err_fmt = format!("{err}");
|
let err_fmt = format!("{err}");
|
||||||
insta::assert_snapshot!(err_fmt)
|
insta::assert_snapshot!(err_fmt)
|
||||||
|
|||||||
Reference in New Issue
Block a user