Implemented assignments, working on job control

This commit is contained in:
2025-03-15 16:56:53 -04:00
parent 97b4b1835d
commit 2acf70ef96
25 changed files with 1390 additions and 280 deletions

2
.gitignore vendored
View File

@@ -9,7 +9,7 @@ shell.nix
*~
TODO.md
rust-toolchain.toml
*snapshot*
*src_old
# cachix tmp file
store-path-pre-build

168
Cargo.lock generated
View File

@@ -29,12 +29,42 @@ dependencies = [
"error-code",
]
[[package]]
name = "console"
version = "0.15.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8"
dependencies = [
"encode_unicode",
"libc",
"once_cell",
"windows-sys 0.59.0",
]
[[package]]
name = "diff"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8"
[[package]]
name = "encode_unicode"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0"
[[package]]
name = "endian-type"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d"
[[package]]
name = "equivalent"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "errno"
version = "0.3.10"
@@ -67,10 +97,20 @@ name = "fern"
version = "0.1.0"
dependencies = [
"bitflags",
"insta",
"nix",
"pretty_assertions",
"rustyline",
"serde",
"serde_yaml",
]
[[package]]
name = "hashbrown"
version = "0.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289"
[[package]]
name = "home"
version = "0.5.11"
@@ -80,12 +120,47 @@ dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "indexmap"
version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3954d50fe15b02142bf25d3b8bdadb634ec3948f103d04ffe3031bc8fe9d7058"
dependencies = [
"equivalent",
"hashbrown",
]
[[package]]
name = "insta"
version = "1.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50259abbaa67d11d2bcafc7ba1d094ed7a0c70e3ce893f0d0997f73558cb3084"
dependencies = [
"console",
"linked-hash-map",
"once_cell",
"pin-project",
"similar",
]
[[package]]
name = "itoa"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
[[package]]
name = "libc"
version = "0.2.169"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a"
[[package]]
name = "linked-hash-map"
version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
[[package]]
name = "linux-raw-sys"
version = "0.4.15"
@@ -125,6 +200,42 @@ dependencies = [
"libc",
]
[[package]]
name = "once_cell"
version = "1.21.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d75b0bedcc4fe52caa0e03d9f1151a323e4aa5e2d78ba3580400cd3c9e2bc4bc"
[[package]]
name = "pin-project"
version = "1.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a"
dependencies = [
"pin-project-internal",
]
[[package]]
name = "pin-project-internal"
version = "1.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "pretty_assertions"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d"
dependencies = [
"diff",
"yansi",
]
[[package]]
name = "proc-macro2"
version = "1.0.93"
@@ -200,6 +311,51 @@ dependencies = [
"syn",
]
[[package]]
name = "ryu"
version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
[[package]]
name = "serde"
version = "1.0.219"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.219"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_yaml"
version = "0.9.34+deprecated"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47"
dependencies = [
"indexmap",
"itoa",
"ryu",
"serde",
"unsafe-libyaml",
]
[[package]]
name = "similar"
version = "2.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa"
[[package]]
name = "smallvec"
version = "1.14.0"
@@ -235,6 +391,12 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd"
[[package]]
name = "unsafe-libyaml"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861"
[[package]]
name = "utf8parse"
version = "0.2.2"
@@ -322,3 +484,9 @@ name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "yansi"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"

View File

@@ -11,5 +11,13 @@ debug = true
[dependencies]
bitflags = "2.8.0"
insta = "1.42.2"
nix = { version = "0.29.0", features = ["uio", "term", "user", "hostname", "fs", "default", "signal", "process", "event", "ioctl"] }
pretty_assertions = "1.4.1"
rustyline = { version = "15.0.0", features = [ "derive" ] }
serde = { version = "1.0.219", features = [ "derive" ] }
serde_yaml = "0.9.34"
[[bin]]
name = "fern"
path = "src/fern.rs"

30
src/builtin/echo.rs Normal file
View File

@@ -0,0 +1,30 @@
use crate::{libsh::error::ShResult, parse::{execute::prepare_argv, NdRule, Node}, prelude::*, procio::{borrow_fd, IoStack}};
pub fn echo(node: Node, io_stack: &mut IoStack) -> ShResult<()> {
let NdRule::Command { assignments: _, argv } = node.class else {
unreachable!()
};
assert!(!argv.is_empty());
for redir in node.redirs {
io_stack.push_to_frame(redir);
}
let mut io_frame = io_stack.pop_frame();
io_frame.redirect()?;
let stdout = borrow_fd(STDOUT_FILENO);
let mut echo_output = prepare_argv(argv)
.into_iter()
.skip(1) // Skip 'echo'
.collect::<Vec<_>>()
.join(" ");
echo_output.push('\n');
write(stdout, echo_output.as_bytes())?;
io_frame.restore()?;
Ok(())
}

5
src/builtin/mod.rs Normal file
View File

@@ -0,0 +1,5 @@
pub mod echo;
pub const BUILTINS: [&str;1] = [
"echo"
];

View File

