Various additions and improvements

This commit is contained in:
2025-03-15 21:04:45 -04:00
parent 7f21e5baa7
commit 505b968c60
31 changed files with 1421 additions and 341 deletions

29
src/builtin/cd.rs Normal file
View 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(())
}

View File

@@ -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))?; let child = ChildProc::new(Pid::this(), Some("echo"), Some(child_pgid))?;
job.push_child(child); job.push_child(child);
for redir in node.redirs { io_stack.append_to_frame(node.redirs);
io_stack.push_to_frame(redir);
}
let mut io_frame = io_stack.pop_frame(); let mut io_frame = io_stack.pop_frame();
io_frame.redirect()?; 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) let mut echo_output = prepare_argv(argv)
.into_iter() .into_iter()
.map(|a| a.0) // Extract the String from the tuple of (String,Span)
.skip(1) // Skip 'echo' .skip(1) // Skip 'echo'
.collect::<Vec<_>>() .collect::<Vec<_>>()
.join(" "); .join(" ");

32
src/builtin/export.rs Normal file
View 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(())
}

View File

@@ -1,5 +1,15 @@
pub mod echo; pub mod echo;
pub mod cd;
pub mod export;
pub mod pwd;
pub mod source;
pub mod shift;
pub const BUILTINS: [&str;1] = [ pub const BUILTINS: [&str;6] = [
"echo" "echo",
"cd",
"export",
"pwd",
"source",
"shift"
]; ];

30
src/builtin/pwd.rs Normal file
View 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
View 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
View 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(())
}

View File

