More progress on integrating ariadne's error reporting
This commit is contained in:
@@ -2,7 +2,8 @@ use std::{
|
||||
cell::Cell, collections::{HashSet, VecDeque}, os::unix::fs::PermissionsExt
|
||||
};
|
||||
|
||||
use ariadne::{Fmt, Label};
|
||||
|
||||
use ariadne::{Fmt, Label, Span as AriadneSpan};
|
||||
|
||||
use crate::{
|
||||
builtin::{
|
||||
@@ -294,10 +295,10 @@ impl Dispatcher {
|
||||
let name = name.span.as_str().strip_suffix("()").unwrap();
|
||||
|
||||
if KEYWORDS.contains(&name) {
|
||||
return Err(ShErr::full(
|
||||
return Err(ShErr::at(
|
||||
ShErrKind::SyntaxErr,
|
||||
format!("function: Forbidden function name `{name}`"),
|
||||
blame,
|
||||
format!("function: Forbidden function name `{name}`"),
|
||||
));
|
||||
}
|
||||
|
||||
@@ -342,6 +343,8 @@ impl Dispatcher {
|
||||
}
|
||||
fn exec_func(&mut self, func: Node) -> ShResult<()> {
|
||||
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 {
|
||||
assignments,
|
||||
mut argv,
|
||||
@@ -358,24 +361,25 @@ impl Dispatcher {
|
||||
});
|
||||
if depth > max_depth {
|
||||
RECURSE_DEPTH.with(|d| d.set(d.get() - 1));
|
||||
return Err(ShErr::full(
|
||||
return Err(ShErr::at(
|
||||
ShErrKind::InternalErr,
|
||||
format!("maximum recursion depth ({max_depth}) exceeded"),
|
||||
blame,
|
||||
format!("maximum recursion depth ({max_depth}) exceeded"),
|
||||
));
|
||||
}
|
||||
|
||||
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());
|
||||
|
||||
self.io_stack.append_to_frame(func.redirs);
|
||||
|
||||
let func_name = argv.remove(0).span.as_str().to_string();
|
||||
blame.rename(func_name.clone());
|
||||
|
||||
let argv = prepare_argv(argv)?;
|
||||
let result = if let Some(ref mut func_body) = read_logic(|l| l.get_func(&func_name)) {
|
||||
let _guard = ScopeGuard::exclusive_scope(Some(argv));
|
||||
func_body.body_mut().propagate_context(func_ctx);
|
||||
func_body.body_mut().flags = func.flags;
|
||||
|
||||
if let Err(e) = self.exec_brc_grp(func_body.body().clone()) {
|
||||
@@ -384,16 +388,16 @@ impl Dispatcher {
|
||||
state::set_status(*code);
|
||||
Ok(())
|
||||
}
|
||||
_ => Err(e),
|
||||
_ => Err(e)
|
||||
}
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
} else {
|
||||
Err(ShErr::full(
|
||||
Err(ShErr::at(
|
||||
ShErrKind::InternalErr,
|
||||
format!("Failed to find function '{}'", func_name),
|
||||
blame,
|
||||
format!("Failed to find function '{}'", func_name),
|
||||
))
|
||||
};
|
||||
|
||||
@@ -747,6 +751,7 @@ impl Dispatcher {
|
||||
}
|
||||
fn dispatch_builtin(&mut self, mut cmd: Node) -> ShResult<()> {
|
||||
let cmd_raw = cmd.get_command().unwrap().to_string();
|
||||
let context = cmd.context.clone();
|
||||
let NdRule::Command { assignments, argv } = &mut cmd.class else {
|
||||
unreachable!()
|
||||
};
|
||||
@@ -773,7 +778,7 @@ impl Dispatcher {
|
||||
}
|
||||
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),
|
||||
"cd" => cd(cmd, curr_job_mut),
|
||||
"export" => export(cmd, io_stack_mut, curr_job_mut),
|
||||
@@ -819,7 +824,13 @@ impl Dispatcher {
|
||||
Ok(())
|
||||
}
|
||||
_ => 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<()> {
|
||||
let context = cmd.context.clone();
|
||||
@@ -859,20 +870,13 @@ impl Dispatcher {
|
||||
let cmd_str = cmd.to_str().unwrap().to_string();
|
||||
match e {
|
||||
Errno::ENOENT => {
|
||||
let source = span.span_source().clone();
|
||||
let color = next_color();
|
||||
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)))
|
||||
)
|
||||
ShErr::new(ShErrKind::CmdNotFound, span.clone())
|
||||
.labeled(span, format!("{cmd_str}: command not found"))
|
||||
.with_context(context)
|
||||
.print_error();
|
||||
}
|
||||
_ => {
|
||||
ShErr::full(ShErrKind::Errno(e), format!("{e}"), span)
|
||||
ShErr::at(ShErrKind::Errno(e), span, format!("{e}"))
|
||||
.with_context(context)
|
||||
.print_error();
|
||||
}
|
||||
|
||||
@@ -354,10 +354,10 @@ impl LexStream {
|
||||
if !found_fd && !self.flags.contains(LexFlags::LEX_UNFINISHED) {
|
||||
let span_start = self.cursor;
|
||||
self.cursor = pos;
|
||||
return Some(Err(ShErr::full(
|
||||
return Some(Err(ShErr::at(
|
||||
ShErrKind::ParseErr,
|
||||
"Invalid redirection",
|
||||
Span::new(span_start..pos, self.source.clone()),
|
||||
"Invalid redirection",
|
||||
)));
|
||||
} else {
|
||||
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) {
|
||||
self.cursor = pos;
|
||||
return Err(ShErr::full(
|
||||
return Err(ShErr::at(
|
||||
ShErrKind::ParseErr,
|
||||
"Unclosed subshell",
|
||||
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) {
|
||||
self.cursor = pos;
|
||||
return Err(ShErr::full(
|
||||
return Err(ShErr::at(
|
||||
ShErrKind::ParseErr,
|
||||
"Unclosed subshell",
|
||||
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) {
|
||||
self.cursor = pos;
|
||||
return Err(ShErr::full(
|
||||
return Err(ShErr::at(
|
||||
ShErrKind::ParseErr,
|
||||
"Unclosed subshell",
|
||||
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) {
|
||||
self.cursor = pos;
|
||||
return Err(ShErr::full(
|
||||
return Err(ShErr::at(
|
||||
ShErrKind::ParseErr,
|
||||
"Unclosed subshell",
|
||||
Span::new(paren_pos..paren_pos + 1, self.source.clone()),
|
||||
"Unclosed subshell",
|
||||
));
|
||||
}
|
||||
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);
|
||||
if self.quote_state.in_quote() && !self.flags.contains(LexFlags::LEX_UNFINISHED) {
|
||||
self.cursor = pos;
|
||||
return Err(ShErr::full(
|
||||
return Err(ShErr::at(
|
||||
ShErrKind::ParseErr,
|
||||
"Unterminated quote",
|
||||
new_tk.span,
|
||||
"Unterminated quote",
|
||||
));
|
||||
}
|
||||
|
||||
@@ -750,10 +750,10 @@ impl Iterator for LexStream {
|
||||
if self.in_brc_grp() && !self.flags.contains(LexFlags::LEX_UNFINISHED) {
|
||||
let start = self.brc_grp_start.unwrap_or(self.cursor.saturating_sub(1));
|
||||
self.flags |= LexFlags::STALE;
|
||||
return Err(ShErr::full(
|
||||
return Err(ShErr::at(
|
||||
ShErrKind::ParseErr,
|
||||
"Unclosed brace group",
|
||||
Span::new(start..self.cursor, self.source.clone()),
|
||||
"Unclosed brace group",
|
||||
))
|
||||
.into();
|
||||
}
|
||||
@@ -789,10 +789,10 @@ impl Iterator for LexStream {
|
||||
if self.cursor == self.source.len() {
|
||||
if self.in_brc_grp() && !self.flags.contains(LexFlags::LEX_UNFINISHED) {
|
||||
let start = self.brc_grp_start.unwrap_or(self.cursor.saturating_sub(1));
|
||||
return Err(ShErr::full(
|
||||
return Err(ShErr::at(
|
||||
ShErrKind::ParseErr,
|
||||
"Unclosed brace group",
|
||||
Span::new(start..self.cursor, self.source.clone()),
|
||||
"Unclosed brace group",
|
||||
))
|
||||
.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 fmt::Display;
|
||||
use lex::{LexFlags, LexStream, Span, SpanSource, Tk, TkFlags, TkRule};
|
||||
@@ -9,7 +9,7 @@ use yansi::Color;
|
||||
use crate::{
|
||||
libsh::{
|
||||
error::{Note, ShErr, ShErrKind, ShResult, next_color},
|
||||
utils::TkVecUtils,
|
||||
utils::{NodeVecUtils, TkVecUtils},
|
||||
},
|
||||
prelude::*,
|
||||
procio::IoMode,
|
||||
@@ -56,7 +56,7 @@ impl ParsedSrc {
|
||||
src,
|
||||
ast: Ast::new(vec![]),
|
||||
lex_flags: LexFlags::empty(),
|
||||
context: vec![],
|
||||
context: VecDeque::new(),
|
||||
}
|
||||
}
|
||||
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>);
|
||||
|
||||
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)]
|
||||
pub struct Node {
|
||||
@@ -135,6 +135,108 @@ impl Node {
|
||||
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 {
|
||||
let Some(first_tk) = self.tokens.first() else {
|
||||
unreachable!()
|
||||
@@ -565,7 +667,7 @@ impl Debug for ParseStream {
|
||||
|
||||
impl ParseStream {
|
||||
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 {
|
||||
Self { tokens, context }
|
||||
@@ -726,7 +828,7 @@ impl ParseStream {
|
||||
src.rename(name_raw.clone());
|
||||
let color = next_color();
|
||||
// Push a placeholder context so child nodes inherit it
|
||||
self.context.push((
|
||||
self.context.push_back((
|
||||
src.clone(),
|
||||
Label::new(name_tk.span.clone().with_name(name_raw.clone()))
|
||||
.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 {
|
||||
self.context.pop();
|
||||
self.context.pop_back();
|
||||
return Err(parse_err_full(
|
||||
"Expected a brace group after function name",
|
||||
&node_tks.get_span().unwrap(),
|
||||
@@ -743,14 +845,7 @@ impl ParseStream {
|
||||
};
|
||||
body = Box::new(brc_grp);
|
||||
// Replace placeholder with full-span label
|
||||
self.context.pop();
|
||||
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),
|
||||
));
|
||||
self.context.pop_back();
|
||||
|
||||
let node = Node {
|
||||
class: NdRule::FuncDef { name, body },
|
||||
@@ -760,7 +855,7 @@ impl ParseStream {
|
||||
context: self.context.clone()
|
||||
};
|
||||
|
||||
self.context.pop();
|
||||
self.context.pop_back();
|
||||
Ok(Some(node))
|
||||
}
|
||||
fn panic_mode(&mut self, node_tks: &mut Vec<Tk>) {
|
||||
@@ -906,10 +1001,10 @@ impl ParseStream {
|
||||
let path_tk = self.next_tk();
|
||||
|
||||
if path_tk.clone().is_none_or(|tk| tk.class == TkRule::EOI) {
|
||||
return Err(ShErr::full(
|
||||
return Err(ShErr::at(
|
||||
ShErrKind::ParseErr,
|
||||
"Expected a filename after this redirection",
|
||||
tk.span.clone(),
|
||||
"Expected a filename after this redirection",
|
||||
));
|
||||
};
|
||||
|
||||
@@ -1412,6 +1507,14 @@ impl ParseStream {
|
||||
// If we have assignments but no command word,
|
||||
// return the assignment-only command without parsing more tokens
|
||||
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 {
|
||||
class: NdRule::Command { assignments, argv },
|
||||
tokens: node_tks,
|
||||
@@ -1448,10 +1551,11 @@ impl ParseStream {
|
||||
let path_tk = tk_iter.next();
|
||||
|
||||
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,
|
||||
"Expected a filename after this redirection",
|
||||
tk.span.clone(),
|
||||
"Expected a filename after this redirection",
|
||||
));
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user