rustfmt'd the codebase
This commit is contained in:
@@ -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)
|
||||
|
||||
388
src/parse/lex.rs
388
src/parse/lex.rs
@@ -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 */ }
|
||||
|
||||
400
src/parse/mod.rs
400
src/parse/mod.rs
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user