@@ -5,19 +5,19 @@ pub mod procio;
pub mod parse;
pub mod expand;
pub mod state;
pub mod builtin;
pub mod jobs;
#[cfg(test)]
pub mod tests;
use std::process::exit;
use parse::{execute::{get_pipe_stack, Dispatcher}, lex::{LexFlags, LexStream}, ParseResult, ParseStream};
use state::write_vars;
use parse::{execute::Dispatcher, lex::{LexFlags, LexStream}, ParseStream};
use crate::prelude::*;
fn main() {
loop {
'main: loop {
let input = prompt::read_line().unwrap();
if input == "quit" { break };
write_vars(|v| v.new_var("foo", "bar"));
let start = Instant::now();
let mut tokens = vec![];
for token in LexStream::new(&input, LexFlags::empty()) {
@@ -31,13 +31,16 @@ fn main() {
let mut nodes = vec![];
for result in ParseStream::new(tokens) {
match result {
ParseResult::Error(e) => panic!("{}",e),
ParseResult::Match(node) => nodes.push(node),
_ => unreachable!()
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());
}
}

390
src/jobs.rs Normal file
View File

@@ -0,0 +1,390 @@
use crate::{libsh::{error::ShResult, term::{Style, Styled}}, prelude::*, procio::borrow_fd};
bitflags! {
#[derive(Debug, Copy, Clone)]
pub struct JobCmdFlags: u8 {
const LONG = 0b0000_0001; // 0x01
const PIDS = 0b0000_0010; // 0x02
const NEW_ONLY = 0b0000_0100; // 0x04
const RUNNING = 0b0000_1000; // 0x08
const STOPPED = 0b0001_0000; // 0x10
const INIT = 0b0010_0000; // 0x20
}
}
#[derive(Debug)]
pub struct DisplayWaitStatus(pub WtStat);
impl fmt::Display for DisplayWaitStatus {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.0 {
WtStat::Exited(_, code) => {
match code {
0 => write!(f, "done"),
_ => write!(f, "failed: {}", code),
}
}
WtStat::Signaled(_, signal, _) => {
write!(f, "signaled: {:?}", signal)
}
WtStat::Stopped(_, signal) => {
write!(f, "stopped: {:?}", signal)
}
WtStat::PtraceEvent(_, signal, _) => {
write!(f, "ptrace event: {:?}", signal)
}
WtStat::PtraceSyscall(_) => {
write!(f, "ptrace syscall")
}
WtStat::Continued(_) => {
write!(f, "continued")
}
WtStat::StillAlive => {
write!(f, "running")
}
}
}
}
#[derive(Clone,Debug)]
pub enum JobID {
Pgid(Pid),
Pid(Pid),
TableID(usize),
Command(String)
}
#[derive(Debug,Clone)]
pub struct ChildProc {
pgid: Pid,
pid: Pid,
command: Option<String>,
stat: WtStat
}
impl<'a> ChildProc {
pub fn new(pid: Pid, command: Option<&str>, pgid: Option<Pid>) -> ShResult<Self> {
let command = command.map(|str| str.to_string());
let stat = if kill(pid,None).is_ok() {
WtStat::StillAlive
} else {
WtStat::Exited(pid, 0)
};
let mut child = Self { pgid: pid, pid, command, stat };
if let Some(pgid) = pgid {
child.set_pgid(pgid).ok();
}
Ok(child)
}
pub fn pid(&self) -> Pid {
self.pid
}
pub fn pgid(&self) -> Pid {
self.pgid
}
pub fn cmd(&self) -> Option<&str> {
self.command.as_ref().map(|cmd| cmd.as_str())
}
pub fn stat(&self) -> WtStat {
self.stat
}
pub fn wait(&mut self, flags: Option<WtFlag>) -> Result<WtStat,Errno> {
let result = waitpid(self.pid, flags);
if let Ok(stat) = result {
self.stat = stat
}
result
}
pub fn kill<T: Into<Option<Signal>>>(&self, sig: T) -> ShResult<()> {
Ok(kill(self.pid, sig)?)
}
pub fn set_pgid(&mut self, pgid: Pid) -> ShResult<()> {
setpgid(self.pid, pgid)?;
self.pgid = pgid;
Ok(())
}
pub fn set_stat(&mut self, stat: WtStat) {
self.stat = stat
}
pub fn is_alive(&self) -> bool {
self.stat == WtStat::StillAlive
}
pub fn is_stopped(&self) -> bool {
matches!(self.stat,WtStat::Stopped(..))
}
pub fn exited(&self) -> bool {
matches!(self.stat,WtStat::Exited(..))
}
}
pub struct JobBldr {
table_id: Option<usize>,
pgid: Option<Pid>,
children: Vec<ChildProc>
}
impl Default for JobBldr {
fn default() -> Self {
Self::new()
}
}
impl JobBldr {
pub fn new() -> Self {
Self { table_id: None, pgid: None, children: vec![] }
}
pub fn with_id(self, id: usize) -> Self {
Self {
table_id: Some(id),
pgid: self.pgid,
children: self.children
}
}
pub fn with_pgid(self, pgid: Pid) -> Self {
Self {
table_id: self.table_id,
pgid: Some(pgid),
children: self.children
}
}
pub fn with_children(self, children: Vec<ChildProc>) -> Self {
Self {
table_id: self.table_id,
pgid: self.pgid,
children
}
}
pub fn build(self) -> Job {
Job {
table_id: self.table_id,
pgid: self.pgid.unwrap_or(Pid::from_raw(0)),
children: self.children
}
}
}
#[derive(Debug,Clone)]
pub struct Job {
table_id: Option<usize>,
pgid: Pid,
children: Vec<ChildProc>
}
impl Job {
pub fn set_tabid(&mut self, id: usize) {
self.table_id = Some(id)
}
pub fn running(&self) -> bool {
!self.children.iter().all(|chld| chld.exited())
}
pub fn tabid(&self) -> Option<usize> {
self.table_id
}
pub fn pgid(&self) -> Pid {
self.pgid
}
pub fn get_cmds(&self) -> Vec<&str> {
let mut cmds = vec![];
for child in &self.children {
cmds.push(child.cmd().unwrap_or_default())
}
cmds
}
pub fn set_stats(&mut self, stat: WtStat) {
for child in self.children.iter_mut() {
child.set_stat(stat);
}
}
pub fn get_stats(&self) -> Vec<WtStat> {
self.children
.iter()
.map(|chld| chld.stat())
.collect::<Vec<WtStat>>()
}
pub fn get_pids(&self) -> Vec<Pid> {
self.children
.iter()
.map(|chld| chld.pid())
.collect::<Vec<Pid>>()
}
pub fn children(&self) -> &[ChildProc] {
&self.children
}
pub fn children_mut(&mut self) -> &mut Vec<ChildProc> {
&mut self.children
}
pub fn killpg(&mut self, sig: Signal) -> ShResult<()> {
let stat = match sig {
Signal::SIGTSTP => WtStat::Stopped(self.pgid, Signal::SIGTSTP),
Signal::SIGCONT => WtStat::Continued(self.pgid),
Signal::SIGTERM => WtStat::Signaled(self.pgid, Signal::SIGTERM, false),
_ => unimplemented!("{}",sig)
};
self.set_stats(stat);
Ok(killpg(self.pgid, sig)?)
}
pub fn wait_pgrp<'a>(&mut self) -> ShResult<Vec<WtStat>> {
let mut stats = vec![];
for child in self.children.iter_mut() {
let result = child.wait(Some(WtFlag::WUNTRACED));
match result {
Ok(stat) => {
stats.push(stat);
}
Err(Errno::ECHILD) => break,
Err(e) => return Err(e.into())
}
}
Ok(stats)
}
pub fn update_by_id(&mut self, id: JobID, stat: WtStat) -> ShResult<()> {
match id {
JobID::Pid(pid) => {
let query_result = self.children.iter_mut().find(|chld| chld.pid == pid);
if let Some(child) = query_result {
child.set_stat(stat);
}
}
JobID::Command(cmd) => {
let query_result = self.children
.iter_mut()
.find(|chld| chld
.cmd()
.is_some_and(|chld_cmd| chld_cmd.contains(&cmd))
);
if let Some(child) = query_result {
child.set_stat(stat);
}
}
JobID::TableID(tid) => {
if self.table_id.is_some_and(|tblid| tblid == tid) {
for child in self.children.iter_mut() {
child.set_stat(stat);
}
}
}
JobID::Pgid(pgid) => {
if pgid == self.pgid {
for child in self.children.iter_mut() {
child.set_stat(stat);
}
}
}
}
Ok(())
}
pub fn display(&self, job_order: &[usize], flags: JobCmdFlags) -> String {
let long = flags.contains(JobCmdFlags::LONG);
let init = flags.contains(JobCmdFlags::INIT);
let pids = flags.contains(JobCmdFlags::PIDS);
let current = job_order.last();
let prev = if job_order.len() > 2 {
job_order.get(job_order.len() - 2)
} else {
None
};
let id = self.table_id.unwrap();
let symbol = if current == self.table_id.as_ref() {
"+"
} else if prev == self.table_id.as_ref() {
"-"
} else {
" "
};
let padding_count = symbol.len() + id.to_string().len() + 3;
let padding = " ".repeat(padding_count);
let mut output = format!("[{}]{}\t", id + 1, symbol);
for (i, cmd) in self.get_cmds().iter().enumerate() {
let pid = if pids || init {
let mut pid = self.get_pids().get(i).unwrap().to_string();
pid.push(' ');
pid
} else {
"".to_string()
};
let job_stat = *self.get_stats().get(i).unwrap();
let fmt_stat = DisplayWaitStatus(job_stat).to_string();
let mut stat_line = if init {
"".to_string()
} else {
fmt_stat.clone()
};
stat_line = format!("{}{} ",pid,stat_line);
stat_line = format!("{} {}", stat_line, cmd);
stat_line = match job_stat {
WtStat::Stopped(..) | WtStat::Signaled(..) => stat_line.styled(Style::Magenta),
WtStat::Exited(_, code) => {
match code {
0 => stat_line.styled(Style::Green),
_ => stat_line.styled(Style::Red),
}
}
_ => stat_line.styled(Style::Cyan)
};
if i != self.get_cmds().len() - 1 {
stat_line = format!("{} |",stat_line);
}
let stat_final = if long {
format!(
"{}{} {}",
if i != 0 { &padding } else { "" },
self.get_pids().get(i).unwrap(),
stat_line
)
} else {
format!(
"{}{}",
if i != 0 { &padding } else { "" },
stat_line
)
};
output.push_str(&stat_final);
output.push('\n');
}
output
}
}
pub fn term_ctlr() -> Pid {
tcgetpgrp(borrow_fd(0)).unwrap_or(getpgrp())
}
pub fn attach_tty(pgid: Pid) -> ShResult<()> {
// If we aren't attached to a terminal, the pgid already controls it, or the process group does not exist
// Then return ok
if !isatty(0).unwrap_or(false) || pgid == term_ctlr() || killpg(pgid, None).is_err() {
return Ok(())
}
flog!(TRACE, "Attaching tty to pgid: {}",pgid);
if pgid == getpgrp() && term_ctlr() != getpgrp() {
kill(term_ctlr(), Signal::SIGTTOU).ok();
}
let mut new_mask = SigSet::empty();
let mut mask_bkup = SigSet::empty();
new_mask.add(Signal::SIGTSTP);
new_mask.add(Signal::SIGTTIN);
new_mask.add(Signal::SIGTTOU);
pthread_sigmask(SigmaskHow::SIG_BLOCK, Some(&mut new_mask), Some(&mut mask_bkup))?;
let result = tcsetpgrp(borrow_fd(0), pgid);
pthread_sigmask(SigmaskHow::SIG_SETMASK, Some(&mut mask_bkup), Some(&mut new_mask))?;
match result {
Ok(_) => return Ok(()),
Err(e) => {
flog!(ERROR, "error while switching term control: {}",e);
tcsetpgrp(borrow_fd(0), getpgrp())?;
Ok(())
}
}
}

View File

