More progress on integrating ariadne's error reporting

This commit is contained in:
2026-03-01 02:20:58 -05:00
parent 792b0c21d0
commit dff87fd5c2
19 changed files with 297 additions and 295 deletions

View File

@@ -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();
}

View File

@@ -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();
}

View File

@@ -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",
));
};