@@ -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 /// Variable substitution marker
pub const VAR_SUB: char = '\u{fdd0}'; 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 { pub fn expand(self, span: Span<'t>, flags: TkFlags) -> Self {
let exp = Expander::new(self).expand(); let exp = Expander::new(self).expand();
let class = TkRule::Expanded { exp }; 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> { pub fn get_words(&self) -> Vec<String> {
match &self.class { match &self.class {
@@ -34,9 +34,10 @@ impl<'t> Expander {
} }
pub fn expand(&'t mut self) -> Vec<String> { pub fn expand(&'t mut self) -> Vec<String> {
self.raw = self.expand_raw(); 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) let tokens: Vec<_> = LexStream::new(&self.raw, LexFlags::RAW)
.filter(|tk| !matches!(tk.class, TkRule::EOI | TkRule::SOI)) .filter(|tk| !matches!(tk.as_ref().unwrap().class, TkRule::EOI | TkRule::SOI))
.map(|tk| tk.to_string()) .map(|tk| tk.unwrap().to_string())
.collect(); .collect();
tokens 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 /// 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 { pub fn unescape_str(raw: &str) -> String {
let mut chars = raw.chars(); let mut chars = raw.chars();

View File

@@ -11,6 +11,7 @@ pub mod signal;
#[cfg(test)] #[cfg(test)]
pub mod tests; pub mod tests;
use libsh::error::ShResult;
use parse::{execute::Dispatcher, lex::{LexFlags, LexStream}, ParseStream}; use parse::{execute::Dispatcher, lex::{LexFlags, LexStream}, ParseStream};
use termios::{LocalFlags, Termios}; use termios::{LocalFlags, Termios};
use crate::prelude::*; 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() { fn main() {
'main: loop { save_termios();
set_termios();
loop {
let input = prompt::read_line().unwrap(); let input = prompt::read_line().unwrap();
if input == "quit" { break };
let start = Instant::now(); let start = Instant::now();
let mut tokens = vec![]; if let Err(e) = exec_input(&input) {
for token in LexStream::new(&input, LexFlags::empty()) { eprintln!("{e}");
if token.is_err() {
let error = format!("{:?}: {}",token.err,token.err_span.unwrap().as_str());
panic!("{error}");
}
tokens.push(token);
} }
flog!(INFO, "cmd duration: {:?}", start.elapsed());
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());
} }
} }

View File

@@ -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 { pub struct JobBldr {
table_id: Option<usize>, table_id: Option<usize>,
pgid: Option<Pid>, pgid: Option<Pid>,
@@ -237,8 +435,15 @@ impl Job {
} }
pub fn wait_pgrp<'a>(&mut self) -> ShResult<Vec<WtStat>> { pub fn wait_pgrp<'a>(&mut self) -> ShResult<Vec<WtStat>> {
let mut stats = vec![]; let mut stats = vec![];
flog!(TRACE, "waiting on children");
flog!(TRACE, self.children);
for child in self.children.iter_mut() { 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 { match result {
Ok(stat) => { Ok(stat) => {
stats.push(stat); stats.push(stat);

View File

@@ -29,9 +29,8 @@ impl<'s> ShErr {
let msg = msg.into(); let msg = msg.into();
Self::Simple { kind, msg } 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 msg = msg.into();
let span = span.into();
Self::Full { kind, msg, span } Self::Full { kind, msg, span }
} }
pub fn unpack(self) -> (ShErrKind,String,Option<ErrSpan>) { pub fn unpack(self) -> (ShErrKind,String,Option<ErrSpan>) {

View File

@@ -2,3 +2,4 @@ pub mod error;
pub mod term; pub mod term;
pub mod flog; pub mod flog;
pub mod sys; pub mod sys;
pub mod utils;

11
src/libsh/utils.rs Normal file
View 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>>()
}
}

View File

@@ -1,9 +1,9 @@
use std::collections::VecDeque; 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 { pub enum AssignBehavior {
Export, Export,
@@ -26,11 +26,11 @@ impl ExecArgs {
let envp = Self::get_envp(); let envp = Self::get_envp();
Self { cmd, argv, envp } Self { cmd, argv, envp }
} }
pub fn get_cmd(argv: &[String]) -> CString { pub fn get_cmd(argv: &[(String,Span)]) -> CString {
CString::new(argv[0].as_str()).unwrap() CString::new(argv[0].0.as_str()).unwrap()
} }
pub fn get_argv(argv: Vec<String>) -> Vec<CString> { pub fn get_argv(argv: Vec<(String,Span)>) -> Vec<CString> {
argv.into_iter().map(|s| CString::new(s).unwrap()).collect() argv.into_iter().map(|s| CString::new(s.0).unwrap()).collect()
} }
pub fn get_envp() -> Vec<CString> { pub fn get_envp() -> Vec<CString> {
std::env::vars().map(|v| CString::new(format!("{}={}",v.0,v.1)).unwrap()).collect() 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(); let cmd_raw = cmd.get_command().unwrap();
flog!(TRACE, "doing builtin"); flog!(TRACE, "doing builtin");
let curr_job_mut = self.curr_job.as_mut().unwrap(); 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() { 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()) _ => 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); 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() { if argv.is_empty() {
return Ok(()) return Ok(())
} }
self.io_stack.append_to_frame(cmd.redirs);
let exec_args = ExecArgs::new(argv); let exec_args = ExecArgs::new(argv);
let io_frame = self.io_stack.pop_frame(); let io_frame = self.io_stack.pop_frame();
run_fork( 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![]; let mut args = vec![];
for arg in argv { for arg in argv {
let flags = arg.flags; let flags = arg.flags;
let span = arg.span.clone(); let span = arg.span.clone();
let expanded = arg.expand(span, flags); let expanded = arg.expand(span.clone(), flags);
args.extend(expanded.get_words()); for exp in expanded.get_words() {
args.push((exp,span.clone()))
}
} }
args args
} }

View File

@@ -2,7 +2,7 @@ use std::{fmt::Display, ops::{Bound, Deref, Range, RangeBounds}};
use bitflags::bitflags; use bitflags::bitflags;
use crate::{builtin::BUILTINS, prelude::*}; use crate::{builtin::BUILTINS, libsh::error::{ShErr, ShErrKind, ShResult}, prelude::*};
pub const KEYWORDS: [&'static str;14] = [ pub const KEYWORDS: [&'static str;14] = [
"if", "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)] #[derive(Clone,Debug,PartialEq,Default)]
pub struct Tk<'s> { pub struct Tk<'s> {
pub class: TkRule, pub class: TkRule,
pub err_span: Option<Span<'s>>,
pub err: TkErr,
pub span: Span<'s>, pub span: Span<'s>,
pub flags: TkFlags 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 // There's one impl here and then another in expand.rs which has the expansion logic
impl<'s> Tk<'s> { impl<'s> Tk<'s> {
pub fn new(class: TkRule, span: Span<'s>) -> Self { 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 { pub fn to_string(&self) -> String {
match &self.class { match &self.class {
@@ -129,13 +105,6 @@ impl<'s> Tk<'s> {
_ => self.span.as_str().to_string() _ => 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 { pub fn source(&self) -> &'s str {
self.span.source self.span.source
} }
@@ -231,7 +200,7 @@ impl<'t> LexStream<'t> {
pub fn next_is_not_cmd(&mut self) { pub fn next_is_not_cmd(&mut self) {
self.flags &= !LexFlags::NEXT_IS_CMD; 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()); assert!(self.cursor <= self.source.len());
let slice = self.slice(self.cursor..)?; let slice = self.slice(self.cursor..)?;
let mut pos = self.cursor; let mut pos = self.cursor;
@@ -259,10 +228,13 @@ impl<'t> LexStream<'t> {
if !found_fd { if !found_fd {
let err = TkErr::BadRedir; return Some(Err(
tk = self.get_token(self.cursor..pos, TkRule::Redir); ShErr::full(
tk.set_err(self.cursor..pos, self.source, err); ShErrKind::ParseErr,
break "Invalid redirection",
Span::new(self.cursor..pos, self.source).into()
)
));
} else { } else {
tk = self.get_token(self.cursor..pos, TkRule::Redir); tk = self.get_token(self.cursor..pos, TkRule::Redir);
break break
@@ -304,9 +276,9 @@ impl<'t> LexStream<'t> {
} }
self.cursor = pos; 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()); assert!(self.cursor <= self.source.len());
let slice = self.slice_from_cursor().unwrap(); let slice = self.slice_from_cursor().unwrap();
let mut pos = self.cursor; 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); let mut new_tk = self.get_token(self.cursor..pos, TkRule::Str);
if self.in_quote && !self.flags.contains(LexFlags::LEX_UNFINISHED) { if self.in_quote && !self.flags.contains(LexFlags::LEX_UNFINISHED) {
new_tk.set_err( return Err(
quote_pos.unwrap()..pos, ShErr::full(
self.source, ShErrKind::ParseErr,
TkErr::UntermQuote "Unterminated quote",
new_tk.span.into(),
)
); );
} }
if self.flags.contains(LexFlags::NEXT_IS_CMD) { if self.flags.contains(LexFlags::NEXT_IS_CMD) {
@@ -373,7 +347,7 @@ impl<'t> LexStream<'t> {
} }
} }
self.cursor = pos; self.cursor = pos;
new_tk Ok(new_tk)
} }
pub fn get_token(&self, range: Range<usize>, class: TkRule) -> Tk<'t> { pub fn get_token(&self, range: Range<usize>, class: TkRule) -> Tk<'t> {
let span = Span::new(range, self.source); let span = Span::new(range, self.source);
@@ -382,7 +356,7 @@ impl<'t> LexStream<'t> {
} }
impl<'t> Iterator for LexStream<'t> { impl<'t> Iterator for LexStream<'t> {
type Item = Tk<'t>; type Item = ShResult<Tk<'t>>;
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
assert!(self.cursor <= self.source.len()); assert!(self.cursor <= self.source.len());
// We are at the end of the input // We are at the end of the input
@@ -394,14 +368,14 @@ impl<'t> Iterator for LexStream<'t> {
// Return the EOI token // Return the EOI token
let token = self.get_token(self.cursor..self.cursor, TkRule::EOI); let token = self.get_token(self.cursor..self.cursor, TkRule::EOI);
self.flags |= LexFlags::STALE; self.flags |= LexFlags::STALE;
return Some(token) return Some(Ok(token))
} }
} }
// Return the SOI token // Return the SOI token
if self.flags.contains(LexFlags::FRESH) { if self.flags.contains(LexFlags::FRESH) {
self.flags &= !LexFlags::FRESH; self.flags &= !LexFlags::FRESH;
let token = self.get_token(self.cursor..self.cursor, TkRule::SOI); 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 // 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() { if let Some(tk) = self.read_redir() {
self.next_is_not_cmd(); self.next_is_not_cmd();
tk match tk {
Ok(tk) => tk,
Err(e) => return Some(Err(e))
}
} else { } else {
self.read_string() match self.read_string() {
Ok(tk) => tk,
Err(e) => return Some(Err(e))
}
} }
} }
}; };
Some(token) Some(Ok(token))
} }
} }

View File

@@ -25,6 +25,16 @@ impl<'t> Node<'t> {
let command = argv.iter().find(|tk| tk.flags.contains(TkFlags::IS_CMD))?; let command = argv.iter().find(|tk| tk.flags.contains(TkFlags::IS_CMD))?;
Some(command) 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! { bitflags! {
@@ -267,10 +277,12 @@ impl<'t> ParseStream<'t> {
}; };
let conjunction = ConjunctNode { cmd: Box::new(block), operator: conjunct_op }; let conjunction = ConjunctNode { cmd: Box::new(block), operator: conjunct_op };
elements.push(conjunction); elements.push(conjunction);
let Some(tk) = self.next_tk() else { if conjunct_op != ConjunctOp::Null {
break let Some(tk) = self.next_tk() else {
}; break
node_tks.push(tk); };
node_tks.push(tk);
}
if conjunct_op == ConjunctOp::Null { if conjunct_op == ConjunctOp::Null {
break break
} }
@@ -385,7 +397,7 @@ impl<'t> ParseStream<'t> {
ShErr::full( ShErr::full(
ShErrKind::ParseErr, ShErrKind::ParseErr,
"Expected a filename after this redirection", "Expected a filename after this redirection",
tk.span.clone() tk.span.clone().into()
) )
) )
}; };
@@ -420,7 +432,7 @@ impl<'t> ParseStream<'t> {
ShErr::full( ShErr::full(
ShErrKind::InternalErr, ShErrKind::InternalErr,
"Error opening file for redirection", "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() { match self.parse_cmd_list() {
Ok(Some(node)) => return Some(Ok(node)), Ok(Some(node)) => {
return Some(Ok(node));
}
Ok(None) => return None, Ok(None) => return None,
Err(e) => return Some(Err(e)) Err(e) => return Some(Err(e))
} }

View File

@@ -299,6 +299,9 @@ impl<'e> IoStack {
pub fn push_to_frame(&mut self, redir: Redir) { pub fn push_to_frame(&mut self, redir: Redir) {
self.curr_frame_mut().push(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 /// Pop the current stack frame
/// This differs from using `pop()` because it always returns a 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 /// If `self.pop()` would empty the `IoStack`, it instead uses `std::mem::take()` to take the last frame

View File

@@ -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 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 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 { pub struct VarTab {
vars: HashMap<String,String>, vars: HashMap<String,String>,
params: HashMap<char,String>, params: HashMap<char,String>,
sh_argv: VecDeque<String>, // Using a VecDeque makes the implementation of `shift` straightforward
} }
impl VarTab { impl VarTab {
pub fn new() -> Self { pub fn new() -> Self {
let vars = HashMap::new(); let vars = HashMap::new();
let params = Self::init_params(); 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> { fn init_params() -> HashMap<char, String> {
let mut params = HashMap::new(); let mut params = HashMap::new();
@@ -224,6 +30,37 @@ impl VarTab {
params.insert('!', "".into()); // PID of the last background job (if any) params.insert('!', "".into()); // PID of the last background job (if any)
params 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> { pub fn vars(&self) -> &HashMap<String,String> {
&self.vars &self.vars
} }
@@ -256,7 +93,17 @@ impl VarTab {
self.params.insert(param,val.to_string()); self.params.insert(param,val.to_string());
} }
pub fn get_param(&self, param: char) -> String { pub fn get_param(&self, param: char) -> String {
self.params.get(&param).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(&param).map(|s| s.to_string()).unwrap_or("0".into())
} else {
self.params.get(&param).map(|s| s.to_string()).unwrap_or_default()
}
} }
} }
@@ -290,3 +137,14 @@ pub fn get_status() -> i32 {
pub fn set_status(code: i32) { pub fn set_status(code: i32) {
write_vars(|v| v.set_param('?', &code.to_string())) 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(())
}

View File

@@ -34,3 +34,11 @@ fn lex_with_keywords() {
insta::assert_debug_snapshot!(tokens) 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)
}

View File

@@ -35,3 +35,15 @@ fn parse_conjunction_and_pipeline() {
insta::assert_debug_snapshot!(nodes) 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)
}

View 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,
),
},
]

View File

@@ -24,7 +24,7 @@ expression: tokens
source: "echo \"foo bar\" biz baz", source: "echo \"foo bar\" biz baz",
}, },
flags: TkFlags( flags: TkFlags(
IS_CMD, IS_CMD | BUILTIN,
), ),
}, },
Tk { Tk {

View File

@@ -24,7 +24,7 @@ expression: tokens
source: "echo foo > bar.txt", source: "echo foo > bar.txt",
}, },
flags: TkFlags( flags: TkFlags(
IS_CMD, IS_CMD | BUILTIN,
), ),
}, },
Tk { Tk {

View File

@@ -24,7 +24,7 @@ expression: tokens
source: "echo foo 1>&2", source: "echo foo 1>&2",
}, },
flags: TkFlags( flags: TkFlags(
IS_CMD, IS_CMD | BUILTIN,
), ),
}, },
Tk { Tk {

View File

@@ -24,7 +24,7 @@ expression: tokens
source: "echo hello world", source: "echo hello world",
}, },
flags: TkFlags( flags: TkFlags(
IS_CMD, IS_CMD | BUILTIN,
), ),
}, },
Tk { Tk {

View File

@@ -72,7 +72,7 @@ expression: tokens
source: "if true; then echo foo; fi", source: "if true; then echo foo; fi",
}, },
flags: TkFlags( flags: TkFlags(
IS_CMD, IS_CMD | BUILTIN,
), ),
}, },
Tk { Tk {

View File

@@ -24,7 +24,7 @@ expression: nodes
source: "echo foo && echo bar", source: "echo foo && echo bar",
}, },
flags: TkFlags( flags: TkFlags(
IS_CMD, IS_CMD | BUILTIN,
), ),
}, },
Tk { Tk {
@@ -55,7 +55,7 @@ expression: nodes
source: "echo foo && echo bar", source: "echo foo && echo bar",
}, },
flags: TkFlags( flags: TkFlags(
IS_CMD, IS_CMD | BUILTIN,
), ),
}, },
Tk { Tk {
@@ -89,7 +89,7 @@ expression: nodes
source: "echo foo && echo bar", source: "echo foo && echo bar",
}, },
flags: TkFlags( flags: TkFlags(
IS_CMD, IS_CMD | BUILTIN,
), ),
}, },
Tk { Tk {
@@ -125,7 +125,7 @@ expression: nodes
source: "echo foo && echo bar", source: "echo foo && echo bar",
}, },
flags: TkFlags( flags: TkFlags(
IS_CMD, IS_CMD | BUILTIN,
), ),
}, },
Tk { Tk {
@@ -156,7 +156,7 @@ expression: nodes
source: "echo foo && echo bar", source: "echo foo && echo bar",
}, },
flags: TkFlags( flags: TkFlags(
IS_CMD, IS_CMD | BUILTIN,
), ),
}, },
Tk { Tk {
@@ -190,7 +190,7 @@ expression: nodes
source: "echo foo && echo bar", source: "echo foo && echo bar",
}, },
flags: TkFlags( flags: TkFlags(
IS_CMD, IS_CMD | BUILTIN,
), ),
}, },
Tk { Tk {
@@ -225,7 +225,7 @@ expression: nodes
source: "echo foo && echo bar", source: "echo foo && echo bar",
}, },
flags: TkFlags( flags: TkFlags(
IS_CMD, IS_CMD | BUILTIN,
), ),
}, },
Tk { Tk {
@@ -261,7 +261,7 @@ expression: nodes
source: "echo foo && echo bar", source: "echo foo && echo bar",
}, },
flags: TkFlags( flags: TkFlags(
IS_CMD, IS_CMD | BUILTIN,
), ),
}, },
Tk { Tk {

View File

@@ -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/", source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
}, },
flags: TkFlags( flags: TkFlags(
IS_CMD, IS_CMD | BUILTIN,
), ),
}, },
Tk { 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/", source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
}, },
flags: TkFlags( flags: TkFlags(
IS_CMD, IS_CMD | BUILTIN,
), ),
}, },
Tk { 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/", source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
}, },
flags: TkFlags( flags: TkFlags(
IS_CMD, IS_CMD | BUILTIN,
), ),
}, },
Tk { 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/", source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
}, },
flags: TkFlags( flags: TkFlags(
IS_CMD, IS_CMD | BUILTIN,
), ),
}, },
Tk { 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/", source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
}, },
flags: TkFlags( flags: TkFlags(
IS_CMD, IS_CMD | BUILTIN,
), ),
}, },
Tk { 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/", source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
}, },
flags: TkFlags( flags: TkFlags(
IS_CMD, IS_CMD | BUILTIN,
), ),
}, },
Tk { 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/", source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
}, },
flags: TkFlags( flags: TkFlags(
IS_CMD, IS_CMD | BUILTIN,
), ),
}, },
Tk { 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/", source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
}, },
flags: TkFlags( flags: TkFlags(
IS_CMD, IS_CMD | BUILTIN,
), ),
}, },
Tk { 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/", source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
}, },
flags: TkFlags( flags: TkFlags(
IS_CMD, IS_CMD | BUILTIN,
), ),
}, },
Tk { 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/", source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
}, },
flags: TkFlags( flags: TkFlags(
IS_CMD, IS_CMD | BUILTIN,
), ),
}, },
Tk { 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/", source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
}, },
flags: TkFlags( flags: TkFlags(
IS_CMD, IS_CMD | BUILTIN,
), ),
}, },
Tk { 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/", source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
}, },
flags: TkFlags( flags: TkFlags(
IS_CMD, IS_CMD | BUILTIN,
), ),
}, },
Tk { Tk {

View 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,
),
},
],
},
),
]