@@ -1,25 +1,40 @@
use std::{fmt::Display, str::FromStr};
use std::{fmt::Display, ops::Range, str::FromStr};
use crate::{parse::lex::Span, prelude::*};
pub type ShResult<'s,T> = Result<T,ShErr<'s>>;
pub type ShResult<T> = Result<T,ShErr>;
#[derive(Debug)]
pub enum ShErr<'s> {
Simple { kind: ShErrKind, msg: String },
Full { kind: ShErrKind, msg: String, span: Span<'s> }
pub struct ErrSpan {
range: Range<usize>,
source: String
}
impl<'s> ShErr<'s> {
impl<'s> From<Span<'s>> for ErrSpan {
fn from(value: Span<'s>) -> Self {
let range = value.range();
let source = value.get_source().to_string();
Self { range, source }
}
}
#[derive(Debug)]
pub enum ShErr {
Simple { kind: ShErrKind, msg: String },
Full { kind: ShErrKind, msg: String, span: ErrSpan }
}
impl<'s> ShErr {
pub fn simple(kind: ShErrKind, msg: impl Into<String>) -> Self {
let msg = msg.into();
Self::Simple { kind, msg }
}
pub fn full(kind: ShErrKind, msg: impl Into<String>, span: Span<'s>) -> Self {
let msg = msg.into();
let span = span.into();
Self::Full { kind, msg, span }
}
pub fn unpack(self) -> (ShErrKind,String,Option<Span<'s>>) {
pub fn unpack(self) -> (ShErrKind,String,Option<ErrSpan>) {
match self {
ShErr::Simple { kind, msg } => (kind,msg,None),
ShErr::Full { kind, msg, span } => (kind,msg,Some(span))
@@ -27,11 +42,12 @@ impl<'s> ShErr<'s> {
}
pub fn with_span(sherr: ShErr, span: Span<'s>) -> Self {
let (kind,msg,_) = sherr.unpack();
let span = span.into();
Self::Full { kind, msg, span }
}
}
impl<'s> Display for ShErr<'s> {
impl Display for ShErr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Simple { msg, kind: _ } => writeln!(f, "{}", msg),
@@ -40,26 +56,26 @@ impl<'s> Display for ShErr<'s> {
}
}
impl<'s> From<std::io::Error> for ShErr<'s> {
impl From<std::io::Error> for ShErr {
fn from(_: std::io::Error) -> Self {
let msg = std::io::Error::last_os_error();
ShErr::simple(ShErrKind::IoErr, msg.to_string())
}
}
impl<'s> From<std::env::VarError> for ShErr<'s> {
impl From<std::env::VarError> for ShErr {
fn from(value: std::env::VarError) -> Self {
ShErr::simple(ShErrKind::InternalErr, &value.to_string())
}
}
impl<'s> From<rustyline::error::ReadlineError> for ShErr<'s> {
impl From<rustyline::error::ReadlineError> for ShErr {
fn from(value: rustyline::error::ReadlineError) -> Self {
ShErr::simple(ShErrKind::ParseErr, &value.to_string())
}
}
impl<'s> From<Errno> for ShErr<'s> {
impl From<Errno> for ShErr {
fn from(value: Errno) -> Self {
ShErr::simple(ShErrKind::Errno, &value.to_string())
}

144
src/libsh/flog.rs Normal file
View File

@@ -0,0 +1,144 @@
use std::fmt::Display;
use super::term::{Style, Styled};
#[derive(Clone, Copy, PartialEq, PartialOrd, Ord, Eq , Debug)]
#[repr(u8)]
pub enum FernLogLevel {
NONE = 0,
ERROR = 1,
WARN = 2,
INFO = 3,
DEBUG = 4,
TRACE = 5
}
impl Display for FernLogLevel {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
use FernLogLevel::*;
match self {
ERROR => write!(f,"{}","ERROR".styled(Style::Red | Style::Bold)),
WARN => write!(f,"{}","WARN".styled(Style::Yellow | Style::Bold)),
INFO => write!(f,"{}","INFO".styled(Style::Green | Style::Bold)),
DEBUG => write!(f,"{}","DEBUG".styled(Style::Magenta | Style::Bold)),
TRACE => write!(f,"{}","TRACE".styled(Style::Blue | Style::Bold)),
NONE => write!(f,"")
}
}
}
pub fn log_level() -> FernLogLevel {
use FernLogLevel::*;
let level = std::env::var("FERN_LOG_LEVEL").unwrap_or_default();
match level.to_lowercase().as_str() {
"error" => ERROR,
"warn" => WARN,
"info" => INFO,
"debug" => DEBUG,
"trace" => TRACE,
_ => NONE
}
}
/// A structured logging macro designed for `fern`.
///
/// `flog!` was implemented because `rustyline` uses `env_logger`, which clutters the debug output.
/// This macro prints log messages in a structured format, including the log level, filename, and line number.
///
/// # Usage
///
/// The macro supports three types of arguments:
///
/// ## 1. **Formatted Messages**
/// Similar to `println!` or `format!`, allows embedding values inside a formatted string.
///
/// ```rust
/// flog!(ERROR, "foo is {}", foo);
/// ```
/// **Output:**
/// ```plaintext
/// [ERROR][file.rs:10] foo is <value of foo>
/// ```
///
/// ## 2. **Literals**
/// Directly prints each literal argument as a separate line.
///
/// ```rust
/// flog!(WARN, "foo", "bar");
/// ```
/// **Output:**
/// ```plaintext
/// [WARN][file.rs:10] foo
/// [WARN][file.rs:10] bar
/// ```
///
/// ## 3. **Expressions**
/// Logs the evaluated result of each given expression, displaying both the expression and its value.
///
/// ```rust
/// flog!(INFO, 1.min(2));
/// ```
/// **Output:**
/// ```plaintext
/// [INFO][file.rs:10] 1
/// ```
///
/// # Considerations
/// - This macro uses `eprintln!()` internally, so its formatting rules must be followed.
/// - **Literals and formatted messages** require arguments that implement [`std::fmt::Display`].
/// - **Expressions** require arguments that implement [`std::fmt::Debug`].
#[macro_export]
macro_rules! flog {
($level:path, $fmt:literal, $($args:expr),+ $(,)?) => {{
use $crate::libsh::flog::log_level;
use $crate::libsh::term::Styled;
use $crate::libsh::term::Style;
if $level <= log_level() {
let file = file!().styled(Style::Cyan);
let line = line!().to_string().styled(Style::Cyan);
eprintln!(
"[{}][{}:{}] {}",
$level, file, line, format!($fmt, $($args),+)
);
}
}};
($level:path, $($val:expr),+ $(,)?) => {{
use $crate::libsh::flog::log_level;
use $crate::libsh::term::Styled;
use $crate::libsh::term::Style;
if $level <= log_level() {
let file = file!().styled(Style::Cyan);
let line = line!().to_string().styled(Style::Cyan);
$(
let val_name = stringify!($val);
eprintln!(
"[{}][{}:{}] {} = {:#?}",
$level, file, line, val_name, &$val
);
)+
}
}};
($level:path, $($lit:literal),+ $(,)?) => {{
use $crate::libsh::flog::log_level;
use $crate::libsh::term::Styled;
use $crate::libsh::term::Style;
if $level <= log_level() {
let file = file!().styled(Style::Cyan);
let line = line!().to_string().styled(Style::Cyan);
$(
eprintln!(
"[{}][{}:{}] {}",
$level, file, line, $lit
);
)+
}
}};
}

View File

@@ -1,2 +1,3 @@
pub mod error;
pub mod term;
pub mod flog;

View File

@@ -2,9 +2,14 @@ use std::collections::VecDeque;
use nix::sys::wait::WaitPidFlag;
use crate::{libsh::error::ShResult, prelude::*, procio::{IoFrame, IoPipe, IoStack}, state};
use crate::{builtin::echo::echo, libsh::error::ShResult, prelude::*, procio::{IoFrame, IoPipe, IoStack}, state::{self, write_vars}};
use super::{lex::Tk, ConjunctNode, ConjunctOp, NdRule, Node, Redir, RedirType};
use super::{lex::{Tk, TkFlags}, AssignKind, ConjunctNode, ConjunctOp, NdRule, Node, Redir, RedirType};
pub enum AssignBehavior {
Export,
Set
}
/// Arguments to the execvpe function
pub struct ExecArgs {
@@ -14,8 +19,9 @@ pub struct ExecArgs {
}
impl ExecArgs {
pub fn new(argv: Vec<String>) -> Self {
pub fn new(argv: Vec<Tk>) -> Self {
assert!(!argv.is_empty());
let argv = prepare_argv(argv);
let cmd = Self::get_cmd(&argv);
let argv = Self::get_argv(argv);
let envp = Self::get_envp();
@@ -42,22 +48,33 @@ impl<'t> Dispatcher<'t> {
let nodes = VecDeque::from(nodes);
Self { nodes, io_stack: IoStack::new() }
}
pub fn begin_dispatch(&mut self) -> ShResult<'t,()> {
pub fn begin_dispatch(&mut self) -> ShResult<()> {
flog!(TRACE, "beginning dispatch");
while let Some(list) = self.nodes.pop_front() {
self.dispatch_node(list)?;
}
Ok(())
}
pub fn dispatch_node(&mut self, node: Node<'t>) -> ShResult<'t,()> {
pub fn dispatch_node(&mut self, node: Node<'t>) -> ShResult<()> {
match node.class {
NdRule::CmdList {..} => self.exec_conjunction(node)?,
NdRule::Pipeline {..} => self.exec_pipeline(node)?,
NdRule::Command {..} => self.exec_cmd(node)?,
NdRule::Command {..} => self.dispatch_cmd(node)?,
_ => unreachable!()
}
Ok(())
}
pub fn exec_conjunction(&mut self, conjunction: Node<'t>) -> ShResult<'t,()> {
pub fn dispatch_cmd(&mut self, node: Node<'t>) -> ShResult<()> {
let Some(cmd) = node.get_command() else {
return self.exec_cmd(node)
};
if cmd.flags.contains(TkFlags::BUILTIN) {
self.exec_builtin(node)
} else {
self.exec_cmd(node)
}
}
pub fn exec_conjunction(&mut self, conjunction: Node<'t>) -> ShResult<()> {
let NdRule::CmdList { elements } = conjunction.class else {
unreachable!()
};
@@ -76,7 +93,7 @@ impl<'t> Dispatcher<'t> {
}
Ok(())
}
pub fn exec_pipeline(&mut self, pipeline: Node<'t>) -> ShResult<'t,()> {
pub fn exec_pipeline(&mut self, pipeline: Node<'t>) -> ShResult<()> {
let NdRule::Pipeline { cmds, pipe_err } = pipeline.class else {
unreachable!()
};
@@ -96,15 +113,45 @@ impl<'t> Dispatcher<'t> {
}
Ok(())
}
pub fn exec_cmd(&mut self, cmd: Node<'t>) -> ShResult<'t,()> {
pub fn exec_builtin(&mut self, mut cmd: Node<'t>) -> ShResult<()> {
let NdRule::Command { ref mut assignments, argv } = &mut cmd.class else {
unreachable!()
};
let env_vars_to_unset = self.set_assignments(mem::take(assignments), AssignBehavior::Export);
let cmd_raw = cmd.get_command().unwrap();
flog!(TRACE, "doing builtin");
let result = match cmd_raw.span.as_str() {
"echo" => echo(cmd, &mut self.io_stack),
_ => unimplemented!("Have not yet added support for builtin '{}'", cmd_raw.span.as_str())
};
for var in env_vars_to_unset {
env::set_var(&var, "");
}
Ok(result?)
}
pub fn exec_cmd(&mut self, cmd: Node<'t>) -> ShResult<()> {
let NdRule::Command { assignments, argv } = cmd.class else {
unreachable!()
};
let mut env_vars_to_unset = vec![];
if !assignments.is_empty() {
let assign_behavior = if argv.is_empty() {
AssignBehavior::Set
} else {
AssignBehavior::Export
};
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(())
}
let exec_args = ExecArgs::new(prepare_argv(argv));
let exec_args = ExecArgs::new(argv);
let io_frame = self.io_stack.pop_frame();
run_fork(
io_frame,
@@ -113,8 +160,51 @@ impl<'t> Dispatcher<'t> {
def_parent_action
)?;
for var in env_vars_to_unset {
std::env::set_var(&var, "");
}
Ok(())
}
pub fn set_assignments(&self, assigns: Vec<Node<'t>>, behavior: AssignBehavior) -> Vec<String> {
let mut new_env_vars = vec![];
match behavior {
AssignBehavior::Export => {
for assign in assigns {
let NdRule::Assignment { kind, var, val } = assign.class else {
unreachable!()
};
let var = var.span.as_str();
let val = val.span.as_str();
match kind {
AssignKind::Eq => std::env::set_var(var, val),
AssignKind::PlusEq => todo!(),
AssignKind::MinusEq => todo!(),
AssignKind::MultEq => todo!(),
AssignKind::DivEq => todo!(),
}
new_env_vars.push(var.to_string());
}
}
AssignBehavior::Set => {
for assign in assigns {
let NdRule::Assignment { kind, var, val } = assign.class else {
unreachable!()
};
let var = var.span.as_str();
let val = val.span.as_str();
match kind {
AssignKind::Eq => write_vars(|v| v.new_var(var, val)),
AssignKind::PlusEq => todo!(),
AssignKind::MinusEq => todo!(),
AssignKind::MultEq => todo!(),
AssignKind::DivEq => todo!(),
}
}
}
}
new_env_vars
}
}
pub fn prepare_argv(argv: Vec<Tk>) -> Vec<String> {
@@ -134,15 +224,20 @@ pub fn run_fork<'t,C,P>(
exec_args: ExecArgs,
child_action: C,
parent_action: P,
) -> ShResult<'t,()>
) -> ShResult<()>
where
C: Fn(IoFrame,ExecArgs),
P: Fn(IoFrame,Pid) -> ShResult<'t,()>
C: Fn(IoFrame,ExecArgs) -> Errno,
P: Fn(IoFrame,Pid) -> ShResult<()>
{
match unsafe { fork()? } {
ForkResult::Child => {
child_action(io_frame,exec_args);
exit(1);
let cmd = &exec_args.cmd.to_str().unwrap().to_string();
let errno = child_action(io_frame,exec_args);
match errno {
Errno::ENOENT => eprintln!("Command not found: {}", cmd),
_ => eprintln!("{errno}")
}
exit(errno as i32);
}
ForkResult::Parent { child } => {
parent_action(io_frame,child)
@@ -151,18 +246,21 @@ where
}
/// The default behavior for the child process after forking
pub fn def_child_action<'t>(mut io_frame: IoFrame, exec_args: ExecArgs) {
io_frame.redirect().unwrap();
execvpe(&exec_args.cmd, &exec_args.argv, &exec_args.envp).unwrap();
pub fn def_child_action<'t>(mut io_frame: IoFrame, exec_args: ExecArgs) -> Errno {
if let Err(e) = io_frame.redirect() {
eprintln!("{e}");
}
let Err(e) = execvpe(&exec_args.cmd, &exec_args.argv, &exec_args.envp);
e
}
/// The default behavior for the parent process after forking
pub fn def_parent_action<'t>(io_frame: IoFrame, child: Pid) -> ShResult<'t,()> {
let status = waitpid(child, Some(WaitPidFlag::WSTOPPED))?;
pub fn def_parent_action<'t>(io_frame: IoFrame, child: Pid) -> ShResult<()> {
let status = waitpid(child, Some(WtFlag::WSTOPPED))?;
match status {
WaitStatus::Exited(_, status) => state::set_status(status),
WtStat::Exited(_, status) => state::set_status(status),
_ => unimplemented!()
}
}
Ok(())
}

View File

@@ -2,7 +2,7 @@ use std::{fmt::Display, ops::{Bound, Deref, Range, RangeBounds}};
use bitflags::bitflags;
use crate::{libsh::error::{ShErr, ShErrKind}, prelude::*};
use crate::{builtin::BUILTINS, libsh::error::{ShErr, ShErrKind}, prelude::*};
pub const KEYWORDS: [&'static str;14] = [
"if",
@@ -48,6 +48,12 @@ impl<'s> Span<'s> {
pub fn as_str(&self) -> &str {
&self.source[self.start..self.end]
}
pub fn get_source(&'s self) -> &'s str {
self.source
}
pub fn range(&self) -> Range<usize> {
self.range.clone()
}
}
/// Allows simple access to the underlying range wrapped by the span
@@ -130,6 +136,9 @@ impl<'s> Tk<'s> {
pub fn is_err(&self) -> bool {
self.err_span.is_some()
}
pub fn source(&self) -> &'s str {
self.span.source
}
}
impl<'s> Display for Tk<'s> {
@@ -150,6 +159,8 @@ bitflags! {
const OPENER = 0b0000000000000010;
const IS_CMD = 0b0000000000000100;
const IS_OP = 0b0000000000001000;
const ASSIGN = 0b0000000000010000;
const BUILTIN = 0b0000000000100000;
}
}
@@ -181,6 +192,7 @@ bitflags! {
impl<'t> LexStream<'t> {
pub fn new(source: &'t str, flags: LexFlags) -> Self {
flog!(TRACE, "new lex stream");
let flags = flags | LexFlags::FRESH | LexFlags::NEXT_IS_CMD;
Self { source, cursor: 0, in_quote: false, flags }
}
@@ -346,8 +358,15 @@ impl<'t> LexStream<'t> {
if self.flags.contains(LexFlags::NEXT_IS_CMD) {
if KEYWORDS.contains(&new_tk.span.as_str()) {
new_tk.flags |= TkFlags::KEYWORD;
} else if is_assignment(&new_tk.span.as_str()) {
new_tk.flags |= TkFlags::ASSIGN;
} else {
new_tk.flags |= TkFlags::IS_CMD;
flog!(TRACE, new_tk.span.as_str());
if BUILTINS.contains(&new_tk.span.as_str()) {
new_tk.flags |= TkFlags::BUILTIN;
}
flog!(TRACE, new_tk.flags);
self.next_is_not_cmd();
}
}
@@ -480,6 +499,19 @@ pub fn get_char(src: &str, idx: usize) -> Option<char> {
src.get(idx..)?.chars().next()
}
pub fn is_assignment(text: &str) -> bool {
let mut chars = text.chars();
while let Some(ch) = chars.next() {
match ch {
'\\' => { chars.next(); }
'=' => return true,
_ => continue
}
}
false
}
/// Is '|', '&', '>', or '<'
pub fn is_op(ch: char) -> bool {
matches!(ch, '|' | '&' | '>' | '<')

View File

@@ -1,7 +1,7 @@
use std::str::FromStr;
use bitflags::bitflags;
use lex::{Span, Tk, TkFlags, TkRule};
use lex::{is_hard_sep, Span, Tk, TkFlags, TkRule};
use crate::{prelude::*, libsh::error::{ShErr, ShErrKind, ShResult}, procio::{IoFd, IoFile, IoInfo}};
@@ -17,6 +17,16 @@ pub struct Node<'t> {
pub tokens: Vec<Tk<'t>>,
}
impl<'t> Node<'t> {
pub fn get_command(&'t self) -> Option<&'t Tk<'t>> {
let NdRule::Command { assignments: _, argv } = &self.class else {
return None
};
let command = argv.iter().find(|tk| tk.flags.contains(TkFlags::IS_CMD))?;
Some(command)
}
}
bitflags! {
#[derive(Debug)]
pub struct NdFlags: u32 {
@@ -48,8 +58,8 @@ impl RedirBldr {
Default::default()
}
pub fn with_io_info(self, io_info: Box<dyn IoInfo>) -> Self {
let Self { io_info: _, class, tgt_fd } = self;
Self { io_info: Some(io_info), class, tgt_fd }
let Self { io_info: _, class, tgt_fd } = self;
Self { io_info: Some(io_info), class, tgt_fd }
}
pub fn with_class(self, class: RedirType) -> Self {
let Self { io_info, class: _, tgt_fd } = self;
@@ -127,8 +137,8 @@ impl FromStr for RedirBldr {
let tgt_fd = tgt_fd.parse::<i32>().unwrap_or_else(|_| {
match redir.class.unwrap() {
RedirType::Input |
RedirType::HereDoc |
RedirType::HereString => 0,
RedirType::HereDoc |
RedirType::HereString => 0,
_ => 1
}
});
@@ -199,22 +209,12 @@ pub enum NdRule<'t> {
LoopNode { kind: LoopKind, cond_block: CondNode<'t> },
ForNode { vars: Vec<Tk<'t>>, arr: Vec<Tk<'t>>, body: Vec<Node<'t>> },
CaseNode { pattern: Tk<'t>, case_blocks: Vec<CaseNode<'t>> },
Command { assignments: Vec<Tk<'t>>, argv: Vec<Tk<'t>> },
Command { assignments: Vec<Node<'t>>, argv: Vec<Tk<'t>> },
Pipeline { cmds: Vec<Node<'t>>, pipe_err: bool },
CmdList { elements: Vec<ConjunctNode<'t>> },
Assignment { kind: AssignKind, var: Tk<'t>, val: Tk<'t> },
}
/// If the given expression returns Some(), then return it
#[derive(Debug)]
pub enum ParseResult<'t> {
NoMatch,
Error(ShErr<'t>),
Match(Node<'t>)
}
use ParseResult::*; // muh ergonomics
#[derive(Debug)]
pub struct ParseStream<'t> {
pub tokens: Vec<Tk<'t>>,
@@ -254,11 +254,11 @@ impl<'t> ParseStream<'t> {
assert!(num_consumed <= self.tokens.len());
self.tokens = self.tokens[num_consumed..].to_vec();
}
fn parse_cmd_list(&mut self) -> ParseResult<'t> {
fn parse_cmd_list(&mut self) -> ShResult<Option<Node<'t>>> {
let mut elements = vec![];
let mut node_tks = vec![];
while let Match(block) = self.parse_block(true) {
while let Some(block) = self.parse_block(true)? {
node_tks.append(&mut block.tokens.clone());
let conjunct_op = match self.next_tk_class() {
TkRule::And => ConjunctOp::And,
@@ -275,35 +275,37 @@ impl<'t> ParseStream<'t> {
break
}
}
Match(Node {
class: NdRule::CmdList { elements },
flags: NdFlags::empty(),
redirs: vec![],
tokens: node_tks
})
if elements.is_empty() {
Ok(None)
} else {
Ok(Some(Node {
class: NdRule::CmdList { elements },
flags: NdFlags::empty(),
redirs: vec![],
tokens: node_tks
}))
}
}
/// This tries to match on different stuff that can appear in a command position
/// Matches shell commands like if-then-fi, pipelines, etc.
/// Ordered from specialized to general, with more generally matchable stuff appearing at the bottom
/// The check_pipelines parameter is used to prevent infinite recursion in parse_pipeline
fn parse_block(&mut self, check_pipelines: bool) -> ParseResult<'t> {
fn parse_block(&mut self, check_pipelines: bool) -> ShResult<Option<Node<'t>>> {
if check_pipelines {
match self.parse_pipeline() {
NoMatch => { /* Continue */ }
result => return result
if let Some(node) = self.parse_pipeline()? {
return Ok(Some(node))
}
} else {
match self.parse_cmd() {
NoMatch => { /* Continue */ }
result => return result
if let Some(node) = self.parse_cmd()? {
return Ok(Some(node))
}
}
NoMatch
Ok(None)
}
fn parse_pipeline(&mut self) -> ParseResult<'t> {
fn parse_pipeline(&mut self) -> ShResult<Option<Node<'t>>> {
let mut cmds = vec![];
let mut node_tks = vec![];
while let Match(cmd) = self.parse_block(false) {
while let Some(cmd) = self.parse_block(false)? {
let is_punctuated = node_is_punctuated(&cmd.tokens);
node_tks.append(&mut cmd.tokens.clone());
cmds.push(cmd);
@@ -318,18 +320,18 @@ impl<'t> ParseStream<'t> {
}
}
if cmds.is_empty() {
NoMatch
Ok(None)
} else {
Match(Node {
Ok(Some(Node {
// TODO: implement pipe_err support
class: NdRule::Pipeline { cmds, pipe_err: false },
flags: NdFlags::empty(),
redirs: vec![],
tokens: node_tks
})
}))
}
}
fn parse_cmd(&mut self) -> ParseResult<'t> {
fn parse_cmd(&mut self) -> ShResult<Option<Node<'t>>> {
let tk_slice = self.tokens.as_slice();
let mut tk_iter = tk_slice.iter();
let mut node_tks = vec![];
@@ -337,25 +339,32 @@ impl<'t> ParseStream<'t> {
let mut argv = vec![];
let mut assignments = vec![];
let Some(cmd_tk) = tk_iter.next() else {
return NoMatch
};
while let Some(prefix_tk) = tk_iter.next() {
if prefix_tk.flags.contains(TkFlags::IS_CMD) {
node_tks.push(prefix_tk.clone());
argv.push(prefix_tk.clone());
break
} else if prefix_tk.flags.contains(TkFlags::ASSIGN) {
let Some(assign) = self.parse_assignment(&prefix_tk) else {
break
};
node_tks.push(prefix_tk.clone());
assignments.push(assign)
}
}
if !cmd_tk.flags.contains(TkFlags::IS_CMD) {
return NoMatch
} else {
node_tks.push(cmd_tk.clone());
argv.push(cmd_tk.clone());
if argv.is_empty() && assignments.is_empty() {
return Ok(None)
}
while let Some(tk) = tk_iter.next() {
match tk.class {
TkRule::EOI |
TkRule::Pipe |
TkRule::And |
TkRule::Or => {
break
}
TkRule::Pipe |
TkRule::And |
TkRule::Or => {
break
}
TkRule::Sep => {
node_tks.push(tk.clone());
break
@@ -368,9 +377,11 @@ impl<'t> ParseStream<'t> {
node_tks.push(tk.clone());
let redir_bldr = tk.span.as_str().parse::<RedirBldr>().unwrap();
if redir_bldr.io_info.is_none() {
let Some(path_tk) = tk_iter.next() else {
let path_tk = tk_iter.next();
if path_tk.is_none_or(|tk| tk.class == TkRule::EOI) {
self.flags |= ParseFlags::ERROR;
return Error(
return Err(
ShErr::full(
ShErrKind::ParseErr,
"Expected a filename after this redirection",
@@ -378,6 +389,8 @@ impl<'t> ParseStream<'t> {
)
)
};
let path_tk = path_tk.unwrap();
node_tks.push(path_tk.clone());
let Ok(file) = (match redir_bldr.class.unwrap() {
@@ -403,7 +416,7 @@ impl<'t> ParseStream<'t> {
_ => unreachable!()
}) else {
self.flags |= ParseFlags::ERROR;
return Error(
return Err(
ShErr::full(
ShErrKind::InternalErr,
"Error opening file for redirection",
@@ -423,17 +436,118 @@ impl<'t> ParseStream<'t> {
}
self.commit(node_tks.len());
Match(Node {
Ok(Some(Node {
class: NdRule::Command { assignments, argv },
tokens: node_tks,
flags: NdFlags::empty(),
redirs,
})
}))
}
fn parse_assignment(&self, token: &Tk<'t>) -> Option<Node<'t>> {
let mut chars = token.span.as_str().chars();
let mut var_name = String::new();
let mut name_range = token.span.start..token.span.start;
let mut var_val = String::new();
let mut val_range = token.span.end..token.span.end;
let mut assign_kind = None;
let mut pos = token.span.start;
while let Some(ch) = chars.next() {
if !assign_kind.is_none() {
match ch {
'\\' => {
pos += ch.len_utf8();
var_val.push(ch);
if let Some(esc_ch) = chars.next() {
pos += esc_ch.len_utf8();
var_val.push(esc_ch);
}
}
_ => {
pos += ch.len_utf8();
var_val.push(ch);
}
}
} else {
match ch {
'=' => {
name_range.end = pos;
pos += ch.len_utf8();
val_range.start = pos;
assign_kind = Some(AssignKind::Eq);
}
'-' => {
name_range.end = pos;
pos += ch.len_utf8();
let Some('=') = chars.next() else {
return None
};
pos += '='.len_utf8();
val_range.start = pos;
assign_kind = Some(AssignKind::MinusEq);
}
'+' => {
name_range.end = pos;
pos += ch.len_utf8();
let Some('=') = chars.next() else {
return None
};
pos += '='.len_utf8();
val_range.start = pos;
assign_kind = Some(AssignKind::PlusEq);
}
'/' => {
name_range.end = pos;
pos += ch.len_utf8();
let Some('=') = chars.next() else {
return None
};
pos += '='.len_utf8();
val_range.start = pos;
assign_kind = Some(AssignKind::DivEq);
}
'*' => {
name_range.end = pos;
pos += ch.len_utf8();
let Some('=') = chars.next() else {
return None
};
pos += '='.len_utf8();
val_range.start = pos;
assign_kind = Some(AssignKind::MultEq);
}
'\\' => {
pos += ch.len_utf8();
var_name.push(ch);
if let Some(esc_ch) = chars.next() {
pos += esc_ch.len_utf8();
var_name.push(esc_ch);
}
}
_ => {
pos += ch.len_utf8();
var_name.push(ch)
}
}
}
}
if assign_kind.is_none() || var_name.is_empty() {
None
} else {
let var = Tk::new(TkRule::Str, Span::new(name_range, token.source()));
let val = Tk::new(TkRule::Str, Span::new(val_range, token.source()));
Some(Node {
class: NdRule::Assignment { kind: assign_kind.unwrap(), var, val },
tokens: vec![token.clone()],
flags: NdFlags::empty(),
redirs: vec![]
})
}
}
}
impl<'t> Iterator for ParseStream<'t> {
type Item = ParseResult<'t>;
type Item = ShResult<Node<'t>>;
fn next(&mut self) -> Option<Self::Item> {
// Empty token vector or only SOI/EOI tokens, nothing to do
if self.tokens.is_empty() || self.tokens.len() == 2 {
@@ -451,9 +565,9 @@ impl<'t> Iterator for ParseStream<'t> {
}
}
match self.parse_cmd_list() {
NoMatch => None,
Error(err) => Some(Error(err)),
Match(cmdlist) => Some(Match(cmdlist))
Ok(Some(node)) => return Some(Ok(node)),
Ok(None) => return None,
Err(e) => return Some(Err(e))
}
}
}

View File

@@ -15,21 +15,32 @@ pub use std::fs::{ self, File, OpenOptions };
pub use std::path::{ Path, PathBuf };
pub use std::ffi::{ CStr, CString };
pub use std::process::exit;
pub use std::time::Instant;
pub use std::mem;
pub use std::env;
pub use std::fmt;
// Unix-specific IO abstractions
pub use std::os::unix::io::{ AsRawFd, FromRawFd, IntoRawFd, OwnedFd, RawFd, };
pub use std::os::unix::io::{ AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd, RawFd, };
// Nix crate for POSIX APIs
pub use nix::{
errno::Errno,
fcntl::{ open, OFlag },
sys::{
signal::{ self, kill, SigHandler, Signal },
signal::{ self, kill, killpg, pthread_sigmask, SigSet, SigmaskHow, SigHandler, Signal },
stat::Mode,
wait::{ waitpid, WaitStatus },
wait::{ waitpid, WaitPidFlag as WtFlag, WaitStatus as WtStat },
},
libc::{ STDIN_FILENO, STDERR_FILENO, STDOUT_FILENO },
unistd::{ dup, read, write, close, dup2, execvpe, fork, pipe, Pid, ForkResult },
unistd::{
dup, read, isatty, write, close, setpgid, dup2, getpgrp,
execvpe, tcgetpgrp, tcsetpgrp, fork, pipe, Pid, ForkResult
},
};
pub use bitflags::bitflags;
pub use crate::flog;
pub use crate::libsh::flog::FernLogLevel::*;
// Additional utilities, if needed, can be added here

View File

@@ -1,6 +1,6 @@
use std::{fmt::Debug, ops::{Deref, DerefMut}};
use crate::{libsh::error::ShResult, parse::Redir, prelude::*};
use crate::{libsh::error::ShResult, parse::{Redir, RedirType}, prelude::*};
// Credit to fish-shell for many of the implementation ideas present in this module
// https://fishshell.com/
@@ -46,6 +46,8 @@ impl Debug for Box<dyn IoInfo> {
}
}
/// A redirection to a raw fildesc
/// e.g. `2>&1`
#[derive(Debug)]
pub struct IoFd {
tgt_fd: RawFd,
@@ -81,6 +83,8 @@ impl IoInfo for IoFd {
}
}
/// A redirection to a file
/// e.g. `> file.txt`
#[derive(Debug)]
pub struct IoFile {
tgt_fd: RawFd,
@@ -112,6 +116,8 @@ impl IoInfo for IoFile {
}
}
/// A redirection to a pipe
/// e.g. `echo foo | sed s/foo/bar/`
#[derive(Debug)]
pub struct IoPipe {
tgt_fd: RawFd,
@@ -150,9 +156,32 @@ impl IoInfo for IoPipe {
}
}
pub struct FdWriter {
tgt: OwnedFd
}
impl FdWriter {
pub fn new(fd: i32) -> Self {
let tgt = unsafe { OwnedFd::from_raw_fd(fd) };
Self { tgt }
}
}
impl Write for FdWriter {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
Ok(write(&self.tgt, buf)?)
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
/// A struct wrapping three fildescs representing `stdin`, `stdout`, and `stderr` respectively
#[derive(Debug)]
pub struct IoGroup(OwnedFd,OwnedFd,OwnedFd);
/// A single stack frame used with the IoStack
/// Each stack frame represents the redirections of a single command
#[derive(Default,Debug)]
pub struct IoFrame {
redirs: Vec<Redir>,
@@ -163,6 +192,42 @@ impl<'e> IoFrame {
pub fn new() -> Self {
Default::default()
}
pub fn from_redirs(redirs: Vec<Redir>) -> Self {
Self { redirs, saved_io: None }
}
/// This method returns a 2-tuple of `IoFrames`.
/// This is to be used in the case of shell structures such as `if-then` and `while-do`.
/// # Params
/// * redirs: a vector of redirections
///
/// # Returns
/// * An `IoFrame` containing all of the redirections which target stdin
/// * An `IoFrame` containing all of the redirections which target stdout/stderr
///
/// # Purpose
/// In the case of something like `if cat; then echo foo; fi < input.txt > output.txt`
/// This will cleanly separate the redirections such that `cat` can receive the input from input.txt
/// and `echo foo` can redirect it's output to output.txt
pub fn cond_and_body(redirs: Vec<Redir>) -> (Self, Self) {
let mut output_redirs = vec![];
let mut input_redirs = vec![];
for redir in redirs {
match redir.class {
RedirType::Input => input_redirs.push(redir),
RedirType::Pipe => {
match redir.io_info.tgt_fd() {
STDIN_FILENO => input_redirs.push(redir),
STDOUT_FILENO |
STDERR_FILENO => output_redirs.push(redir),
_ => unreachable!()
}
}
_ => output_redirs.push(redir)
}
}
(Self::from_redirs(input_redirs),Self::from_redirs(output_redirs))
}
pub fn save(&'e mut self) {
unsafe {
let saved_in = OwnedFd::from_raw_fd(dup(STDIN_FILENO).unwrap());
@@ -171,7 +236,7 @@ impl<'e> IoFrame {
self.saved_io = Some(IoGroup(saved_in,saved_out,saved_err));
}
}
pub fn redirect(&'e mut self) -> ShResult<'e,()> {
pub fn redirect(&mut self) -> ShResult<()> {
self.save();
for redir in &mut self.redirs {
let io_info = &mut redir.io_info;
@@ -182,12 +247,11 @@ impl<'e> IoFrame {
}
Ok(())
}
pub fn restore(&'e mut self) -> ShResult<'e,()> {
pub fn restore(&mut self) -> ShResult<()> {
while let Some(mut redir) = self.pop() {
redir.io_info.close().ok();
redir.io_info.close()?;
}
if let Some(saved) = self.saved_io.take() {
dbg!(&saved);
dup2(saved.0.as_raw_fd(), STDIN_FILENO)?;
dup2(saved.1.as_raw_fd(), STDOUT_FILENO)?;
dup2(saved.2.as_raw_fd(), STDERR_FILENO)?;
@@ -209,6 +273,12 @@ impl DerefMut for IoFrame {
}
}
/// A stack that maintains the current state of I/O for commands
///
/// This struct maintains the current state of I/O for the `Dispatcher` struct
/// Each executed command requires an `IoFrame` in order to perform redirections.
/// As nodes are walked through by the `Dispatcher`, it pushes new frames in certain contexts, and pops frames in others.
/// Each command calls pop_frame() in order to get the current IoFrame in order to perform redirection
#[derive(Default)]
pub struct IoStack {
stack: Vec<IoFrame>,
@@ -229,13 +299,31 @@ impl<'e> IoStack {
pub fn push_to_frame(&mut self, redir: Redir) {
self.curr_frame_mut().push(redir)
}
/// 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
/// There will always be at least one frame in the `IoStack`.
pub fn pop_frame(&mut self) -> IoFrame {
if self.stack.len() > 1 {
self.stack.pop().unwrap()
self.pop().unwrap()
} else {
std::mem::take(self.curr_frame_mut())
}
}
/// Push a new stack frame.
pub fn push_frame(&mut self, frame: IoFrame) {
self.push(frame)
}
/// Flatten the `IoStack`
/// All of the current stack frames will be flattened into a single one
/// Not sure what use this will serve, but my gut said this was worthy of writing
pub fn flatten(&mut self) {
let mut flat_frame = IoFrame::new();
while let Some(mut frame) = self.pop() {
flat_frame.append(&mut frame)
}
self.push(flat_frame);
}
}
impl Deref for IoStack {
@@ -250,3 +338,7 @@ impl DerefMut for IoStack {
&mut self.stack
}
}
pub fn borrow_fd<'f>(fd: i32) -> BorrowedFd<'f> {
unsafe { BorrowedFd::borrow_raw(fd) }
}

View File

@@ -66,7 +66,7 @@ impl<'e> FernHist {
pub fn new() -> Self {
Self { file_path: None, entries: HistEntries::new(), max_len: 1000, flags: HistFlags::empty() }
}
pub fn from_path(file_path: PathBuf) -> ShResult<'e,Self> {
pub fn from_path(file_path: PathBuf) -> ShResult<Self> {
let mut new_hist = FernHist::new();
new_hist.file_path = Some(file_path);
new_hist.load_hist()?;
@@ -76,14 +76,14 @@ impl<'e> FernHist {
let id = self.len() + 1;
HistEntry::new(body.to_string(), id)
}
pub fn init_hist_file(&mut self) -> ShResult<'e,()> {
pub fn init_hist_file(&mut self) -> ShResult<()> {
let Some(path) = self.file_path.clone() else {
return Ok(());
};
self.save(&path)?;
Ok(())
}
pub fn load_hist(&mut self) -> ShResult<'e,()> {
pub fn load_hist(&mut self) -> ShResult<()> {
let Some(file_path) = self.file_path.clone() else {
return Err(
ShErr::simple(

View File

@@ -9,16 +9,16 @@ use rustyline::{error::ReadlineError, history::{FileHistory, History}, Config, E
use crate::{libsh::{error::ShResult, term::{Style, Styled}}, prelude::*};
fn init_rl<'s>() -> ShResult<'s,Editor<FernReadline,FernHist>> {
let hist = FernHist::default();
fn init_rl<'s>() -> ShResult<Editor<FernReadline,FileHistory>> {
let rl = FernReadline::new();
let config = Config::default();
let mut editor = Editor::with_history(config,hist)?;
let mut editor = Editor::new()?;
editor.set_helper(Some(rl));
editor.load_history(&Path::new("/home/pagedmov/.fernhist"))?;
Ok(editor)
}
pub fn read_line<'s>() -> ShResult<'s,String> {
pub fn read_line<'s>() -> ShResult<String> {
assert!(isatty(STDIN_FILENO).unwrap());
let mut editor = init_rl()?;
let prompt = "$ ".styled(Style::Green | Style::Bold);
match editor.readline(&prompt) {

View File

@@ -47,7 +47,11 @@ impl Hint for FernHint {
impl Hinter for FernReadline {
type Hint = FernHint;
fn hint(&self, line: &str, pos: usize, ctx: &rustyline::Context<'_>) -> Option<Self::Hint> {
let ent = ctx.history().search(line, pos, rustyline::history::SearchDirection::Reverse).ok()??;
let ent = ctx.history().search(
line,
ctx.history().len() - 1,
rustyline::history::SearchDirection::Reverse
).ok()??;
let entry_raw = ent.entry.get(pos..)?.to_string();
Some(FernHint::new(entry_raw))
}

View File

@@ -6,8 +6,6 @@ pub static JOB_TABLE: LazyLock<RwLock<JobTab>> = LazyLock::new(|| RwLock::new(Jo
pub static VAR_TABLE: LazyLock<RwLock<VarTab>> = LazyLock::new(|| RwLock::new(VarTab::new()));
pub static ENV_TABLE: LazyLock<RwLock<EnvTab>> = LazyLock::new(|| RwLock::new(EnvTab::new()));
pub struct JobTab {
}
@@ -51,7 +49,11 @@ impl VarTab {
&mut self.params
}
pub fn get_var(&self, var: &str) -> String {
self.vars.get(var).map(|s| s.to_string()).unwrap_or_default()
if let Some(var) = self.vars.get(var).map(|s| s.to_string()) {
var
} else {
std::env::var(var).unwrap_or_default()
}
}
pub fn new_var(&mut self, var: &str, val: &str) {
self.vars.insert(var.to_string(), val.to_string());
@@ -64,16 +66,6 @@ impl VarTab {
}
}
pub struct EnvTab {
}
impl EnvTab {
pub fn new() -> Self {
Self {}
}
}
/// Read from the job table
pub fn read_jobs<T, F: FnOnce(RwLockReadGuard<JobTab>) -> T>(f: F) -> T {
let lock = JOB_TABLE.read().unwrap();
@@ -98,18 +90,6 @@ pub fn write_vars<T, F: FnOnce(&mut RwLockWriteGuard<VarTab>) -> T>(f: F) -> T {
f(lock)
}
/// Read from the environment table
pub fn read_env<T, F: FnOnce(RwLockReadGuard<EnvTab>) -> T>(f: F) -> T {
let lock = ENV_TABLE.read().unwrap();
f(lock)
}
/// Write to the environment table
pub fn write_env<T, F: FnOnce(&mut RwLockWriteGuard<EnvTab>) -> T>(f: F) -> T {
let lock = &mut ENV_TABLE.write().unwrap();
f(lock)
}
pub fn get_status() -> i32 {
read_vars(|v| v.get_param('?')).parse::<i32>().unwrap()
}

View File

@@ -1,3 +1,4 @@
use expand::unescape_str;
use parse::lex::{Tk, TkFlags, TkRule};
use state::write_vars;
use super::super::*;
@@ -10,10 +11,18 @@ fn simple_expansion() {
let mut tokens: Vec<Tk> = LexStream::new(varsub, LexFlags::empty())
.filter(|tk| !matches!(tk.class, TkRule::EOI | TkRule::SOI))
.collect();
let var_tk = tokens.pop().unwrap();
let var_tk = tokens.pop().unwrap();
let var_span = var_tk.span.clone();
let exp_tk = var_tk.expand(var_span, TkFlags::empty());
write_vars(|v| v.vars_mut().clear());
insta::assert_debug_snapshot!(exp_tk.get_words())
let var_span = var_tk.span.clone();
let exp_tk = var_tk.expand(var_span, TkFlags::empty());
write_vars(|v| v.vars_mut().clear());
insta::assert_debug_snapshot!(exp_tk.get_words())
}
#[test]
fn unescape_string() {
let string = "echo $foo \\$bar";
let unescaped = unescape_str(string);
insta::assert_snapshot!(unescaped)
}

View File

@@ -0,0 +1,5 @@
---
source: src/tests/expand.rs
expression: unescaped
---
echo ﷐foo $bar

View File

@@ -3,7 +3,7 @@ source: src/tests/parser.rs
expression: nodes
---
[
Match(
Ok(
Node {
class: CmdList {
elements: [

View File

@@ -3,7 +3,7 @@ source: src/tests/parser.rs
expression: nodes
---
[
Match(
Ok(
Node {
class: CmdList {
elements: [
@@ -21,7 +21,7 @@ expression: nodes
err: Null,
span: Span {
range: 0..4,
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(
IS_CMD,
@@ -33,7 +33,7 @@ expression: nodes
err: Null,
span: Span {
range: 5..8,
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(
0x0,
@@ -52,7 +52,7 @@ expression: nodes
err: Null,
span: Span {
range: 0..4,
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(
IS_CMD,
@@ -64,7 +64,7 @@ expression: nodes
err: Null,
span: Span {
range: 5..8,
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(
0x0,
@@ -82,7 +82,7 @@ expression: nodes
err: Null,
span: Span {
range: 11..14,
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(
IS_CMD,
@@ -93,8 +93,8 @@ expression: nodes
err_span: None,
err: Null,
span: Span {
range: 15..24,
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
range: 15..25,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
0x0,
@@ -113,7 +113,7 @@ expression: nodes
err: Null,
span: Span {
range: 11..14,
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(
IS_CMD,
@@ -124,8 +124,8 @@ expression: nodes
err_span: None,
err: Null,
span: Span {
range: 15..24,
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
range: 15..25,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
0x0,
@@ -147,7 +147,7 @@ expression: nodes
err: Null,
span: Span {
range: 0..4,
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(
IS_CMD,
@@ -159,7 +159,7 @@ expression: nodes
err: Null,
span: Span {
range: 5..8,
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(
0x0,
@@ -171,7 +171,7 @@ expression: nodes
err: Null,
span: Span {
range: 9..10,
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(
0x0,
@@ -183,7 +183,7 @@ expression: nodes
err: Null,
span: Span {
range: 11..14,
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(
IS_CMD,
@@ -194,8 +194,8 @@ expression: nodes
err_span: None,
err: Null,
span: Span {
range: 15..24,
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
range: 15..25,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
0x0,
@@ -218,8 +218,8 @@ expression: nodes
err_span: None,
err: Null,
span: Span {
range: 28..32,
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
range: 29..33,
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,
@@ -230,8 +230,8 @@ expression: nodes
err_span: None,
err: Null,
span: Span {
range: 33..36,
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
range: 34..37,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
0x0,
@@ -249,8 +249,8 @@ expression: nodes
err_span: None,
err: Null,
span: Span {
range: 28..32,
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
range: 29..33,
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,
@@ -261,8 +261,8 @@ expression: nodes
err_span: None,
err: Null,
span: Span {
range: 33..36,
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
range: 34..37,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
0x0,
@@ -279,8 +279,8 @@ expression: nodes
err_span: None,
err: Null,
span: Span {
range: 39..42,
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
range: 40..43,
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,
@@ -291,8 +291,8 @@ expression: nodes
err_span: None,
err: Null,
span: Span {
range: 43..52,
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
range: 44..54,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
0x0,
@@ -310,8 +310,8 @@ expression: nodes
err_span: None,
err: Null,
span: Span {
range: 39..42,
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
range: 40..43,
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,
@@ -322,8 +322,8 @@ expression: nodes
err_span: None,
err: Null,
span: Span {
range: 43..52,
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
range: 44..54,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
0x0,
@@ -344,8 +344,8 @@ expression: nodes
err_span: None,
err: Null,
span: Span {
range: 28..32,
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
range: 29..33,
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,
@@ -356,8 +356,8 @@ expression: nodes
err_span: None,
err: Null,
span: Span {
range: 33..36,
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
range: 34..37,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
0x0,
@@ -368,8 +368,8 @@ expression: nodes
err_span: None,
err: Null,
span: Span {
range: 37..38,
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
range: 38..39,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
0x0,
@@ -380,8 +380,8 @@ expression: nodes
err_span: None,
err: Null,
span: Span {
range: 39..42,
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
range: 40..43,
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,
@@ -392,8 +392,8 @@ expression: nodes
err_span: None,
err: Null,
span: Span {
range: 43..52,
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
range: 44..54,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
0x0,
@@ -416,8 +416,8 @@ expression: nodes
err_span: None,
err: Null,
span: Span {
range: 56..60,
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
range: 58..62,
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,
@@ -428,8 +428,8 @@ expression: nodes
err_span: None,
err: Null,
span: Span {
range: 61..64,
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
range: 63..66,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
0x0,
@@ -440,8 +440,8 @@ expression: nodes
err_span: None,
err: Null,
span: Span {
range: 65..68,
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
range: 67..70,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
0x0,
@@ -459,8 +459,8 @@ expression: nodes
err_span: None,
err: Null,
span: Span {
range: 56..60,
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
range: 58..62,
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,
@@ -471,8 +471,8 @@ expression: nodes
err_span: None,
err: Null,
span: Span {
range: 61..64,
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
range: 63..66,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
0x0,
@@ -483,8 +483,8 @@ expression: nodes
err_span: None,
err: Null,
span: Span {
range: 65..68,
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
range: 67..70,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
0x0,
@@ -501,8 +501,8 @@ expression: nodes
err_span: None,
err: Null,
span: Span {
range: 71..74,
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
range: 73..76,
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,
@@ -513,8 +513,8 @@ expression: nodes
err_span: None,
err: Null,
span: Span {
range: 75..80,
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
range: 77..82,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
0x0,
@@ -525,8 +525,8 @@ expression: nodes
err_span: None,
err: Null,
span: Span {
range: 81..88,
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
range: 83..90,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
0x0,
@@ -537,8 +537,8 @@ expression: nodes
err_span: None,
err: Null,
span: Span {
range: 89..93,
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
range: 91..95,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
0x0,
@@ -556,8 +556,8 @@ expression: nodes
err_span: None,
err: Null,
span: Span {
range: 71..74,
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
range: 73..76,
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,
@@ -568,8 +568,8 @@ expression: nodes
err_span: None,
err: Null,
span: Span {
range: 75..80,
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
range: 77..82,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
0x0,
@@ -580,8 +580,8 @@ expression: nodes
err_span: None,
err: Null,
span: Span {
range: 81..88,
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
range: 83..90,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
0x0,
@@ -592,8 +592,8 @@ expression: nodes
err_span: None,
err: Null,
span: Span {
range: 89..93,
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
range: 91..95,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
0x0,
@@ -614,8 +614,8 @@ expression: nodes
err_span: None,
err: Null,
span: Span {
range: 56..60,
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
range: 58..62,
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,
@@ -626,8 +626,8 @@ expression: nodes
err_span: None,
err: Null,
span: Span {
range: 61..64,
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
range: 63..66,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
0x0,
@@ -638,8 +638,8 @@ expression: nodes
err_span: None,
err: Null,
span: Span {
range: 65..68,
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
range: 67..70,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
0x0,
@@ -650,8 +650,8 @@ expression: nodes
err_span: None,
err: Null,
span: Span {
range: 69..70,
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
range: 71..72,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
0x0,
@@ -662,8 +662,8 @@ expression: nodes
err_span: None,
err: Null,
span: Span {
range: 71..74,
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
range: 73..76,
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,
@@ -674,8 +674,8 @@ expression: nodes
err_span: None,
err: Null,
span: Span {
range: 75..80,
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
range: 77..82,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
0x0,
@@ -686,8 +686,8 @@ expression: nodes
err_span: None,
err: Null,
span: Span {
range: 81..88,
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
range: 83..90,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
0x0,
@@ -698,8 +698,8 @@ expression: nodes
err_span: None,
err: Null,
span: Span {
range: 89..93,
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
range: 91..95,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
0x0,
@@ -722,7 +722,7 @@ expression: nodes
err: Null,
span: Span {
range: 0..4,
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(
IS_CMD,
@@ -734,7 +734,7 @@ expression: nodes
err: Null,
span: Span {
range: 5..8,
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(
0x0,
@@ -746,7 +746,7 @@ expression: nodes
err: Null,
span: Span {
range: 9..10,
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(
0x0,
@@ -758,7 +758,7 @@ expression: nodes
err: Null,
span: Span {
range: 11..14,
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(
IS_CMD,
@@ -769,8 +769,8 @@ expression: nodes
err_span: None,
err: Null,
span: Span {
range: 15..24,
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
range: 15..25,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
0x0,
@@ -781,8 +781,8 @@ expression: nodes
err_span: None,
err: Null,
span: Span {
range: 25..27,
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
range: 26..28,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
0x0,
@@ -793,8 +793,8 @@ expression: nodes
err_span: None,
err: Null,
span: Span {
range: 28..32,
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
range: 29..33,
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,
@@ -805,8 +805,8 @@ expression: nodes
err_span: None,
err: Null,
span: Span {
range: 33..36,
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
range: 34..37,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
0x0,
@@ -817,8 +817,8 @@ expression: nodes
err_span: None,
err: Null,
span: Span {
range: 37..38,
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
range: 38..39,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
0x0,
@@ -829,8 +829,8 @@ expression: nodes
err_span: None,
err: Null,
span: Span {
range: 39..42,
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
range: 40..43,
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,
@@ -841,8 +841,8 @@ expression: nodes
err_span: None,
err: Null,
span: Span {
range: 43..52,
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
range: 44..54,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
0x0,
@@ -853,8 +853,8 @@ expression: nodes
err_span: None,
err: Null,
span: Span {
range: 53..55,
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
range: 55..57,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
0x0,
@@ -865,8 +865,8 @@ expression: nodes
err_span: None,
err: Null,
span: Span {
range: 56..60,
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
range: 58..62,
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,
@@ -877,8 +877,8 @@ expression: nodes
err_span: None,
err: Null,
span: Span {
range: 61..64,
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
range: 63..66,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
0x0,
@@ -889,8 +889,8 @@ expression: nodes
err_span: None,
err: Null,
span: Span {
range: 65..68,
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
range: 67..70,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
0x0,
@@ -901,8 +901,8 @@ expression: nodes
err_span: None,
err: Null,
span: Span {
range: 69..70,
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
range: 71..72,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
0x0,
@@ -913,8 +913,8 @@ expression: nodes
err_span: None,
err: Null,
span: Span {
range: 71..74,
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
range: 73..76,
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,
@@ -925,8 +925,8 @@ expression: nodes
err_span: None,
err: Null,
span: Span {
range: 75..80,
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
range: 77..82,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
0x0,
@@ -937,8 +937,8 @@ expression: nodes
err_span: None,
err: Null,
span: Span {
range: 81..88,
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
range: 83..90,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
0x0,
@@ -949,8 +949,8 @@ expression: nodes
err_span: None,
err: Null,
span: Span {
range: 89..93,
source: "echo foo | sed s/foo/bar && echo bar | sed s/bar/foo || echo foo bar | sed s/foo bar/bar foo/",
range: 91..95,
source: "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/",
},
flags: TkFlags(
0x0,

View File

@@ -3,7 +3,7 @@ source: src/tests/parser.rs
expression: nodes
---
[
Match(
Ok(
Node {
class: CmdList {
elements: [

View File

@@ -3,7 +3,7 @@ source: src/tests/parser.rs
expression: nodes
---
[
Match(
Ok(
Node {
class: CmdList {
elements: [