rustfmt'd the codebase

This commit is contained in:
2026-03-04 19:52:29 -05:00
parent ecd6eda424
commit 7be79a3803
51 changed files with 4926 additions and 4131 deletions

View File

@@ -1,15 +1,37 @@
use std::{
cell::Cell, collections::{HashSet, VecDeque}, os::unix::fs::PermissionsExt
cell::Cell,
collections::{HashSet, VecDeque},
os::unix::fs::PermissionsExt,
};
use ariadne::Fmt;
use crate::{
builtin::{
alias::{alias, unalias}, arrops::{arr_fpop, arr_fpush, arr_pop, arr_push, arr_rotate}, autocmd::autocmd, cd::cd, complete::{compgen_builtin, complete_builtin}, dirstack::{dirs, popd, pushd}, echo::echo, eval, exec, flowctl::flowctl, getopts::getopts, intro, jobctl::{self, JobBehavior, continue_job, disown, jobs}, keymap, map, pwd::pwd, read::{self, read_builtin}, shift::shift, shopt::shopt, source::source, test::double_bracket_test, trap::{TrapTarget, trap}, varcmds::{export, local, readonly, unset}, zoltraak::zoltraak
alias::{alias, unalias},
arrops::{arr_fpop, arr_fpush, arr_pop, arr_push, arr_rotate},
autocmd::autocmd,
cd::cd,
complete::{compgen_builtin, complete_builtin},
dirstack::{dirs, popd, pushd},
echo::echo,
eval, exec,
flowctl::flowctl,
getopts::getopts,
intro,
jobctl::{self, JobBehavior, continue_job, disown, jobs},
keymap, map,
pwd::pwd,
read::{self, read_builtin},
shift::shift,
shopt::shopt,
source::source,
test::double_bracket_test,
trap::{TrapTarget, trap},
varcmds::{export, local, readonly, unset},
zoltraak::zoltraak,
},
expand::{Expander, expand_aliases, expand_case_pattern, expand_raw, glob_to_regex},
expand::{expand_aliases, expand_case_pattern, glob_to_regex},
jobs::{ChildProc, JobStack, attach_tty, dispatch_job},
libsh::{
error::{ShErr, ShErrKind, ShResult, ShResultExt, next_color},
@@ -19,7 +41,7 @@ use crate::{
prelude::*,
procio::{IoMode, IoStack},
state::{
self, ShFunc, VarFlags, VarKind, read_logic, read_shopts, write_jobs, write_logic, write_vars
self, ShFunc, VarFlags, VarKind, read_logic, read_shopts, write_jobs, write_logic, write_vars,
},
};
@@ -110,7 +132,12 @@ impl ExecArgs {
}
}
pub fn exec_input(input: String, io_stack: Option<IoStack>, interactive: bool, source_name: Option<String>) -> ShResult<()> {
pub fn exec_input(
input: String,
io_stack: Option<IoStack>,
interactive: bool,
source_name: Option<String>,
) -> ShResult<()> {
let log_tab = read_logic(|l| l.clone());
let input = expand_aliases(input, HashSet::new(), &log_tab);
let lex_flags = if interactive {
@@ -118,8 +145,10 @@ pub fn exec_input(input: String, io_stack: Option<IoStack>, interactive: bool, s
} else {
super::lex::LexFlags::empty()
};
let source_name = source_name.unwrap_or("<stdin>".into());
let mut parser = ParsedSrc::new(Arc::new(input)).with_lex_flags(lex_flags).with_name(source_name.clone());
let source_name = source_name.unwrap_or("<stdin>".into());
let mut parser = ParsedSrc::new(Arc::new(input))
.with_lex_flags(lex_flags)
.with_name(source_name.clone());
if let Err(errors) = parser.parse_src() {
for error in errors {
error.print_error();
@@ -149,7 +178,7 @@ pub fn exec_input(input: String, io_stack: Option<IoStack>, interactive: bool, s
pub struct Dispatcher {
nodes: VecDeque<Node>,
interactive: bool,
source_name: String,
source_name: String,
pub io_stack: IoStack,
pub job_stack: JobStack,
fg_job: bool,
@@ -161,7 +190,7 @@ impl Dispatcher {
Self {
nodes,
interactive,
source_name,
source_name,
io_stack: IoStack::new(),
job_stack: JobStack::new(),
fg_job: true,
@@ -208,7 +237,12 @@ impl Dispatcher {
let stack = IoStack {
stack: self.io_stack.clone(),
};
exec_input(format!("cd {dir}"), Some(stack), self.interactive, Some(self.source_name.clone()))
exec_input(
format!("cd {dir}"),
Some(stack),
self.interactive,
Some(self.source_name.clone()),
)
} else {
self.exec_cmd(node)
}
@@ -274,16 +308,16 @@ impl Dispatcher {
return Ok(());
}
let func = ShFunc::new(func_parser,blame);
let func = ShFunc::new(func_parser, blame);
write_logic(|l| l.insert_func(name, func)); // Store the AST
Ok(())
}
fn exec_subsh(&mut self, subsh: Node) -> ShResult<()> {
let blame = subsh.get_span().clone();
let blame = subsh.get_span().clone();
let NdRule::Command { assignments, argv } = subsh.class else {
unreachable!()
};
let name = self.source_name.clone();
let name = self.source_name.clone();
self.run_fork("anonymous_subshell", |s| {
if let Err(e) = s.set_assignments(assignments, AssignBehavior::Export) {
@@ -309,8 +343,11 @@ 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 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,
@@ -340,12 +377,12 @@ impl Dispatcher {
self.io_stack.append_to_frame(func.redirs);
blame.rename(func_name.clone());
blame.rename(func_name.clone());
let argv = prepare_argv(argv).try_blame(blame.clone())?;
let result = if let Some(ref mut func_body) = read_logic(|l| l.get_func(&func_name)) {
let _guard = scope_guard(Some(argv));
func_body.body_mut().propagate_context(func_ctx);
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()) {
@@ -354,7 +391,7 @@ impl Dispatcher {
state::set_status(*code);
Ok(())
}
_ => Err(e)
_ => Err(e),
}
} else {
Ok(())
@@ -423,12 +460,17 @@ impl Dispatcher {
'outer: for block in case_blocks {
let CaseNode { pattern, body } = block;
let block_pattern_raw = pattern.span.as_str().strip_suffix(')').unwrap_or(pattern.span.as_str()).trim();
let block_pattern_raw = pattern
.span
.as_str()
.strip_suffix(')')
.unwrap_or(pattern.span.as_str())
.trim();
// Split at '|' to allow for multiple patterns like `foo|bar)`
let block_patterns = block_pattern_raw.split('|');
for pattern in block_patterns {
let pattern_exp = expand_case_pattern(pattern)?;
let pattern_exp = expand_case_pattern(pattern)?;
let pattern_regex = glob_to_regex(&pattern_exp, false);
if pattern_regex.is_match(&pattern_raw) {
for node in &body {
@@ -450,7 +492,9 @@ impl Dispatcher {
}
})
} else {
case_logic(self).try_blame(blame).map_err(|e| e.with_redirs(guard))
case_logic(self)
.try_blame(blame)
.map_err(|e| e.with_redirs(guard))
}
}
fn exec_loop(&mut self, loop_stmt: Node) -> ShResult<()> {
@@ -513,7 +557,9 @@ impl Dispatcher {
}
})
} else {
loop_logic(self).try_blame(blame).map_err(|e| e.with_redirs(guard))
loop_logic(self)
.try_blame(blame)
.map_err(|e| e.with_redirs(guard))
}
}
fn exec_for(&mut self, for_stmt: Node) -> ShResult<()> {
@@ -591,7 +637,9 @@ impl Dispatcher {
}
})
} else {
for_logic(self).try_blame(blame).map_err(|e| e.with_redirs(guard))
for_logic(self)
.try_blame(blame)
.map_err(|e| e.with_redirs(guard))
}
}
fn exec_if(&mut self, if_stmt: Node) -> ShResult<()> {
@@ -648,7 +696,9 @@ impl Dispatcher {
}
})
} else {
if_logic(self).try_blame(blame).map_err(|e| e.with_redirs(guard))
if_logic(self)
.try_blame(blame)
.map_err(|e| e.with_redirs(guard))
}
}
fn exec_pipeline(&mut self, pipeline: Node) -> ShResult<()> {
@@ -692,11 +742,14 @@ impl Dispatcher {
// SIGTTOU when they try to modify terminal attributes.
// Only for interactive (top-level) pipelines — command substitution
// and other non-interactive contexts must not steal the terminal.
if !tty_attached && !is_bg && self.interactive
&& let Some(pgid) = self.job_stack.curr_job_mut().unwrap().pgid() {
attach_tty(pgid).ok();
tty_attached = true;
}
if !tty_attached
&& !is_bg
&& self.interactive
&& let Some(pgid) = self.job_stack.curr_job_mut().unwrap().pgid()
{
attach_tty(pgid).ok();
tty_attached = true;
}
}
let job = self.job_stack.finalize_job().unwrap();
dispatch_job(job, is_bg, self.interactive)?;
@@ -732,7 +785,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 context = cmd.context.clone();
let NdRule::Command { assignments, argv } = &mut cmd.class else {
unreachable!()
};
@@ -815,18 +868,18 @@ impl Dispatcher {
"unset" => unset(cmd),
"complete" => complete_builtin(cmd),
"compgen" => compgen_builtin(cmd),
"map" => map::map(cmd),
"pop" => arr_pop(cmd),
"fpop" => arr_fpop(cmd),
"push" => arr_push(cmd),
"fpush" => arr_fpush(cmd),
"rotate" => arr_rotate(cmd),
"wait" => jobctl::wait(cmd),
"type" => intro::type_builtin(cmd),
"getopts" => getopts(cmd),
"keymap" => keymap::keymap(cmd),
"read_key" => read::read_key(cmd),
"autocmd" => autocmd(cmd),
"map" => map::map(cmd),
"pop" => arr_pop(cmd),
"fpop" => arr_fpop(cmd),
"push" => arr_push(cmd),
"fpush" => arr_fpush(cmd),
"rotate" => arr_rotate(cmd),
"wait" => jobctl::wait(cmd),
"type" => intro::type_builtin(cmd),
"getopts" => getopts(cmd),
"keymap" => keymap::keymap(cmd),
"read_key" => read::read_key(cmd),
"autocmd" => autocmd(cmd),
"true" | ":" => {
state::set_status(0);
Ok(())
@@ -838,17 +891,17 @@ impl Dispatcher {
_ => unimplemented!("Have not yet added support for builtin '{}'", cmd_raw),
};
if let Err(e) = result {
if !e.is_flow_control() {
state::set_status(1);
}
Err(e.with_context(context).with_redirs(redir_guard))
} else {
Ok(())
}
if let Err(e) = result {
if !e.is_flow_control() {
state::set_status(1);
}
Err(e.with_context(context).with_redirs(redir_guard))
} else {
Ok(())
}
}
fn exec_cmd(&mut self, cmd: Node) -> ShResult<()> {
let blame = cmd.get_span().clone();
let blame = cmd.get_span().clone();
let context = cmd.context.clone();
let NdRule::Command { assignments, argv } = cmd.class else {
unreachable!()
@@ -887,7 +940,7 @@ impl Dispatcher {
// For foreground jobs, take the terminal BEFORE resetting
// signals. SIGTTOU is still SIG_IGN (inherited from the shell),
// so tcsetpgrp won't stop us. This prevents a race
// so tcsetpgrp won't stop us. This prevents a race
// where the child exec's and tries to read stdin before the
// parent has called tcsetpgrp — which would deliver SIGTTIN
// (now SIG_DFL after reset_signals) and stop the child.
@@ -918,14 +971,14 @@ impl Dispatcher {
match e {
Errno::ENOENT => {
ShErr::new(ShErrKind::NotFound, span.clone())
.labeled(span, format!("{cmd_str}: command not found"))
.with_context(context)
.print_error();
.labeled(span, format!("{cmd_str}: command not found"))
.with_context(context)
.print_error();
}
_ => {
ShErr::at(ShErrKind::Errno(e), span, format!("{e}"))
.with_context(context)
.print_error();
.with_context(context)
.print_error();
}
}
exit(e as i32)

View File

@@ -25,98 +25,101 @@ pub const KEYWORDS: [&str; 16] = [
pub const OPENERS: [&str; 6] = ["if", "while", "until", "for", "select", "case"];
/// Used to track whether the lexer is currently inside a quote, and if so, which type
#[derive(Default,Debug)]
#[derive(Default, Debug)]
pub enum QuoteState {
#[default]
Outside,
Single,
Double
#[default]
Outside,
Single,
Double,
}
impl QuoteState {
pub fn outside(&self) -> bool {
matches!(self, QuoteState::Outside)
}
pub fn in_single(&self) -> bool {
matches!(self, QuoteState::Single)
}
pub fn in_double(&self) -> bool {
matches!(self, QuoteState::Double)
}
pub fn in_quote(&self) -> bool {
!self.outside()
}
/// Toggles whether we are in a double quote. If self = QuoteState::Single, this does nothing, since double quotes inside single quotes are just literal characters
pub fn toggle_double(&mut self) {
match self {
QuoteState::Outside => *self = QuoteState::Double,
QuoteState::Double => *self = QuoteState::Outside,
_ => {}
}
}
/// Toggles whether we are in a single quote. If self == QuoteState::Double, this does nothing, since single quotes are not interpreted inside double quotes
pub fn toggle_single(&mut self) {
match self {
QuoteState::Outside => *self = QuoteState::Single,
QuoteState::Single => *self = QuoteState::Outside,
_ => {}
}
}
pub fn outside(&self) -> bool {
matches!(self, QuoteState::Outside)
}
pub fn in_single(&self) -> bool {
matches!(self, QuoteState::Single)
}
pub fn in_double(&self) -> bool {
matches!(self, QuoteState::Double)
}
pub fn in_quote(&self) -> bool {
!self.outside()
}
/// Toggles whether we are in a double quote. If self = QuoteState::Single, this does nothing, since double quotes inside single quotes are just literal characters
pub fn toggle_double(&mut self) {
match self {
QuoteState::Outside => *self = QuoteState::Double,
QuoteState::Double => *self = QuoteState::Outside,
_ => {}
}
}
/// Toggles whether we are in a single quote. If self == QuoteState::Double, this does nothing, since single quotes are not interpreted inside double quotes
pub fn toggle_single(&mut self) {
match self {
QuoteState::Outside => *self = QuoteState::Single,
QuoteState::Single => *self = QuoteState::Outside,
_ => {}
}
}
}
#[derive(Clone, PartialEq, Default, Debug, Eq, Hash)]
pub struct SpanSource {
name: String,
content: Arc<String>
name: String,
content: Arc<String>,
}
impl SpanSource {
pub fn name(&self) -> &str {
&self.name
}
pub fn content(&self) -> Arc<String> {
self.content.clone()
}
pub fn rename(&mut self, name: String) {
self.name = name;
}
pub fn name(&self) -> &str {
&self.name
}
pub fn content(&self) -> Arc<String> {
self.content.clone()
}
pub fn rename(&mut self, name: String) {
self.name = name;
}
}
impl Display for SpanSource {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.name)
}
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.name)
}
}
/// Span::new(10..20)
#[derive(Clone, PartialEq, Default, Debug)]
pub struct Span {
range: Range<usize>,
source: SpanSource
source: SpanSource,
}
impl Span {
/// New `Span`. Wraps a range and a string slice that it refers to.
pub fn new(range: Range<usize>, source: Arc<String>) -> Self {
let source = SpanSource { name: "<stdin>".into(), content: source };
let source = SpanSource {
name: "<stdin>".into(),
content: source,
};
Span { range, source }
}
pub fn from_span_source(range: Range<usize>, source: SpanSource) -> Self {
Span { range, source }
}
pub fn rename(&mut self, name: String) {
self.source.name = name;
}
pub fn with_name(mut self, name: String) -> Self {
self.source.name = name;
self
}
pub fn line_and_col(&self) -> (usize,usize) {
let content = self.source.content();
let source = ariadne::Source::from(content.as_str());
let (_, line, col) = source.get_byte_line(self.range.start).unwrap();
(line, col)
}
pub fn rename(&mut self, name: String) {
self.source.name = name;
}
pub fn with_name(mut self, name: String) -> Self {
self.source.name = name;
self
}
pub fn line_and_col(&self) -> (usize, usize) {
let content = self.source.content();
let source = ariadne::Source::from(content.as_str());
let (_, line, col) = source.get_byte_line(self.range.start).unwrap();
(line, col)
}
/// Slice the source string at the wrapped range
pub fn as_str(&self) -> &str {
&self.source.content[self.range().start..self.range().end]
@@ -138,19 +141,19 @@ impl Span {
}
impl ariadne::Span for Span {
type SourceId = SpanSource;
type SourceId = SpanSource;
fn source(&self) -> &Self::SourceId {
&self.source
}
fn source(&self) -> &Self::SourceId {
&self.source
}
fn start(&self) -> usize {
self.range.start
}
fn start(&self) -> usize {
self.range.start
}
fn end(&self) -> usize {
self.range.end
}
fn end(&self) -> usize {
self.range.end
}
}
/// Allows simple access to the underlying range wrapped by the span
@@ -243,7 +246,7 @@ bitflags! {
pub struct LexStream {
source: Arc<String>,
pub cursor: usize,
pub name: String,
pub name: String,
quote_state: QuoteState,
brc_grp_depth: usize,
brc_grp_start: Option<usize>,
@@ -273,23 +276,23 @@ bitflags! {
}
pub fn clean_input(input: &str) -> String {
let mut chars = input.chars().peekable();
let mut output = String::new();
while let Some(ch) = chars.next() {
match ch {
'\\' if chars.peek() == Some(&'\n') => {
chars.next();
}
'\r' => {
if chars.peek() == Some(&'\n') {
chars.next();
}
output.push('\n');
}
_ => output.push(ch),
}
}
output
let mut chars = input.chars().peekable();
let mut output = String::new();
while let Some(ch) = chars.next() {
match ch {
'\\' if chars.peek() == Some(&'\n') => {
chars.next();
}
'\r' => {
if chars.peek() == Some(&'\n') {
chars.next();
}
output.push('\n');
}
_ => output.push(ch),
}
}
output
}
impl LexStream {
@@ -298,7 +301,7 @@ impl LexStream {
Self {
flags,
source,
name: "<stdin>".into(),
name: "<stdin>".into(),
cursor: 0,
quote_state: QuoteState::default(),
brc_grp_depth: 0,
@@ -327,10 +330,10 @@ impl LexStream {
};
self.source.get(start..end)
}
pub fn with_name(mut self, name: String) -> Self {
self.name = name;
self
}
pub fn with_name(mut self, name: String) -> Self {
self.name = name;
self
}
pub fn slice_from_cursor(&self) -> Option<&str> {
self.slice(self.cursor..)
}
@@ -475,11 +478,11 @@ impl LexStream {
pos += ch.len_utf8();
}
}
'\'' => {
pos += 1;
self.quote_state.toggle_single();
}
_ if self.quote_state.in_single() => pos += ch.len_utf8(),
'\'' => {
pos += 1;
self.quote_state.toggle_single();
}
_ if self.quote_state.in_single() => pos += ch.len_utf8(),
'$' if chars.peek() == Some(&'(') => {
pos += 2;
chars.next();
@@ -543,11 +546,11 @@ impl LexStream {
}
}
}
'"' => {
pos += 1;
self.quote_state.toggle_double();
}
_ if self.quote_state.in_double() => pos += ch.len_utf8(),
'"' => {
pos += 1;
self.quote_state.toggle_double();
}
_ if self.quote_state.in_double() => pos += ch.len_utf8(),
'<' if chars.peek() == Some(&'(') => {
pos += 2;
chars.next();
@@ -770,7 +773,7 @@ impl LexStream {
}
pub fn get_token(&self, range: Range<usize>, class: TkRule) -> Tk {
let mut span = Span::new(range, self.source.clone());
span.rename(self.name.clone());
span.rename(self.name.clone());
Tk::new(class, span)
}
}
@@ -845,15 +848,15 @@ impl Iterator for LexStream {
self.set_next_is_cmd(true);
while let Some(ch) = get_char(&self.source, self.cursor) {
match ch {
'\\' if get_char(&self.source, self.cursor + 1) == Some('\n') => {
self.cursor = (self.cursor + 2).min(self.source.len());
}
_ if is_hard_sep(ch) => {
self.cursor += 1;
}
_ => break,
}
match ch {
'\\' if get_char(&self.source, self.cursor + 1) == Some('\n') => {
self.cursor = (self.cursor + 2).min(self.source.len());
}
_ if is_hard_sep(ch) => {
self.cursor += 1;
}
_ => break,
}
}
self.get_token(ch_idx..self.cursor, TkRule::Sep)
}
@@ -974,84 +977,101 @@ pub fn ends_with_unescaped(slice: &str, pat: &str) -> bool {
/// Splits a string by a pattern, but only if the pattern is not escaped by a backslash
/// and not in quotes.
pub fn split_all_unescaped(slice: &str, pat: &str) -> Vec<String> {
let mut cursor = 0;
let mut splits = vec![];
while let Some(split) = split_at_unescaped(&slice[cursor..], pat) {
cursor += split.0.len() + pat.len();
splits.push(split.0);
}
if let Some(remaining) = slice.get(cursor..) {
splits.push(remaining.to_string());
}
splits
let mut cursor = 0;
let mut splits = vec![];
while let Some(split) = split_at_unescaped(&slice[cursor..], pat) {
cursor += split.0.len() + pat.len();
splits.push(split.0);
}
if let Some(remaining) = slice.get(cursor..) {
splits.push(remaining.to_string());
}
splits
}
/// Splits a string at the first occurrence of a pattern, but only if the pattern is not escaped by a backslash
/// and not in quotes. Returns None if the pattern is not found or only found escaped.
pub fn split_at_unescaped(slice: &str, pat: &str) -> Option<(String,String)> {
let mut chars = slice.char_indices().peekable();
let mut qt_state = QuoteState::default();
pub fn split_at_unescaped(slice: &str, pat: &str) -> Option<(String, String)> {
let mut chars = slice.char_indices().peekable();
let mut qt_state = QuoteState::default();
while let Some((i, ch)) = chars.next() {
match ch {
'\\' => { chars.next(); continue; }
'\'' => qt_state.toggle_single(),
'"' => qt_state.toggle_double(),
_ if qt_state.in_quote() => continue,
_ => {}
}
while let Some((i, ch)) = chars.next() {
match ch {
'\\' => {
chars.next();
continue;
}
'\'' => qt_state.toggle_single(),
'"' => qt_state.toggle_double(),
_ if qt_state.in_quote() => continue,
_ => {}
}
if slice[i..].starts_with(pat) {
let before = slice[..i].to_string();
let after = slice[i + pat.len()..].to_string();
return Some((before, after));
}
}
if slice[i..].starts_with(pat) {
let before = slice[..i].to_string();
let after = slice[i + pat.len()..].to_string();
return Some((before, after));
}
}
None
None
}
pub fn split_tk(tk: &Tk, pat: &str) -> Vec<Tk> {
let slice = tk.as_str();
let mut cursor = 0;
let mut splits = vec![];
while let Some(split) = split_at_unescaped(&slice[cursor..], pat) {
let before_span = Span::new(tk.span.range().start + cursor..tk.span.range().start + cursor + split.0.len(), tk.source().clone());
splits.push(Tk::new(tk.class.clone(), before_span));
cursor += split.0.len() + pat.len();
}
if slice.get(cursor..).is_some_and(|s| !s.is_empty()) {
let remaining_span = Span::new(tk.span.range().start + cursor..tk.span.range().end, tk.source().clone());
splits.push(Tk::new(tk.class.clone(), remaining_span));
}
splits
let slice = tk.as_str();
let mut cursor = 0;
let mut splits = vec![];
while let Some(split) = split_at_unescaped(&slice[cursor..], pat) {
let before_span = Span::new(
tk.span.range().start + cursor..tk.span.range().start + cursor + split.0.len(),
tk.source().clone(),
);
splits.push(Tk::new(tk.class.clone(), before_span));
cursor += split.0.len() + pat.len();
}
if slice.get(cursor..).is_some_and(|s| !s.is_empty()) {
let remaining_span = Span::new(
tk.span.range().start + cursor..tk.span.range().end,
tk.source().clone(),
);
splits.push(Tk::new(tk.class.clone(), remaining_span));
}
splits
}
pub fn split_tk_at(tk: &Tk, pat: &str) -> Option<(Tk, Tk)> {
let slice = tk.as_str();
let mut chars = slice.char_indices().peekable();
let mut qt_state = QuoteState::default();
let slice = tk.as_str();
let mut chars = slice.char_indices().peekable();
let mut qt_state = QuoteState::default();
while let Some((i, ch)) = chars.next() {
match ch {
'\\' => { chars.next(); continue; }
'\'' => qt_state.toggle_single(),
'"' => qt_state.toggle_double(),
_ if qt_state.in_quote() => continue,
_ => {}
}
while let Some((i, ch)) = chars.next() {
match ch {
'\\' => {
chars.next();
continue;
}
'\'' => qt_state.toggle_single(),
'"' => qt_state.toggle_double(),
_ if qt_state.in_quote() => continue,
_ => {}
}
if slice[i..].starts_with(pat) {
let before_span = Span::new(tk.span.range().start..tk.span.range().start + i, tk.source().clone());
let after_span = Span::new(tk.span.range().start + i + pat.len()..tk.span.range().end, tk.source().clone());
let before_tk = Tk::new(tk.class.clone(), before_span);
let after_tk = Tk::new(tk.class.clone(), after_span);
return Some((before_tk, after_tk));
}
}
if slice[i..].starts_with(pat) {
let before_span = Span::new(
tk.span.range().start..tk.span.range().start + i,
tk.source().clone(),
);
let after_span = Span::new(
tk.span.range().start + i + pat.len()..tk.span.range().end,
tk.source().clone(),
);
let before_tk = Tk::new(tk.class.clone(), before_span);
let after_tk = Tk::new(tk.class.clone(), after_span);
return Some((before_tk, after_tk));
}
}
None
None
}
pub fn pos_is_escaped(slice: &str, pos: usize) -> bool {
@@ -1083,7 +1103,7 @@ pub fn lookahead(pat: &str, mut chars: Chars) -> Option<usize> {
pub fn case_pat_lookahead(mut chars: Peekable<Chars>) -> Option<usize> {
let mut pos = 0;
let mut qt_state = QuoteState::default();
let mut qt_state = QuoteState::default();
while let Some(ch) = chars.next() {
pos += ch.len_utf8();
match ch {
@@ -1108,12 +1128,12 @@ pub fn case_pat_lookahead(mut chars: Peekable<Chars>) -> Option<usize> {
}
}
}
'\'' => {
qt_state.toggle_single();
}
'"' => {
qt_state.toggle_double();
}
'\'' => {
qt_state.toggle_single();
}
'"' => {
qt_state.toggle_double();
}
')' if qt_state.outside() => return Some(pos),
'(' if qt_state.outside() => return None,
_ => { /* continue */ }

View File

@@ -9,7 +9,10 @@ use crate::{
libsh::{
error::{ShErr, ShErrKind, ShResult, last_color, next_color},
utils::{NodeVecUtils, TkVecUtils},
}, parse::lex::clean_input, prelude::*, procio::IoMode
},
parse::lex::clean_input,
prelude::*,
procio::IoMode,
};
pub mod execute;
@@ -42,7 +45,7 @@ macro_rules! try_match {
#[derive(Clone, Debug)]
pub struct ParsedSrc {
pub src: Arc<String>,
pub name: String,
pub name: String,
pub ast: Ast,
pub lex_flags: LexFlags,
pub context: LabelCtx,
@@ -57,16 +60,16 @@ impl ParsedSrc {
};
Self {
src,
name: "<stdin>".into(),
name: "<stdin>".into(),
ast: Ast::new(vec![]),
lex_flags: LexFlags::empty(),
context: VecDeque::new(),
}
}
pub fn with_name(mut self, name: String) -> Self {
self.name = name;
self
}
pub fn with_name(mut self, name: String) -> Self {
self.name = name;
self
}
pub fn with_lex_flags(mut self, flags: LexFlags) -> Self {
self.lex_flags = flags;
self
@@ -77,7 +80,8 @@ impl ParsedSrc {
}
pub fn parse_src(&mut self) -> Result<(), Vec<ShErr>> {
let mut tokens = vec![];
for lex_result in LexStream::new(self.src.clone(), self.lex_flags).with_name(self.name.clone()) {
for lex_result in LexStream::new(self.src.clone(), self.lex_flags).with_name(self.name.clone())
{
match lex_result {
Ok(token) => tokens.push(token),
Err(error) => return Err(vec![error]),
@@ -128,7 +132,7 @@ pub struct Node {
pub flags: NdFlags,
pub redirs: Vec<Redir>,
pub tokens: Vec<Tk>,
pub context: LabelCtx,
pub context: LabelCtx,
}
impl Node {
@@ -143,108 +147,108 @@ impl Node {
None
}
}
pub fn get_context(&self, msg: String) -> (SpanSource, Label<Span>) {
let color = last_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);
pub fn get_context(&self, msg: String) -> (SpanSource, Label<Span>) {
let color = last_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);
}
}
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()));
}
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!()
@@ -662,20 +666,23 @@ pub enum NdRule {
pub struct ParseStream {
pub tokens: Vec<Tk>,
pub context: LabelCtx
pub context: LabelCtx,
}
impl Debug for ParseStream {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ParseStream")
.field("tokens", &self.tokens)
.finish()
}
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ParseStream")
.field("tokens", &self.tokens)
.finish()
}
}
impl ParseStream {
pub fn new(tokens: Vec<Tk>) -> Self {
Self { tokens, context: VecDeque::new() }
Self {
tokens,
context: VecDeque::new(),
}
}
pub fn with_context(tokens: Vec<Tk>, context: LabelCtx) -> Self {
Self { tokens, context }
@@ -831,39 +838,42 @@ impl ParseStream {
let name_tk = self.next_tk().unwrap();
node_tks.push(name_tk.clone());
let name = name_tk.clone();
let name_raw = name.to_string();
let mut src = name_tk.span.span_source().clone();
src.rename(name_raw.clone());
let color = next_color();
// Push a placeholder context so child nodes inherit it
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)))
.with_color(color),
));
let name_raw = name.to_string();
let mut src = name_tk.span.span_source().clone();
src.rename(name_raw.clone());
let color = next_color();
// Push a placeholder context so child nodes inherit it
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)
))
.with_color(color),
));
let Some(brc_grp) = self.parse_brc_grp(true /* from_func_def */)? else {
self.context.pop_back();
self.context.pop_back();
return Err(parse_err_full(
"Expected a brace group after function name",
&node_tks.get_span().unwrap(),
self.context.clone()
self.context.clone(),
));
};
body = Box::new(brc_grp);
// Replace placeholder with full-span label
self.context.pop_back();
// Replace placeholder with full-span label
self.context.pop_back();
let node = Node {
class: NdRule::FuncDef { name, body },
flags: NdFlags::empty(),
redirs: vec![],
tokens: node_tks,
context: self.context.clone()
context: self.context.clone(),
};
self.context.pop_back();
self.context.pop_back();
Ok(Some(node))
}
fn panic_mode(&mut self, node_tks: &mut Vec<Tk>) {
@@ -893,7 +903,7 @@ impl ParseStream {
return Err(parse_err_full(
"Malformed test call",
&node_tks.get_span().unwrap(),
self.context.clone()
self.context.clone(),
));
} else {
break;
@@ -920,7 +930,7 @@ impl ParseStream {
return Err(parse_err_full(
"Invalid placement for logical operator in test",
&node_tks.get_span().unwrap(),
self.context.clone()
self.context.clone(),
));
}
let op = match tk.class {
@@ -936,7 +946,7 @@ impl ParseStream {
return Err(parse_err_full(
"Invalid placement for logical operator in test",
&node_tks.get_span().unwrap(),
self.context.clone()
self.context.clone(),
));
}
}
@@ -982,7 +992,7 @@ impl ParseStream {
return Err(parse_err_full(
"Expected a closing brace for this brace group",
&node_tks.get_span().unwrap(),
self.context.clone()
self.context.clone(),
));
}
}
@@ -1049,11 +1059,9 @@ impl ParseStream {
let pat_err = parse_err_full(
"Expected a pattern after 'case' keyword",
&node_tks.get_span().unwrap(),
self.context.clone()
self.context.clone(),
)
.with_note(
"Patterns can be raw text, or anything that gets substituted with raw text"
);
.with_note("Patterns can be raw text, or anything that gets substituted with raw text");
let Some(pat_tk) = self.next_tk() else {
self.panic_mode(&mut node_tks);
@@ -1073,7 +1081,7 @@ impl ParseStream {
return Err(parse_err_full(
"Expected 'in' after case variable name",
&node_tks.get_span().unwrap(),
self.context.clone()
self.context.clone(),
));
}
node_tks.push(self.next_tk().unwrap());
@@ -1086,7 +1094,7 @@ impl ParseStream {
return Err(parse_err_full(
"Expected a case pattern here",
&node_tks.get_span().unwrap(),
self.context.clone()
self.context.clone(),
));
}
let case_pat_tk = self.next_tk().unwrap();
@@ -1123,7 +1131,7 @@ impl ParseStream {
return Err(parse_err_full(
"Expected 'esac' after case block",
&node_tks.get_span().unwrap(),
self.context.clone()
self.context.clone(),
));
}
}
@@ -1140,12 +1148,12 @@ impl ParseStream {
};
Ok(Some(node))
}
fn make_err(&self, span: lex::Span, label: Label<lex::Span>) -> ShErr {
let src = span.span_source().clone();
ShErr::new(ShErrKind::ParseErr, span)
.with_label(src, label)
.with_context(self.context.clone())
}
fn make_err(&self, span: lex::Span, label: Label<lex::Span>) -> ShErr {
let src = span.span_source().clone();
ShErr::new(ShErrKind::ParseErr, span)
.with_label(src, label)
.with_context(self.context.clone())
}
fn parse_if(&mut self) -> ShResult<Option<Node>> {
// Needs at last one 'if-then',
// Any number of 'elif-then',
@@ -1164,14 +1172,19 @@ impl ParseStream {
let prefix_keywrd = if cond_nodes.is_empty() { "if" } else { "elif" };
let Some(cond) = self.parse_cmd_list()? else {
self.panic_mode(&mut node_tks);
let span = node_tks.get_span().unwrap();
let color = next_color();
return Err(self.make_err(span.clone(),
Label::new(span)
.with_message(format!("Expected an expression after '{}'", prefix_keywrd.fg(color)))
.with_color(color)
));
let span = node_tks.get_span().unwrap();
let color = next_color();
return Err(
self.make_err(
span.clone(),
Label::new(span)
.with_message(format!(
"Expected an expression after '{}'",
prefix_keywrd.fg(color)
))
.with_color(color),
),
);
};
node_tks.extend(cond.tokens.clone());
@@ -1180,7 +1193,7 @@ impl ParseStream {
return Err(parse_err_full(
&format!("Expected 'then' after '{prefix_keywrd}' condition"),
&node_tks.get_span().unwrap(),
self.context.clone()
self.context.clone(),
));
}
node_tks.push(self.next_tk().unwrap());
@@ -1196,7 +1209,7 @@ impl ParseStream {
return Err(parse_err_full(
"Expected an expression after 'then'",
&node_tks.get_span().unwrap(),
self.context.clone()
self.context.clone(),
));
};
let cond_node = CondNode {
@@ -1205,7 +1218,7 @@ impl ParseStream {
};
cond_nodes.push(cond_node);
self.catch_separator(&mut node_tks);
self.catch_separator(&mut node_tks);
if !self.check_keyword("elif") || !self.next_tk_is_some() {
break;
} else {
@@ -1214,7 +1227,7 @@ impl ParseStream {
}
}
self.catch_separator(&mut node_tks);
self.catch_separator(&mut node_tks);
if self.check_keyword("else") {
node_tks.push(self.next_tk().unwrap());
self.catch_separator(&mut node_tks);
@@ -1226,7 +1239,7 @@ impl ParseStream {
return Err(parse_err_full(
"Expected an expression after 'else'",
&node_tks.get_span().unwrap(),
self.context.clone()
self.context.clone(),
));
}
}
@@ -1237,7 +1250,7 @@ impl ParseStream {
return Err(parse_err_full(
"Expected 'fi' after if statement",
&node_tks.get_span().unwrap(),
self.context.clone()
self.context.clone(),
));
}
node_tks.push(self.next_tk().unwrap());
@@ -1293,7 +1306,7 @@ impl ParseStream {
return Err(parse_err_full(
"This for loop is missing a variable",
&node_tks.get_span().unwrap(),
self.context.clone()
self.context.clone(),
));
}
if arr.is_empty() {
@@ -1301,7 +1314,7 @@ impl ParseStream {
return Err(parse_err_full(
"This for loop is missing an array",
&node_tks.get_span().unwrap(),
self.context.clone()
self.context.clone(),
));
}
if !self.check_keyword("do") || !self.next_tk_is_some() {
@@ -1309,7 +1322,7 @@ impl ParseStream {
return Err(parse_err_full(
"Missing a 'do' for this for loop",
&node_tks.get_span().unwrap(),
self.context.clone()
self.context.clone(),
));
}
node_tks.push(self.next_tk().unwrap());
@@ -1325,7 +1338,7 @@ impl ParseStream {
return Err(parse_err_full(
"Missing a 'done' after this for loop",
&node_tks.get_span().unwrap(),
self.context.clone()
self.context.clone(),
));
}
node_tks.push(self.next_tk().unwrap());
@@ -1366,7 +1379,7 @@ impl ParseStream {
return Err(parse_err_full(
&format!("Expected an expression after '{loop_kind}'"), // It also implements Display
&node_tks.get_span().unwrap(),
self.context.clone()
self.context.clone(),
));
};
node_tks.extend(cond.tokens.clone());
@@ -1376,7 +1389,7 @@ impl ParseStream {
return Err(parse_err_full(
"Expected 'do' after loop condition",
&node_tks.get_span().unwrap(),
self.context.clone()
self.context.clone(),
));
}
node_tks.push(self.next_tk().unwrap());
@@ -1392,7 +1405,7 @@ impl ParseStream {
return Err(parse_err_full(
"Expected an expression after 'do'",
&node_tks.get_span().unwrap(),
self.context.clone()
self.context.clone(),
));
};
@@ -1402,7 +1415,7 @@ impl ParseStream {
return Err(parse_err_full(
"Expected 'done' after loop body",
&node_tks.get_span().unwrap(),
self.context.clone()
self.context.clone(),
));
}
node_tks.push(self.next_tk().unwrap());
@@ -1479,7 +1492,7 @@ impl ParseStream {
return Err(parse_err_full(
"Found case pattern in command",
&prefix_tk.span,
self.context.clone()
self.context.clone(),
));
}
let is_cmd = prefix_tk.flags.contains(TkFlags::IS_CMD);
@@ -1515,14 +1528,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())
));
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,
@@ -1559,7 +1572,7 @@ impl ParseStream {
let path_tk = tk_iter.next();
if path_tk.is_none_or(|tk| tk.class == TkRule::EOI) {
self.panic_mode(&mut node_tks);
self.panic_mode(&mut node_tks);
return Err(ShErr::at(
ShErrKind::ParseErr,
tk.span.clone(),
@@ -1737,7 +1750,7 @@ fn node_is_punctuated(tokens: &[Tk]) -> bool {
}
pub fn get_redir_file<P: AsRef<Path>>(class: RedirType, path: P) -> ShResult<File> {
let path = path.as_ref();
let path = path.as_ref();
let result = match class {
RedirType::Input => OpenOptions::new().read(true).open(Path::new(&path)),
RedirType::Output => OpenOptions::new()
@@ -1745,23 +1758,22 @@ pub fn get_redir_file<P: AsRef<Path>>(class: RedirType, path: P) -> ShResult<Fil
.create(true)
.truncate(true)
.open(path),
RedirType::Append => OpenOptions::new()
.create(true)
.append(true)
.open(path),
RedirType::Append => OpenOptions::new().create(true).append(true).open(path),
_ => unimplemented!(),
};
Ok(result?)
}
fn parse_err_full(reason: &str, blame: &Span, context: LabelCtx) -> ShErr {
let color = last_color();
ShErr::new(ShErrKind::ParseErr, blame.clone())
.with_label(
blame.span_source().clone(),
Label::new(blame.clone()).with_message(reason).with_color(color)
)
.with_context(context)
let color = last_color();
ShErr::new(ShErrKind::ParseErr, blame.clone())
.with_label(
blame.span_source().clone(),
Label::new(blame.clone())
.with_message(reason)
.with_color(color),
)
.with_context(context)
}
fn is_func_name(tk: Option<&Tk>) -> bool {