View File

@@ -24,7 +24,7 @@ expression: nodes
source: "echo foo | sed s/foo/bar", source: "echo foo | sed s/foo/bar",
}, },
flags: TkFlags( flags: TkFlags(
IS_CMD, IS_CMD | BUILTIN,
), ),
}, },
Tk { Tk {
@@ -55,7 +55,7 @@ expression: nodes
source: "echo foo | sed s/foo/bar", source: "echo foo | sed s/foo/bar",
}, },
flags: TkFlags( flags: TkFlags(
IS_CMD, IS_CMD | BUILTIN,
), ),
}, },
Tk { Tk {
@@ -150,7 +150,7 @@ expression: nodes
source: "echo foo | sed s/foo/bar", source: "echo foo | sed s/foo/bar",
}, },
flags: TkFlags( flags: TkFlags(
IS_CMD, IS_CMD | BUILTIN,
), ),
}, },
Tk { Tk {
@@ -221,7 +221,7 @@ expression: nodes
source: "echo foo | sed s/foo/bar", source: "echo foo | sed s/foo/bar",
}, },
flags: TkFlags( flags: TkFlags(
IS_CMD, IS_CMD | BUILTIN,
), ),
}, },
Tk { Tk {

View File

@@ -24,7 +24,7 @@ expression: nodes
source: "echo hello world", source: "echo hello world",
}, },
flags: TkFlags( flags: TkFlags(
IS_CMD, IS_CMD | BUILTIN,
), ),
}, },
Tk { Tk {
@@ -67,7 +67,7 @@ expression: nodes
source: "echo hello world", source: "echo hello world",
}, },
flags: TkFlags( flags: TkFlags(
IS_CMD, IS_CMD | BUILTIN,
), ),
}, },
Tk { Tk {
@@ -113,7 +113,7 @@ expression: nodes
source: "echo hello world", source: "echo hello world",
}, },
flags: TkFlags( flags: TkFlags(
IS_CMD, IS_CMD | BUILTIN,
), ),
}, },
Tk { Tk {
@@ -160,7 +160,7 @@ expression: nodes
source: "echo hello world", source: "echo hello world",
}, },
flags: TkFlags( flags: TkFlags(
IS_CMD, IS_CMD | BUILTIN,
), ),
}, },
Tk { Tk {