Various additions and improvements
This commit is contained in:
29
src/builtin/cd.rs
Normal file
29
src/builtin/cd.rs
Normal file
@@ -0,0 +1,29 @@
|
||||
use crate::{jobs::{ChildProc, JobBldr}, libsh::error::{ShErr, ShErrKind, ShResult}, parse::{execute::prepare_argv, NdRule, Node}, prelude::*, state::write_vars};
|
||||
|
||||
pub fn cd(node: Node, job: &mut JobBldr) -> ShResult<()> {
|
||||
let NdRule::Command { assignments: _, argv } = node.class else {
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
let child_pgid = if let Some(pgid) = job.pgid() {
|
||||
pgid
|
||||
} else {
|
||||
job.set_pgid(Pid::this());
|
||||
Pid::this()
|
||||
};
|
||||
let child = ChildProc::new(Pid::this(), Some("cd"), Some(child_pgid))?;
|
||||
job.push_child(child);
|
||||
|
||||
let argv = prepare_argv(argv);
|
||||
let new_dir = if let Some((arg,_)) = argv.into_iter().skip(1).next() {
|
||||
PathBuf::from(arg)
|
||||
} else {
|
||||
PathBuf::from(env::var("HOME").unwrap())
|
||||
};
|
||||
|
||||
env::set_current_dir(new_dir).unwrap();
|
||||
let new_dir = env::current_dir().unwrap();
|
||||
env::set_var("PWD", new_dir);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -15,9 +15,7 @@ pub fn echo(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<(
|
||||
let child = ChildProc::new(Pid::this(), Some("echo"), Some(child_pgid))?;
|
||||
job.push_child(child);
|
||||
|
||||
for redir in node.redirs {
|
||||
io_stack.push_to_frame(redir);
|
||||
}
|
||||
io_stack.append_to_frame(node.redirs);
|
||||
let mut io_frame = io_stack.pop_frame();
|
||||
|
||||
io_frame.redirect()?;
|
||||
@@ -26,6 +24,7 @@ pub fn echo(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<(
|
||||
|
||||
let mut echo_output = prepare_argv(argv)
|
||||
.into_iter()
|
||||
.map(|a| a.0) // Extract the String from the tuple of (String,Span)
|
||||
.skip(1) // Skip 'echo'
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ");
|
||||
|
||||
32
src/builtin/export.rs
Normal file
32
src/builtin/export.rs
Normal file
@@ -0,0 +1,32 @@
|
||||
use crate::{jobs::{ChildProc, JobBldr}, libsh::error::{ShErr, ShErrKind, ShResult}, parse::{execute::prepare_argv, NdRule, Node}, prelude::*};
|
||||
|
||||
pub fn export(node: Node, job: &mut JobBldr) -> ShResult<()> {
|
||||
let NdRule::Command { assignments: _, argv } = node.class else {
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
let child_pgid = if let Some(pgid) = job.pgid() {
|
||||
pgid
|
||||
} else {
|
||||
job.set_pgid(Pid::this());
|
||||
Pid::this()
|
||||
};
|
||||
let child = ChildProc::new(Pid::this(), Some("export"), Some(child_pgid))?;
|
||||
job.push_child(child);
|
||||
|
||||
let argv = prepare_argv(argv);
|
||||
|
||||
for (arg,span) in argv {
|
||||
let Some((var,val)) = arg.split_once('=') else {
|
||||
return Err(
|
||||
ShErr::full(
|
||||
ShErrKind::ExecFail,
|
||||
"Expected an assignment in export args",
|
||||
span.into()
|
||||
)
|
||||
)
|
||||
};
|
||||
env::set_var(var, val);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,5 +1,15 @@
|
||||
pub mod echo;
|
||||
pub mod cd;
|
||||
pub mod export;
|
||||
pub mod pwd;
|
||||
pub mod source;
|
||||
pub mod shift;
|
||||
|
||||
pub const BUILTINS: [&str;1] = [
|
||||
"echo"
|
||||
pub const BUILTINS: [&str;6] = [
|
||||
"echo",
|
||||
"cd",
|
||||
"export",
|
||||
"pwd",
|
||||
"source",
|
||||
"shift"
|
||||
];
|
||||
|
||||
30
src/builtin/pwd.rs
Normal file
30
src/builtin/pwd.rs
Normal file
@@ -0,0 +1,30 @@
|
||||
use crate::{jobs::{ChildProc, JobBldr}, libsh::error::ShResult, parse::{NdRule, Node}, prelude::*, procio::{borrow_fd, IoStack}};
|
||||
|
||||
pub fn pwd(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
|
||||
let NdRule::Command { assignments, argv } = node.class else {
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
let child_pgid = if let Some(pgid) = job.pgid() {
|
||||
pgid
|
||||
} else {
|
||||
job.set_pgid(Pid::this());
|
||||
Pid::this()
|
||||
};
|
||||
let child = ChildProc::new(Pid::this(), Some("pwd"), Some(child_pgid))?;
|
||||
job.push_child(child);
|
||||
|
||||
io_stack.append_to_frame(node.redirs);
|
||||
let mut io_frame = io_stack.pop_frame();
|
||||
|
||||
io_frame.redirect()?;
|
||||
|
||||
let stdout = borrow_fd(STDOUT_FILENO);
|
||||
|
||||
let mut curr_dir = env::current_dir().unwrap().to_str().unwrap().to_string();
|
||||
curr_dir.push('\n');
|
||||
write(stdout, curr_dir.as_bytes())?;
|
||||
io_frame.restore().unwrap();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
35
src/builtin/shift.rs
Normal file
35
src/builtin/shift.rs
Normal file
@@ -0,0 +1,35 @@
|
||||
use crate::{jobs::{ChildProc, JobBldr}, libsh::error::{ErrSpan, ShErr, ShErrKind, ShResult}, parse::{execute::prepare_argv, NdRule, Node}, prelude::*, state::write_vars};
|
||||
|
||||
pub fn shift(node: Node, job: &mut JobBldr) -> ShResult<()> {
|
||||
let NdRule::Command { assignments: _, argv } = node.class else {
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
let child_pgid = if let Some(pgid) = job.pgid() {
|
||||
pgid
|
||||
} else {
|
||||
job.set_pgid(Pid::this());
|
||||
Pid::this()
|
||||
};
|
||||
let child = ChildProc::new(Pid::this(), Some("shift"), Some(child_pgid))?;
|
||||
job.push_child(child);
|
||||
|
||||
let mut argv = prepare_argv(argv).into_iter().skip(1);
|
||||
|
||||
if let Some((arg,span)) = argv.next() {
|
||||
let Ok(count) = arg.parse::<usize>() else {
|
||||
return Err(
|
||||
ShErr::full(
|
||||
ShErrKind::ExecFail,
|
||||
"Expected a number in shift args",
|
||||
span.into()
|
||||
)
|
||||
)
|
||||
};
|
||||
for _ in 0..count {
|
||||
write_vars(|v| v.fpop_arg());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
43
src/builtin/source.rs
Normal file
43
src/builtin/source.rs
Normal file
@@ -0,0 +1,43 @@
|
||||
use crate::{jobs::{ChildProc, JobBldr}, libsh::error::{ShErr, ShErrKind, ShResult}, parse::{execute::prepare_argv, NdRule, Node}, prelude::*, state::source_file};
|
||||
|
||||
pub fn source(node: Node, job: &mut JobBldr) -> ShResult<()> {
|
||||
let NdRule::Command { assignments: _, argv } = node.class else {
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
let child_pgid = if let Some(pgid) = job.pgid() {
|
||||
pgid
|
||||
} else {
|
||||
job.set_pgid(Pid::this());
|
||||
Pid::this()
|
||||
};
|
||||
let child = ChildProc::new(Pid::this(), Some("source"), Some(child_pgid))?;
|
||||
job.push_child(child);
|
||||
|
||||
let argv = prepare_argv(argv).into_iter().skip(1);
|
||||
|
||||
for (arg,span) in argv {
|
||||
let path = PathBuf::from(arg);
|
||||
if !path.exists() {
|
||||
return Err(
|
||||
ShErr::full(
|
||||
ShErrKind::ExecFail,
|
||||
"source: File not found",
|
||||
span.into()
|
||||
)
|
||||
);
|
||||
}
|
||||
if !path.is_file() {
|
||||
return Err(
|
||||
ShErr::full(
|
||||
ShErrKind::ExecFail,
|
||||
"source: Given path is not a file",
|
||||
span.into()
|
||||
)
|
||||
);
|
||||
}
|
||||
source_file(path)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::{parse::lex::{is_hard_sep, LexFlags, LexStream, Tk, Span, TkErr, TkFlags, TkRule}, state::read_vars};
|
||||
use crate::{parse::lex::{is_hard_sep, LexFlags, LexStream, Tk, Span, TkFlags, TkRule}, state::read_vars};
|
||||
|
||||
/// Variable substitution marker
|
||||
pub const VAR_SUB: char = '\u{fdd0}';
|
||||
@@ -13,7 +13,7 @@ impl<'t> Tk<'t> {
|
||||
pub fn expand(self, span: Span<'t>, flags: TkFlags) -> Self {
|
||||
let exp = Expander::new(self).expand();
|
||||
let class = TkRule::Expanded { exp };
|
||||
Self { class, span, err_span: None, flags, err: TkErr::Null }
|
||||
Self { class, span, flags, }
|
||||
}
|
||||
pub fn get_words(&self) -> Vec<String> {
|
||||
match &self.class {
|
||||
@@ -34,9 +34,10 @@ impl<'t> Expander {
|
||||
}
|
||||
pub fn expand(&'t mut self) -> Vec<String> {
|
||||
self.raw = self.expand_raw();
|
||||
// Unwrap here is safe because LexFlags::RAW has no error states
|
||||
let tokens: Vec<_> = LexStream::new(&self.raw, LexFlags::RAW)
|
||||
.filter(|tk| !matches!(tk.class, TkRule::EOI | TkRule::SOI))
|
||||
.map(|tk| tk.to_string())
|
||||
.filter(|tk| !matches!(tk.as_ref().unwrap().class, TkRule::EOI | TkRule::SOI))
|
||||
.map(|tk| tk.unwrap().to_string())
|
||||
.collect();
|
||||
tokens
|
||||
}
|
||||
@@ -82,6 +83,8 @@ impl<'t> Expander {
|
||||
}
|
||||
}
|
||||
|
||||
/// Processes strings into intermediate representations that are more readable by the program
|
||||
///
|
||||
/// Clean up a single layer of escape characters, and then replace control characters like '$' with a non-character unicode representation that is unmistakable by the rest of the code
|
||||
pub fn unescape_str(raw: &str) -> String {
|
||||
let mut chars = raw.chars();
|
||||
|
||||
47
src/fern.rs
47
src/fern.rs
@@ -11,6 +11,7 @@ pub mod signal;
|
||||
#[cfg(test)]
|
||||
pub mod tests;
|
||||
|
||||
use libsh::error::ShResult;
|
||||
use parse::{execute::Dispatcher, lex::{LexFlags, LexStream}, ParseStream};
|
||||
use termios::{LocalFlags, Termios};
|
||||
use crate::prelude::*;
|
||||
@@ -44,34 +45,32 @@ fn set_termios() {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn exec_input(input: &str) -> ShResult<()> {
|
||||
let mut tokens = vec![];
|
||||
for token in LexStream::new(&input, LexFlags::empty()) {
|
||||
tokens.push(token?);
|
||||
}
|
||||
|
||||
let mut nodes = vec![];
|
||||
for result in ParseStream::new(tokens) {
|
||||
nodes.push(result?);
|
||||
}
|
||||
|
||||
let mut dispatcher = Dispatcher::new(nodes);
|
||||
dispatcher.begin_dispatch()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main() {
|
||||
'main: loop {
|
||||
save_termios();
|
||||
set_termios();
|
||||
loop {
|
||||
let input = prompt::read_line().unwrap();
|
||||
if input == "quit" { break };
|
||||
let start = Instant::now();
|
||||
|
||||
let mut tokens = vec![];
|
||||
for token in LexStream::new(&input, LexFlags::empty()) {
|
||||
if token.is_err() {
|
||||
let error = format!("{:?}: {}",token.err,token.err_span.unwrap().as_str());
|
||||
panic!("{error}");
|
||||
}
|
||||
tokens.push(token);
|
||||
if let Err(e) = exec_input(&input) {
|
||||
eprintln!("{e}");
|
||||
}
|
||||
|
||||
let mut nodes = vec![];
|
||||
for result in ParseStream::new(tokens) {
|
||||
match result {
|
||||
Ok(node) => nodes.push(node),
|
||||
Err(e) => {
|
||||
eprintln!("{:?}",e);
|
||||
continue 'main // Isn't rust cool
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut dispatcher = Dispatcher::new(nodes);
|
||||
dispatcher.begin_dispatch().unwrap();
|
||||
flog!(INFO, "elapsed: {:?}", start.elapsed());
|
||||
flog!(INFO, "cmd duration: {:?}", start.elapsed());
|
||||
}
|
||||
}
|
||||
|
||||
207
src/jobs.rs
207
src/jobs.rs
@@ -120,6 +120,204 @@ impl<'a> ChildProc {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct JobTab {
|
||||
fg: Option<Job>,
|
||||
order: Vec<usize>,
|
||||
new_updates: Vec<usize>,
|
||||
jobs: Vec<Option<Job>>
|
||||
}
|
||||
|
||||
impl JobTab {
|
||||
pub fn new() -> Self {
|
||||
Self { fg: None, order: vec![], new_updates: vec![], jobs: vec![] }
|
||||
}
|
||||
pub fn take_fg(&mut self) -> Option<Job> {
|
||||
self.fg.take()
|
||||
}
|
||||
fn next_open_pos(&self) -> usize {
|
||||
if let Some(position) = self.jobs.iter().position(|slot| slot.is_none()) {
|
||||
position
|
||||
} else {
|
||||
self.jobs.len()
|
||||
}
|
||||
}
|
||||
pub fn jobs(&self) -> &Vec<Option<Job>> {
|
||||
&self.jobs
|
||||
}
|
||||
pub fn jobs_mut(&mut self) -> &mut Vec<Option<Job>> {
|
||||
&mut self.jobs
|
||||
}
|
||||
pub fn curr_job(&self) -> Option<usize> {
|
||||
self.order.last().copied()
|
||||
}
|
||||
pub fn prev_job(&self) -> Option<usize> {
|
||||
self.order.last().copied()
|
||||
}
|
||||
fn prune_jobs(&mut self) {
|
||||
while let Some(job) = self.jobs.last() {
|
||||
if job.is_none() {
|
||||
self.jobs.pop();
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn insert_job(&mut self, mut job: Job, silent: bool) -> ShResult<usize> {
|
||||
self.prune_jobs();
|
||||
let tab_pos = if let Some(id) = job.tabid() { id } else { self.next_open_pos() };
|
||||
job.set_tabid(tab_pos);
|
||||
self.order.push(tab_pos);
|
||||
if !silent {
|
||||
write(borrow_fd(1),format!("{}", job.display(&self.order, JobCmdFlags::INIT)).as_bytes())?;
|
||||
}
|
||||
if tab_pos == self.jobs.len() {
|
||||
self.jobs.push(Some(job))
|
||||
} else {
|
||||
self.jobs[tab_pos] = Some(job);
|
||||
}
|
||||
Ok(tab_pos)
|
||||
}
|
||||
pub fn order(&self) -> &[usize] {
|
||||
&self.order
|
||||
}
|
||||
pub fn query(&self, identifier: JobID) -> Option<&Job> {
|
||||
match identifier {
|
||||
// Match by process group ID
|
||||
JobID::Pgid(pgid) => {
|
||||
self.jobs.iter().find_map(|job| {
|
||||
job.as_ref().filter(|j| j.pgid() == pgid)
|
||||
})
|
||||
}
|
||||
// Match by process ID
|
||||
JobID::Pid(pid) => {
|
||||
self.jobs.iter().find_map(|job| {
|
||||
job.as_ref().filter(|j| j.children().iter().any(|child| child.pid() == pid))
|
||||
})
|
||||
}
|
||||
// Match by table ID (index in the job table)
|
||||
JobID::TableID(id) => {
|
||||
self.jobs.get(id).and_then(|job| job.as_ref())
|
||||
}
|
||||
// Match by command name (partial match)
|
||||
JobID::Command(cmd) => {
|
||||
self.jobs.iter().find_map(|job| {
|
||||
job.as_ref().filter(|j| {
|
||||
j.children().iter().any(|child| {
|
||||
child.cmd().as_ref().is_some_and(|c| c.contains(&cmd))
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn query_mut(&mut self, identifier: JobID) -> Option<&mut Job> {
|
||||
match identifier {
|
||||
// Match by process group ID
|
||||
JobID::Pgid(pgid) => {
|
||||
self.jobs.iter_mut().find_map(|job| {
|
||||
job.as_mut().filter(|j| j.pgid() == pgid)
|
||||
})
|
||||
}
|
||||
// Match by process ID
|
||||
JobID::Pid(pid) => {
|
||||
self.jobs.iter_mut().find_map(|job| {
|
||||
job.as_mut().filter(|j| j.children().iter().any(|child| child.pid() == pid))
|
||||
})
|
||||
}
|
||||
// Match by table ID (index in the job table)
|
||||
JobID::TableID(id) => {
|
||||
self.jobs.get_mut(id).and_then(|job| job.as_mut())
|
||||
}
|
||||
// Match by command name (partial match)
|
||||
JobID::Command(cmd) => {
|
||||
self.jobs.iter_mut().find_map(|job| {
|
||||
job.as_mut().filter(|j| {
|
||||
j.children().iter().any(|child| {
|
||||
child.cmd().as_ref().is_some_and(|c| c.contains(&cmd))
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn get_fg(&self) -> Option<&Job> {
|
||||
self.fg.as_ref()
|
||||
}
|
||||
pub fn get_fg_mut(&mut self) -> Option<&mut Job> {
|
||||
self.fg.as_mut()
|
||||
}
|
||||
pub fn new_fg<'a>(&mut self, job: Job) -> ShResult<Vec<WtStat>> {
|
||||
let pgid = job.pgid();
|
||||
self.fg = Some(job);
|
||||
attach_tty(pgid)?;
|
||||
let statuses = self.fg.as_mut().unwrap().wait_pgrp()?;
|
||||
attach_tty(getpgrp())?;
|
||||
Ok(statuses)
|
||||
}
|
||||
pub fn fg_to_bg(&mut self, stat: WtStat) -> ShResult<()> {
|
||||
if self.fg.is_none() {
|
||||
return Ok(())
|
||||
}
|
||||
take_term()?;
|
||||
let fg = std::mem::take(&mut self.fg);
|
||||
if let Some(mut job) = fg {
|
||||
job.set_stats(stat);
|
||||
self.insert_job(job, false)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
pub fn bg_to_fg(&mut self, id: JobID) -> ShResult<()> {
|
||||
let job = self.remove_job(id);
|
||||
if let Some(job) = job {
|
||||
wait_fg(job)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
pub fn remove_job(&mut self, id: JobID) -> Option<Job> {
|
||||
let tabid = self.query(id).map(|job| job.tabid().unwrap());
|
||||
if let Some(tabid) = tabid {
|
||||
self.jobs.get_mut(tabid).and_then(Option::take)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
pub fn print_jobs(&mut self, flags: JobCmdFlags) -> ShResult<()> {
|
||||
let jobs = if flags.contains(JobCmdFlags::NEW_ONLY) {
|
||||
&self.jobs
|
||||
.iter()
|
||||
.filter(|job| job.as_ref().is_some_and(|job| self.new_updates.contains(&job.tabid().unwrap())))
|
||||
.map(|job| job.as_ref())
|
||||
.collect::<Vec<Option<&Job>>>()
|
||||
} else {
|
||||
&self.jobs
|
||||
.iter()
|
||||
.map(|job| job.as_ref())
|
||||
.collect::<Vec<Option<&Job>>>()
|
||||
};
|
||||
let mut jobs_to_remove = vec![];
|
||||
for job in jobs.iter().flatten() {
|
||||
// Skip foreground job
|
||||
let id = job.tabid().unwrap();
|
||||
// Filter jobs based on flags
|
||||
if flags.contains(JobCmdFlags::RUNNING) && !matches!(job.get_stats().get(id).unwrap(), WtStat::StillAlive | WtStat::Continued(_)) {
|
||||
continue;
|
||||
}
|
||||
if flags.contains(JobCmdFlags::STOPPED) && !matches!(job.get_stats().get(id).unwrap(), WtStat::Stopped(_,_)) {
|
||||
continue;
|
||||
}
|
||||
// Print the job in the selected format
|
||||
write(borrow_fd(1), format!("{}\n",job.display(&self.order,flags)).as_bytes())?;
|
||||
if job.get_stats().iter().all(|stat| matches!(stat,WtStat::Exited(_, _))) {
|
||||
jobs_to_remove.push(JobID::TableID(id));
|
||||
}
|
||||
}
|
||||
for id in jobs_to_remove {
|
||||
self.remove_job(id);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct JobBldr {
|
||||
table_id: Option<usize>,
|
||||
pgid: Option<Pid>,
|
||||
@@ -237,8 +435,15 @@ impl Job {
|
||||
}
|
||||
pub fn wait_pgrp<'a>(&mut self) -> ShResult<Vec<WtStat>> {
|
||||
let mut stats = vec![];
|
||||
flog!(TRACE, "waiting on children");
|
||||
flog!(TRACE, self.children);
|
||||
for child in self.children.iter_mut() {
|
||||
let result = child.wait(Some(WtFlag::WUNTRACED));
|
||||
if child.pid == Pid::this() {
|
||||
// TODO: figure out some way to get the exit code of builtins
|
||||
stats.push(WtStat::Exited(child.pid, 0));
|
||||
continue
|
||||
}
|
||||
let result = child.wait(Some(WtFlag::WSTOPPED));
|
||||
match result {
|
||||
Ok(stat) => {
|
||||
stats.push(stat);
|
||||
|
||||
@@ -29,9 +29,8 @@ impl<'s> ShErr {
|
||||
let msg = msg.into();
|
||||
Self::Simple { kind, msg }
|
||||
}
|
||||
pub fn full(kind: ShErrKind, msg: impl Into<String>, span: Span<'s>) -> Self {
|
||||
pub fn full(kind: ShErrKind, msg: impl Into<String>, span: ErrSpan) -> Self {
|
||||
let msg = msg.into();
|
||||
let span = span.into();
|
||||
Self::Full { kind, msg, span }
|
||||
}
|
||||
pub fn unpack(self) -> (ShErrKind,String,Option<ErrSpan>) {
|
||||
|
||||
@@ -2,3 +2,4 @@ pub mod error;
|
||||
pub mod term;
|
||||
pub mod flog;
|
||||
pub mod sys;
|
||||
pub mod utils;
|
||||
|
||||
11
src/libsh/utils.rs
Normal file
11
src/libsh/utils.rs
Normal file
@@ -0,0 +1,11 @@
|
||||
use std::collections::VecDeque;
|
||||
|
||||
pub trait VecDequeExt<T> {
|
||||
fn to_vec(self) -> Vec<T>;
|
||||
}
|
||||
|
||||
impl<T> VecDequeExt<T> for VecDeque<T> {
|
||||
fn to_vec(self) -> Vec<T> {
|
||||
self.into_iter().collect::<Vec<T>>()
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
use std::collections::VecDeque;
|
||||
|
||||
|
||||
use crate::{builtin::echo::echo, jobs::{dispatch_job, ChildProc, Job, JobBldr}, libsh::error::ShResult, prelude::*, procio::{IoFrame, IoPipe, IoStack}, state::{self, write_vars}};
|
||||
use crate::{builtin::{cd::cd, echo::echo, export::export, pwd::pwd, shift::shift, source::source}, jobs::{dispatch_job, ChildProc, Job, JobBldr}, libsh::error::ShResult, prelude::*, procio::{IoFrame, IoPipe, IoStack}, state::{self, write_vars}};
|
||||
|
||||
use super::{lex::{Tk, TkFlags}, AssignKind, ConjunctNode, ConjunctOp, NdFlags, NdRule, Node, Redir, RedirType};
|
||||
use super::{lex::{Span, Tk, TkFlags}, AssignKind, ConjunctNode, ConjunctOp, NdFlags, NdRule, Node, Redir, RedirType};
|
||||
|
||||
pub enum AssignBehavior {
|
||||
Export,
|
||||
@@ -26,11 +26,11 @@ impl ExecArgs {
|
||||
let envp = Self::get_envp();
|
||||
Self { cmd, argv, envp }
|
||||
}
|
||||
pub fn get_cmd(argv: &[String]) -> CString {
|
||||
CString::new(argv[0].as_str()).unwrap()
|
||||
pub fn get_cmd(argv: &[(String,Span)]) -> CString {
|
||||
CString::new(argv[0].0.as_str()).unwrap()
|
||||
}
|
||||
pub fn get_argv(argv: Vec<String>) -> Vec<CString> {
|
||||
argv.into_iter().map(|s| CString::new(s).unwrap()).collect()
|
||||
pub fn get_argv(argv: Vec<(String,Span)>) -> Vec<CString> {
|
||||
argv.into_iter().map(|s| CString::new(s.0).unwrap()).collect()
|
||||
}
|
||||
pub fn get_envp() -> Vec<CString> {
|
||||
std::env::vars().map(|v| CString::new(format!("{}={}",v.0,v.1)).unwrap()).collect()
|
||||
@@ -128,8 +128,14 @@ impl<'t> Dispatcher<'t> {
|
||||
let cmd_raw = cmd.get_command().unwrap();
|
||||
flog!(TRACE, "doing builtin");
|
||||
let curr_job_mut = self.curr_job.as_mut().unwrap();
|
||||
let io_stack_mut = &mut self.io_stack;
|
||||
let result = match cmd_raw.span.as_str() {
|
||||
"echo" => echo(cmd, &mut self.io_stack, curr_job_mut),
|
||||
"echo" => echo(cmd, io_stack_mut, curr_job_mut),
|
||||
"cd" => cd(cmd, curr_job_mut),
|
||||
"export" => export(cmd, curr_job_mut),
|
||||
"pwd" => pwd(cmd, io_stack_mut, curr_job_mut),
|
||||
"source" => source(cmd, curr_job_mut),
|
||||
"shift" => shift(cmd, curr_job_mut),
|
||||
_ => unimplemented!("Have not yet added support for builtin '{}'", cmd_raw.span.as_str())
|
||||
};
|
||||
|
||||
@@ -152,13 +158,13 @@ impl<'t> Dispatcher<'t> {
|
||||
};
|
||||
env_vars_to_unset = self.set_assignments(assignments, assign_behavior);
|
||||
}
|
||||
for redir in cmd.redirs {
|
||||
self.io_stack.push_to_frame(redir);
|
||||
}
|
||||
|
||||
if argv.is_empty() {
|
||||
return Ok(())
|
||||
}
|
||||
|
||||
self.io_stack.append_to_frame(cmd.redirs);
|
||||
|
||||
let exec_args = ExecArgs::new(argv);
|
||||
let io_frame = self.io_stack.pop_frame();
|
||||
run_fork(
|
||||
@@ -216,14 +222,16 @@ impl<'t> Dispatcher<'t> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn prepare_argv(argv: Vec<Tk>) -> Vec<String> {
|
||||
pub fn prepare_argv(argv: Vec<Tk>) -> Vec<(String,Span)> {
|
||||
let mut args = vec![];
|
||||
|
||||
for arg in argv {
|
||||
let flags = arg.flags;
|
||||
let span = arg.span.clone();
|
||||
let expanded = arg.expand(span, flags);
|
||||
args.extend(expanded.get_words());
|
||||
let expanded = arg.expand(span.clone(), flags);
|
||||
for exp in expanded.get_words() {
|
||||
args.push((exp,span.clone()))
|
||||
}
|
||||
}
|
||||
args
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::{fmt::Display, ops::{Bound, Deref, Range, RangeBounds}};
|
||||
|
||||
use bitflags::bitflags;
|
||||
|
||||
use crate::{builtin::BUILTINS, prelude::*};
|
||||
use crate::{builtin::BUILTINS, libsh::error::{ShErr, ShErrKind, ShResult}, prelude::*};
|
||||
|
||||
pub const KEYWORDS: [&'static str;14] = [
|
||||
"if",
|
||||
@@ -87,33 +87,9 @@ impl Default for TkRule {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone,Copy,PartialEq,Debug)]
|
||||
pub enum TkErr {
|
||||
Null,
|
||||
UntermQuote,
|
||||
UntermSubsh,
|
||||
UntermEscape,
|
||||
UntermBrace,
|
||||
BadRedir,
|
||||
BadPipe,
|
||||
HangingDelim,
|
||||
}
|
||||
|
||||
impl Default for TkErr {
|
||||
fn default() -> Self {
|
||||
TkErr::Null
|
||||
}
|
||||
}
|
||||
|
||||
pub enum TkState {
|
||||
Raw,
|
||||
}
|
||||
|
||||
#[derive(Clone,Debug,PartialEq,Default)]
|
||||
pub struct Tk<'s> {
|
||||
pub class: TkRule,
|
||||
pub err_span: Option<Span<'s>>,
|
||||
pub err: TkErr,
|
||||
pub span: Span<'s>,
|
||||
pub flags: TkFlags
|
||||
}
|
||||
@@ -121,7 +97,7 @@ pub struct Tk<'s> {
|
||||
// There's one impl here and then another in expand.rs which has the expansion logic
|
||||
impl<'s> Tk<'s> {
|
||||
pub fn new(class: TkRule, span: Span<'s>) -> Self {
|
||||
Self { class, err_span: None, err: TkErr::Null, span, flags: TkFlags::empty() }
|
||||
Self { class, span, flags: TkFlags::empty() }
|
||||
}
|
||||
pub fn to_string(&self) -> String {
|
||||
match &self.class {
|
||||
@@ -129,13 +105,6 @@ impl<'s> Tk<'s> {
|
||||
_ => self.span.as_str().to_string()
|
||||
}
|
||||
}
|
||||
pub fn set_err(&mut self, range: Range<usize>, slice: &'s str, err: TkErr) {
|
||||
self.err_span = Some(Span::new(range, slice));
|
||||
self.err = err
|
||||
}
|
||||
pub fn is_err(&self) -> bool {
|
||||
self.err_span.is_some()
|
||||
}
|
||||
pub fn source(&self) -> &'s str {
|
||||
self.span.source
|
||||
}
|
||||
@@ -231,7 +200,7 @@ impl<'t> LexStream<'t> {
|
||||
pub fn next_is_not_cmd(&mut self) {
|
||||
self.flags &= !LexFlags::NEXT_IS_CMD;
|
||||
}
|
||||
pub fn read_redir(&mut self) -> Option<Tk<'t>> {
|
||||
pub fn read_redir(&mut self) -> Option<ShResult<Tk<'t>>> {
|
||||
assert!(self.cursor <= self.source.len());
|
||||
let slice = self.slice(self.cursor..)?;
|
||||
let mut pos = self.cursor;
|
||||
@@ -259,10 +228,13 @@ impl<'t> LexStream<'t> {
|
||||
|
||||
|
||||
if !found_fd {
|
||||
let err = TkErr::BadRedir;
|
||||
tk = self.get_token(self.cursor..pos, TkRule::Redir);
|
||||
tk.set_err(self.cursor..pos, self.source, err);
|
||||
break
|
||||
return Some(Err(
|
||||
ShErr::full(
|
||||
ShErrKind::ParseErr,
|
||||
"Invalid redirection",
|
||||
Span::new(self.cursor..pos, self.source).into()
|
||||
)
|
||||
));
|
||||
} else {
|
||||
tk = self.get_token(self.cursor..pos, TkRule::Redir);
|
||||
break
|
||||
@@ -304,9 +276,9 @@ impl<'t> LexStream<'t> {
|
||||
}
|
||||
|
||||
self.cursor = pos;
|
||||
Some(tk)
|
||||
Some(Ok(tk))
|
||||
}
|
||||
pub fn read_string(&mut self) -> Tk<'t> {
|
||||
pub fn read_string(&mut self) -> ShResult<Tk<'t>> {
|
||||
assert!(self.cursor <= self.source.len());
|
||||
let slice = self.slice_from_cursor().unwrap();
|
||||
let mut pos = self.cursor;
|
||||
@@ -351,10 +323,12 @@ impl<'t> LexStream<'t> {
|
||||
}
|
||||
let mut new_tk = self.get_token(self.cursor..pos, TkRule::Str);
|
||||
if self.in_quote && !self.flags.contains(LexFlags::LEX_UNFINISHED) {
|
||||
new_tk.set_err(
|
||||
quote_pos.unwrap()..pos,
|
||||
self.source,
|
||||
TkErr::UntermQuote
|
||||
return Err(
|
||||
ShErr::full(
|
||||
ShErrKind::ParseErr,
|
||||
"Unterminated quote",
|
||||
new_tk.span.into(),
|
||||
)
|
||||
);
|
||||
}
|
||||
if self.flags.contains(LexFlags::NEXT_IS_CMD) {
|
||||
@@ -373,7 +347,7 @@ impl<'t> LexStream<'t> {
|
||||
}
|
||||
}
|
||||
self.cursor = pos;
|
||||
new_tk
|
||||
Ok(new_tk)
|
||||
}
|
||||
pub fn get_token(&self, range: Range<usize>, class: TkRule) -> Tk<'t> {
|
||||
let span = Span::new(range, self.source);
|
||||
@@ -382,7 +356,7 @@ impl<'t> LexStream<'t> {
|
||||
}
|
||||
|
||||
impl<'t> Iterator for LexStream<'t> {
|
||||
type Item = Tk<'t>;
|
||||
type Item = ShResult<Tk<'t>>;
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
assert!(self.cursor <= self.source.len());
|
||||
// We are at the end of the input
|
||||
@@ -394,14 +368,14 @@ impl<'t> Iterator for LexStream<'t> {
|
||||
// Return the EOI token
|
||||
let token = self.get_token(self.cursor..self.cursor, TkRule::EOI);
|
||||
self.flags |= LexFlags::STALE;
|
||||
return Some(token)
|
||||
return Some(Ok(token))
|
||||
}
|
||||
}
|
||||
// Return the SOI token
|
||||
if self.flags.contains(LexFlags::FRESH) {
|
||||
self.flags &= !LexFlags::FRESH;
|
||||
let token = self.get_token(self.cursor..self.cursor, TkRule::SOI);
|
||||
return Some(token)
|
||||
return Some(Ok(token))
|
||||
}
|
||||
|
||||
// If we are just reading raw words, short circuit here
|
||||
@@ -486,13 +460,19 @@ impl<'t> Iterator for LexStream<'t> {
|
||||
_ => {
|
||||
if let Some(tk) = self.read_redir() {
|
||||
self.next_is_not_cmd();
|
||||
tk
|
||||
match tk {
|
||||
Ok(tk) => tk,
|
||||
Err(e) => return Some(Err(e))
|
||||
}
|
||||
} else {
|
||||
self.read_string()
|
||||
match self.read_string() {
|
||||
Ok(tk) => tk,
|
||||
Err(e) => return Some(Err(e))
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
Some(token)
|
||||
Some(Ok(token))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,16 @@ impl<'t> Node<'t> {
|
||||
let command = argv.iter().find(|tk| tk.flags.contains(TkFlags::IS_CMD))?;
|
||||
Some(command)
|
||||
}
|
||||
pub fn get_span(&'t self) -> Span<'t> {
|
||||
let Some(first_tk) = self.tokens.first() else {
|
||||
unreachable!()
|
||||
};
|
||||
let Some(last_tk) = self.tokens.last() else {
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
Span::new(first_tk.span.start..last_tk.span.end, first_tk.span.get_source())
|
||||
}
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
@@ -267,10 +277,12 @@ impl<'t> ParseStream<'t> {
|
||||
};
|
||||
let conjunction = ConjunctNode { cmd: Box::new(block), operator: conjunct_op };
|
||||
elements.push(conjunction);
|
||||
let Some(tk) = self.next_tk() else {
|
||||
break
|
||||
};
|
||||
node_tks.push(tk);
|
||||
if conjunct_op != ConjunctOp::Null {
|
||||
let Some(tk) = self.next_tk() else {
|
||||
break
|
||||
};
|
||||
node_tks.push(tk);
|
||||
}
|
||||
if conjunct_op == ConjunctOp::Null {
|
||||
break
|
||||
}
|
||||
@@ -385,7 +397,7 @@ impl<'t> ParseStream<'t> {
|
||||
ShErr::full(
|
||||
ShErrKind::ParseErr,
|
||||
"Expected a filename after this redirection",
|
||||
tk.span.clone()
|
||||
tk.span.clone().into()
|
||||
)
|
||||
)
|
||||
};
|
||||
@@ -420,7 +432,7 @@ impl<'t> ParseStream<'t> {
|
||||
ShErr::full(
|
||||
ShErrKind::InternalErr,
|
||||
"Error opening file for redirection",
|
||||
path_tk.span.clone()
|
||||
path_tk.span.clone().into()
|
||||
)
|
||||
)
|
||||
};
|
||||
@@ -565,7 +577,9 @@ impl<'t> Iterator for ParseStream<'t> {
|
||||
}
|
||||
}
|
||||
match self.parse_cmd_list() {
|
||||
Ok(Some(node)) => return Some(Ok(node)),
|
||||
Ok(Some(node)) => {
|
||||
return Some(Ok(node));
|
||||
}
|
||||
Ok(None) => return None,
|
||||
Err(e) => return Some(Err(e))
|
||||
}
|
||||
|
||||
@@ -299,6 +299,9 @@ impl<'e> IoStack {
|
||||
pub fn push_to_frame(&mut self, redir: Redir) {
|
||||
self.curr_frame_mut().push(redir)
|
||||
}
|
||||
pub fn append_to_frame(&mut self, mut other: Vec<Redir>) {
|
||||
self.curr_frame_mut().append(&mut other)
|
||||
}
|
||||
/// Pop the current stack frame
|
||||
/// This differs from using `pop()` because it always returns a stack frame
|
||||
/// If `self.pop()` would empty the `IoStack`, it instead uses `std::mem::take()` to take the last frame
|
||||
|
||||
260
src/state.rs
260
src/state.rs
@@ -1,219 +1,25 @@
|
||||
use std::{collections::HashMap, sync::{LazyLock, RwLock, RwLockReadGuard, RwLockWriteGuard}};
|
||||
use std::{collections::{HashMap, VecDeque}, sync::{LazyLock, RwLock, RwLockReadGuard, RwLockWriteGuard}};
|
||||
|
||||
use crate::{jobs::{attach_tty, take_term, wait_fg, Job, JobCmdFlags, JobID}, libsh::error::ShResult, parse::lex::get_char, prelude::*, procio::borrow_fd};
|
||||
use crate::{exec_input, jobs::JobTab, libsh::{error::ShResult, utils::VecDequeExt}, parse::lex::get_char, prelude::*};
|
||||
|
||||
pub static JOB_TABLE: LazyLock<RwLock<JobTab>> = LazyLock::new(|| RwLock::new(JobTab::new()));
|
||||
|
||||
pub static VAR_TABLE: LazyLock<RwLock<VarTab>> = LazyLock::new(|| RwLock::new(VarTab::new()));
|
||||
|
||||
pub struct JobTab {
|
||||
fg: Option<Job>,
|
||||
order: Vec<usize>,
|
||||
new_updates: Vec<usize>,
|
||||
jobs: Vec<Option<Job>>
|
||||
}
|
||||
|
||||
impl JobTab {
|
||||
pub fn new() -> Self {
|
||||
Self { fg: None, order: vec![], new_updates: vec![], jobs: vec![] }
|
||||
}
|
||||
pub fn take_fg(&mut self) -> Option<Job> {
|
||||
self.fg.take()
|
||||
}
|
||||
fn next_open_pos(&self) -> usize {
|
||||
if let Some(position) = self.jobs.iter().position(|slot| slot.is_none()) {
|
||||
position
|
||||
} else {
|
||||
self.jobs.len()
|
||||
}
|
||||
}
|
||||
pub fn jobs(&self) -> &Vec<Option<Job>> {
|
||||
&self.jobs
|
||||
}
|
||||
pub fn jobs_mut(&mut self) -> &mut Vec<Option<Job>> {
|
||||
&mut self.jobs
|
||||
}
|
||||
pub fn curr_job(&self) -> Option<usize> {
|
||||
self.order.last().copied()
|
||||
}
|
||||
pub fn prev_job(&self) -> Option<usize> {
|
||||
self.order.last().copied()
|
||||
}
|
||||
fn prune_jobs(&mut self) {
|
||||
while let Some(job) = self.jobs.last() {
|
||||
if job.is_none() {
|
||||
self.jobs.pop();
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn insert_job(&mut self, mut job: Job, silent: bool) -> ShResult<usize> {
|
||||
self.prune_jobs();
|
||||
let tab_pos = if let Some(id) = job.tabid() { id } else { self.next_open_pos() };
|
||||
job.set_tabid(tab_pos);
|
||||
self.order.push(tab_pos);
|
||||
if !silent {
|
||||
write(borrow_fd(1),format!("{}", job.display(&self.order, JobCmdFlags::INIT)).as_bytes())?;
|
||||
}
|
||||
if tab_pos == self.jobs.len() {
|
||||
self.jobs.push(Some(job))
|
||||
} else {
|
||||
self.jobs[tab_pos] = Some(job);
|
||||
}
|
||||
Ok(tab_pos)
|
||||
}
|
||||
pub fn order(&self) -> &[usize] {
|
||||
&self.order
|
||||
}
|
||||
pub fn query(&self, identifier: JobID) -> Option<&Job> {
|
||||
match identifier {
|
||||
// Match by process group ID
|
||||
JobID::Pgid(pgid) => {
|
||||
self.jobs.iter().find_map(|job| {
|
||||
job.as_ref().filter(|j| j.pgid() == pgid)
|
||||
})
|
||||
}
|
||||
// Match by process ID
|
||||
JobID::Pid(pid) => {
|
||||
self.jobs.iter().find_map(|job| {
|
||||
job.as_ref().filter(|j| j.children().iter().any(|child| child.pid() == pid))
|
||||
})
|
||||
}
|
||||
// Match by table ID (index in the job table)
|
||||
JobID::TableID(id) => {
|
||||
self.jobs.get(id).and_then(|job| job.as_ref())
|
||||
}
|
||||
// Match by command name (partial match)
|
||||
JobID::Command(cmd) => {
|
||||
self.jobs.iter().find_map(|job| {
|
||||
job.as_ref().filter(|j| {
|
||||
j.children().iter().any(|child| {
|
||||
child.cmd().as_ref().is_some_and(|c| c.contains(&cmd))
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn query_mut(&mut self, identifier: JobID) -> Option<&mut Job> {
|
||||
match identifier {
|
||||
// Match by process group ID
|
||||
JobID::Pgid(pgid) => {
|
||||
self.jobs.iter_mut().find_map(|job| {
|
||||
job.as_mut().filter(|j| j.pgid() == pgid)
|
||||
})
|
||||
}
|
||||
// Match by process ID
|
||||
JobID::Pid(pid) => {
|
||||
self.jobs.iter_mut().find_map(|job| {
|
||||
job.as_mut().filter(|j| j.children().iter().any(|child| child.pid() == pid))
|
||||
})
|
||||
}
|
||||
// Match by table ID (index in the job table)
|
||||
JobID::TableID(id) => {
|
||||
self.jobs.get_mut(id).and_then(|job| job.as_mut())
|
||||
}
|
||||
// Match by command name (partial match)
|
||||
JobID::Command(cmd) => {
|
||||
self.jobs.iter_mut().find_map(|job| {
|
||||
job.as_mut().filter(|j| {
|
||||
j.children().iter().any(|child| {
|
||||
child.cmd().as_ref().is_some_and(|c| c.contains(&cmd))
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn get_fg(&self) -> Option<&Job> {
|
||||
self.fg.as_ref()
|
||||
}
|
||||
pub fn get_fg_mut(&mut self) -> Option<&mut Job> {
|
||||
self.fg.as_mut()
|
||||
}
|
||||
pub fn new_fg<'a>(&mut self, job: Job) -> ShResult<Vec<WtStat>> {
|
||||
let pgid = job.pgid();
|
||||
self.fg = Some(job);
|
||||
attach_tty(pgid)?;
|
||||
let statuses = self.fg.as_mut().unwrap().wait_pgrp()?;
|
||||
attach_tty(getpgrp())?;
|
||||
Ok(statuses)
|
||||
}
|
||||
pub fn fg_to_bg(&mut self, stat: WtStat) -> ShResult<()> {
|
||||
if self.fg.is_none() {
|
||||
return Ok(())
|
||||
}
|
||||
take_term()?;
|
||||
let fg = std::mem::take(&mut self.fg);
|
||||
if let Some(mut job) = fg {
|
||||
job.set_stats(stat);
|
||||
self.insert_job(job, false)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
pub fn bg_to_fg(&mut self, id: JobID) -> ShResult<()> {
|
||||
let job = self.remove_job(id);
|
||||
if let Some(job) = job {
|
||||
wait_fg(job)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
pub fn remove_job(&mut self, id: JobID) -> Option<Job> {
|
||||
let tabid = self.query(id).map(|job| job.tabid().unwrap());
|
||||
if let Some(tabid) = tabid {
|
||||
self.jobs.get_mut(tabid).and_then(Option::take)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
pub fn print_jobs(&mut self, flags: JobCmdFlags) -> ShResult<()> {
|
||||
let jobs = if flags.contains(JobCmdFlags::NEW_ONLY) {
|
||||
&self.jobs
|
||||
.iter()
|
||||
.filter(|job| job.as_ref().is_some_and(|job| self.new_updates.contains(&job.tabid().unwrap())))
|
||||
.map(|job| job.as_ref())
|
||||
.collect::<Vec<Option<&Job>>>()
|
||||
} else {
|
||||
&self.jobs
|
||||
.iter()
|
||||
.map(|job| job.as_ref())
|
||||
.collect::<Vec<Option<&Job>>>()
|
||||
};
|
||||
let mut jobs_to_remove = vec![];
|
||||
for job in jobs.iter().flatten() {
|
||||
// Skip foreground job
|
||||
let id = job.tabid().unwrap();
|
||||
// Filter jobs based on flags
|
||||
if flags.contains(JobCmdFlags::RUNNING) && !matches!(job.get_stats().get(id).unwrap(), WtStat::StillAlive | WtStat::Continued(_)) {
|
||||
continue;
|
||||
}
|
||||
if flags.contains(JobCmdFlags::STOPPED) && !matches!(job.get_stats().get(id).unwrap(), WtStat::Stopped(_,_)) {
|
||||
continue;
|
||||
}
|
||||
// Print the job in the selected format
|
||||
write(borrow_fd(1), format!("{}\n",job.display(&self.order,flags)).as_bytes())?;
|
||||
if job.get_stats().iter().all(|stat| matches!(stat,WtStat::Exited(_, _))) {
|
||||
jobs_to_remove.push(JobID::TableID(id));
|
||||
}
|
||||
}
|
||||
for id in jobs_to_remove {
|
||||
self.remove_job(id);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct VarTab {
|
||||
vars: HashMap<String,String>,
|
||||
params: HashMap<char,String>,
|
||||
sh_argv: VecDeque<String>, // Using a VecDeque makes the implementation of `shift` straightforward
|
||||
}
|
||||
|
||||
impl VarTab {
|
||||
pub fn new() -> Self {
|
||||
let vars = HashMap::new();
|
||||
let params = Self::init_params();
|
||||
Self { vars, params }
|
||||
let mut var_tab = Self { vars, params, sh_argv: VecDeque::new() };
|
||||
var_tab.init_sh_argv();
|
||||
var_tab
|
||||
}
|
||||
fn init_params() -> HashMap<char, String> {
|
||||
let mut params = HashMap::new();
|
||||
@@ -224,6 +30,37 @@ impl VarTab {
|
||||
params.insert('!', "".into()); // PID of the last background job (if any)
|
||||
params
|
||||
}
|
||||
pub fn init_sh_argv(&mut self) {
|
||||
for arg in env::args() {
|
||||
self.bpush_arg(arg);
|
||||
}
|
||||
}
|
||||
fn update_arg_params(&mut self) {
|
||||
self.set_param('@', &self.sh_argv.clone().to_vec().join(" "));
|
||||
self.set_param('#', &self.sh_argv.len().to_string());
|
||||
}
|
||||
/// Push an arg to the front of the arg deque
|
||||
pub fn fpush_arg(&mut self, arg: String) {
|
||||
self.sh_argv.push_front(arg);
|
||||
self.update_arg_params();
|
||||
}
|
||||
/// Push an arg to the back of the arg deque
|
||||
pub fn bpush_arg(&mut self, arg: String) {
|
||||
self.sh_argv.push_back(arg);
|
||||
self.update_arg_params();
|
||||
}
|
||||
/// Pop an arg from the front of the arg deque
|
||||
pub fn fpop_arg(&mut self) -> Option<String> {
|
||||
let arg = self.sh_argv.pop_front();
|
||||
self.update_arg_params();
|
||||
arg
|
||||
}
|
||||
/// Pop an arg from the back of the arg deque
|
||||
pub fn bpop_arg(&mut self) -> Option<String> {
|
||||
let arg = self.sh_argv.pop_back();
|
||||
self.update_arg_params();
|
||||
arg
|
||||
}
|
||||
pub fn vars(&self) -> &HashMap<String,String> {
|
||||
&self.vars
|
||||
}
|
||||
@@ -256,7 +93,17 @@ impl VarTab {
|
||||
self.params.insert(param,val.to_string());
|
||||
}
|
||||
pub fn get_param(&self, param: char) -> String {
|
||||
self.params.get(¶m).map(|s| s.to_string()).unwrap_or("0".to_string())
|
||||
if param.is_ascii_digit() {
|
||||
let argv_idx = param
|
||||
.to_string()
|
||||
.parse::<usize>()
|
||||
.unwrap();
|
||||
return self.sh_argv.get(argv_idx).map(|s| s.to_string()).unwrap_or_default()
|
||||
} else if param == '?' {
|
||||
self.params.get(¶m).map(|s| s.to_string()).unwrap_or("0".into())
|
||||
} else {
|
||||
self.params.get(¶m).map(|s| s.to_string()).unwrap_or_default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -290,3 +137,14 @@ pub fn get_status() -> i32 {
|
||||
pub fn set_status(code: i32) {
|
||||
write_vars(|v| v.set_param('?', &code.to_string()))
|
||||
}
|
||||
|
||||
pub fn source_file(path: PathBuf) -> ShResult<()> {
|
||||
let mut file = OpenOptions::new()
|
||||
.read(true)
|
||||
.open(path)?;
|
||||
|
||||
let mut buf = String::new();
|
||||
file.read_to_string(&mut buf)?;
|
||||
exec_input(&buf)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -34,3 +34,11 @@ fn lex_with_keywords() {
|
||||
|
||||
insta::assert_debug_snapshot!(tokens)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lex_multiline() {
|
||||
let input = "echo hello world\necho foo bar\necho boo biz";
|
||||
let tokens: Vec<_> = LexStream::new(input, LexFlags::empty()).collect();
|
||||
|
||||
insta::assert_debug_snapshot!(tokens)
|
||||
}
|
||||
|
||||
@@ -35,3 +35,15 @@ fn parse_conjunction_and_pipeline() {
|
||||
|
||||
insta::assert_debug_snapshot!(nodes)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_multiline() {
|
||||
let input = "
|
||||
echo hello world
|
||||
echo foo bar
|
||||
echo boo biz";
|
||||
let tk_stream: Vec<_> = LexStream::new(input, LexFlags::empty()).collect();
|
||||
let nodes: Vec<_> = ParseStream::new(tk_stream).collect();
|
||||
|
||||
insta::assert_debug_snapshot!(nodes)
|
||||
}
|
||||
|
||||
162
src/tests/snapshots/fern__tests__lexer__lex_multiline.snap
Normal file
162
src/tests/snapshots/fern__tests__lexer__lex_multiline.snap
Normal file
@@ -0,0 +1,162 @@
|
||||
---
|
||||
source: src/tests/lexer.rs
|
||||
expression: tokens
|
||||
---
|
||||
[
|
||||
Tk {
|
||||
class: SOI,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 0..0,
|
||||
source: "echo hello world\necho foo bar\necho boo biz",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 0..4,
|
||||
source: "echo hello world\necho foo bar\necho boo biz",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD | BUILTIN,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 5..10,
|
||||
source: "echo hello world\necho foo bar\necho boo biz",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 11..16,
|
||||
source: "echo hello world\necho foo bar\necho boo biz",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Sep,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 16..17,
|
||||
source: "echo hello world\necho foo bar\necho boo biz",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 17..21,
|
||||
source: "echo hello world\necho foo bar\necho boo biz",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD | BUILTIN,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 22..25,
|
||||
source: "echo hello world\necho foo bar\necho boo biz",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 26..29,
|
||||
source: "echo hello world\necho foo bar\necho boo biz",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Sep,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 29..30,
|
||||
source: "echo hello world\necho foo bar\necho boo biz",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 30..34,
|
||||
source: "echo hello world\necho foo bar\necho boo biz",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD | BUILTIN,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 35..38,
|
||||
source: "echo hello world\necho foo bar\necho boo biz",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 39..42,
|
||||
source: "echo hello world\necho foo bar\necho boo biz",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: EOI,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 42..42,
|
||||
source: "echo hello world\necho foo bar\necho boo biz",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
]
|
||||
@@ -24,7 +24,7 @@ expression: tokens
|
||||
source: "echo \"foo bar\" biz baz",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
IS_CMD | BUILTIN,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
|
||||
@@ -24,7 +24,7 @@ expression: tokens
|
||||
source: "echo foo > bar.txt",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
IS_CMD | BUILTIN,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
|
||||
@@ -24,7 +24,7 @@ expression: tokens
|
||||
source: "echo foo 1>&2",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
IS_CMD | BUILTIN,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
|
||||
@@ -24,7 +24,7 @@ expression: tokens
|
||||
source: "echo hello world",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
IS_CMD | BUILTIN,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
|
||||
@@ -72,7 +72,7 @@ expression: tokens
|
||||
source: "if true; then echo foo; fi",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
IS_CMD | BUILTIN,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
|
||||
@@ -24,7 +24,7 @@ expression: nodes
|
||||
source: "echo foo && echo bar",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
IS_CMD | BUILTIN,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
@@ -55,7 +55,7 @@ expression: nodes
|
||||
source: "echo foo && echo bar",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
IS_CMD | BUILTIN,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
@@ -89,7 +89,7 @@ expression: nodes
|
||||
source: "echo foo && echo bar",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
IS_CMD | BUILTIN,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
@@ -125,7 +125,7 @@ expression: nodes
|
||||
source: "echo foo && echo bar",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
IS_CMD | BUILTIN,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
@@ -156,7 +156,7 @@ expression: nodes
|
||||
source: "echo foo && echo bar",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
IS_CMD | BUILTIN,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
@@ -190,7 +190,7 @@ expression: nodes
|
||||
source: "echo foo && echo bar",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
IS_CMD | BUILTIN,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
@@ -225,7 +225,7 @@ expression: nodes
|
||||
source: "echo foo && echo bar",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
IS_CMD | BUILTIN,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
@@ -261,7 +261,7 @@ expression: nodes
|
||||
source: "echo foo && echo bar",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
IS_CMD | BUILTIN,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
|
||||
@@ -24,7 +24,7 @@ expression: nodes
|
||||
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
IS_CMD | BUILTIN,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
@@ -55,7 +55,7 @@ expression: nodes
|
||||
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
IS_CMD | BUILTIN,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
@@ -150,7 +150,7 @@ expression: nodes
|
||||
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
IS_CMD | BUILTIN,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
@@ -222,7 +222,7 @@ expression: nodes
|
||||
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
IS_CMD | BUILTIN,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
@@ -253,7 +253,7 @@ expression: nodes
|
||||
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
IS_CMD | BUILTIN,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
@@ -348,7 +348,7 @@ expression: nodes
|
||||
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
IS_CMD | BUILTIN,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
@@ -420,7 +420,7 @@ expression: nodes
|
||||
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
IS_CMD | BUILTIN,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
@@ -463,7 +463,7 @@ expression: nodes
|
||||
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
IS_CMD | BUILTIN,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
@@ -618,7 +618,7 @@ expression: nodes
|
||||
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
IS_CMD | BUILTIN,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
@@ -725,7 +725,7 @@ expression: nodes
|
||||
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
IS_CMD | BUILTIN,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
@@ -797,7 +797,7 @@ expression: nodes
|
||||
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
IS_CMD | BUILTIN,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
@@ -869,7 +869,7 @@ expression: nodes
|
||||
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
IS_CMD | BUILTIN,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
|
||||
639
src/tests/snapshots/fern__tests__parser__parse_multiline.snap
Normal file
639
src/tests/snapshots/fern__tests__parser__parse_multiline.snap
Normal file
@@ -0,0 +1,639 @@
|
||||
---
|
||||
source: src/tests/parser.rs
|
||||
expression: nodes
|
||||
---
|
||||
[
|
||||
Ok(
|
||||
Node {
|
||||
class: CmdList {
|
||||
elements: [
|
||||
ConjunctNode {
|
||||
cmd: Node {
|
||||
class: Pipeline {
|
||||
cmds: [
|
||||
Node {
|
||||
class: Command {
|
||||
assignments: [],
|
||||
argv: [
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 1..5,
|
||||
source: "\necho hello world\necho foo bar\necho boo biz",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD | BUILTIN,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 6..11,
|
||||
source: "\necho hello world\necho foo bar\necho boo biz",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 12..17,
|
||||
source: "\necho hello world\necho foo bar\necho boo biz",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
flags: NdFlags(
|
||||
0x0,
|
||||
),
|
||||
redirs: [],
|
||||
tokens: [
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 1..5,
|
||||
source: "\necho hello world\necho foo bar\necho boo biz",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD | BUILTIN,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 6..11,
|
||||
source: "\necho hello world\necho foo bar\necho boo biz",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 12..17,
|
||||
source: "\necho hello world\necho foo bar\necho boo biz",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Sep,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 17..18,
|
||||
source: "\necho hello world\necho foo bar\necho boo biz",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
pipe_err: false,
|
||||
},
|
||||
flags: NdFlags(
|
||||
0x0,
|
||||
),
|
||||
redirs: [],
|
||||
tokens: [
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 1..5,
|
||||
source: "\necho hello world\necho foo bar\necho boo biz",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD | BUILTIN,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 6..11,
|
||||
source: "\necho hello world\necho foo bar\necho boo biz",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 12..17,
|
||||
source: "\necho hello world\necho foo bar\necho boo biz",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Sep,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 17..18,
|
||||
source: "\necho hello world\necho foo bar\necho boo biz",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
operator: Null,
|
||||
},
|
||||
],
|
||||
},
|
||||
flags: NdFlags(
|
||||
0x0,
|
||||
),
|
||||
redirs: [],
|
||||
tokens: [
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 1..5,
|
||||
source: "\necho hello world\necho foo bar\necho boo biz",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD | BUILTIN,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 6..11,
|
||||
source: "\necho hello world\necho foo bar\necho boo biz",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 12..17,
|
||||
source: "\necho hello world\necho foo bar\necho boo biz",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Sep,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 17..18,
|
||||
source: "\necho hello world\necho foo bar\necho boo biz",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
),
|
||||
Ok(
|
||||
Node {
|
||||
class: CmdList {
|
||||
elements: [
|
||||
ConjunctNode {
|
||||
cmd: Node {
|
||||
class: Pipeline {
|
||||
cmds: [
|
||||
Node {
|
||||
class: Command {
|
||||
assignments: [],
|
||||
argv: [
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 18..22,
|
||||
source: "\necho hello world\necho foo bar\necho boo biz",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD | BUILTIN,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 23..26,
|
||||
source: "\necho hello world\necho foo bar\necho boo biz",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 27..30,
|
||||
source: "\necho hello world\necho foo bar\necho boo biz",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
flags: NdFlags(
|
||||
0x0,
|
||||
),
|
||||
redirs: [],
|
||||
tokens: [
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 18..22,
|
||||
source: "\necho hello world\necho foo bar\necho boo biz",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD | BUILTIN,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 23..26,
|
||||
source: "\necho hello world\necho foo bar\necho boo biz",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 27..30,
|
||||
source: "\necho hello world\necho foo bar\necho boo biz",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Sep,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 30..31,
|
||||
source: "\necho hello world\necho foo bar\necho boo biz",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
pipe_err: false,
|
||||
},
|
||||
flags: NdFlags(
|
||||
0x0,
|
||||
),
|
||||
redirs: [],
|
||||
tokens: [
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 18..22,
|
||||
source: "\necho hello world\necho foo bar\necho boo biz",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD | BUILTIN,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 23..26,
|
||||
source: "\necho hello world\necho foo bar\necho boo biz",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 27..30,
|
||||
source: "\necho hello world\necho foo bar\necho boo biz",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Sep,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 30..31,
|
||||
source: "\necho hello world\necho foo bar\necho boo biz",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
operator: Null,
|
||||
},
|
||||
],
|
||||
},
|
||||
flags: NdFlags(
|
||||
0x0,
|
||||
),
|
||||
redirs: [],
|
||||
tokens: [
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 18..22,
|
||||
source: "\necho hello world\necho foo bar\necho boo biz",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD | BUILTIN,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 23..26,
|
||||
source: "\necho hello world\necho foo bar\necho boo biz",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 27..30,
|
||||
source: "\necho hello world\necho foo bar\necho boo biz",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Sep,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 30..31,
|
||||
source: "\necho hello world\necho foo bar\necho boo biz",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
),
|
||||
Ok(
|
||||
Node {
|
||||
class: CmdList {
|
||||
elements: [
|
||||
ConjunctNode {
|
||||
cmd: Node {
|
||||
class: Pipeline {
|
||||
cmds: [
|
||||
Node {
|
||||
class: Command {
|
||||
assignments: [],
|
||||
argv: [
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 31..35,
|
||||
source: "\necho hello world\necho foo bar\necho boo biz",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD | BUILTIN,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 36..39,
|
||||
source: "\necho hello world\necho foo bar\necho boo biz",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 40..43,
|
||||
source: "\necho hello world\necho foo bar\necho boo biz",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
flags: NdFlags(
|
||||
0x0,
|
||||
),
|
||||
redirs: [],
|
||||
tokens: [
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 31..35,
|
||||
source: "\necho hello world\necho foo bar\necho boo biz",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD | BUILTIN,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 36..39,
|
||||
source: "\necho hello world\necho foo bar\necho boo biz",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 40..43,
|
||||
source: "\necho hello world\necho foo bar\necho boo biz",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
pipe_err: false,
|
||||
},
|
||||
flags: NdFlags(
|
||||
0x0,
|
||||
),
|
||||
redirs: [],
|
||||
tokens: [
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 31..35,
|
||||
source: "\necho hello world\necho foo bar\necho boo biz",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD | BUILTIN,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 36..39,
|
||||
source: "\necho hello world\necho foo bar\necho boo biz",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 40..43,
|
||||
source: "\necho hello world\necho foo bar\necho boo biz",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
operator: Null,
|
||||
},
|
||||
],
|
||||
},
|
||||
flags: NdFlags(
|
||||
0x0,
|
||||
),
|
||||
redirs: [],
|
||||
tokens: [
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 31..35,
|
||||
source: "\necho hello world\necho foo bar\necho boo biz",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD | BUILTIN,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 36..39,
|
||||
source: "\necho hello world\necho foo bar\necho boo biz",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
class: Str,
|
||||
err_span: None,
|
||||
err: Null,
|
||||
span: Span {
|
||||
range: 40..43,
|
||||
source: "\necho hello world\necho foo bar\necho boo biz",
|
||||
},
|
||||
flags: TkFlags(
|
||||
0x0,
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -24,7 +24,7 @@ expression: nodes
|
||||
source: "echo foo | sed s/foo/bar",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
IS_CMD | BUILTIN,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
@@ -55,7 +55,7 @@ expression: nodes
|
||||
source: "echo foo | sed s/foo/bar",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
IS_CMD | BUILTIN,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
@@ -150,7 +150,7 @@ expression: nodes
|
||||
source: "echo foo | sed s/foo/bar",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
IS_CMD | BUILTIN,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
@@ -221,7 +221,7 @@ expression: nodes
|
||||
source: "echo foo | sed s/foo/bar",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
IS_CMD | BUILTIN,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
|
||||
@@ -24,7 +24,7 @@ expression: nodes
|
||||
source: "echo hello world",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
IS_CMD | BUILTIN,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
@@ -67,7 +67,7 @@ expression: nodes
|
||||
source: "echo hello world",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
IS_CMD | BUILTIN,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
@@ -113,7 +113,7 @@ expression: nodes
|
||||
source: "echo hello world",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
IS_CMD | BUILTIN,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
@@ -160,7 +160,7 @@ expression: nodes
|
||||
source: "echo hello world",
|
||||
},
|
||||
flags: TkFlags(
|
||||
IS_CMD,
|
||||
IS_CMD | BUILTIN,
|
||||
),
|
||||
},
|
||||
Tk {
|
||||
|
||||
Reference in New Issue
Block a user