about to implement readline myself

This commit is contained in:
2025-05-15 00:53:39 -04:00
parent c9414c8ce3
commit 94e22d68f3
9 changed files with 291 additions and 118 deletions

View File

@@ -5,8 +5,8 @@ use std::str::{Chars, FromStr};
use glob::Pattern; use glob::Pattern;
use regex::Regex; use regex::Regex;
use crate::state::{read_vars, write_meta, write_vars, LogTab}; use crate::state::{read_jobs, read_vars, write_jobs, write_meta, write_vars, LogTab};
use crate::procio::{IoBuf, IoFrame, IoMode}; use crate::procio::{IoBuf, IoFrame, IoMode, IoStack};
use crate::prelude::*; use crate::prelude::*;
use crate::parse::{Redir, RedirType}; use crate::parse::{Redir, RedirType};
use crate::parse::execute::exec_input; use crate::parse::execute::exec_input;
@@ -23,6 +23,10 @@ pub const SNG_QUOTE: char = '\u{fdd2}';
pub const TILDE_SUB: char = '\u{fdd3}'; pub const TILDE_SUB: char = '\u{fdd3}';
/// Subshell marker /// Subshell marker
pub const SUBSH: char = '\u{fdd4}'; pub const SUBSH: char = '\u{fdd4}';
/// Input process sub marker
pub const PROC_SUB_IN: char = '\u{fdd5}';
/// Output process sub marker
pub const PROC_SUB_OUT: char = '\u{fdd6}';
impl Tk { impl Tk {
/// Create a new expanded token /// Create a new expanded token
@@ -105,6 +109,28 @@ pub fn expand_raw(chars: &mut Peekable<Chars<'_>>) -> ShResult<String> {
let home = env::var("HOME").unwrap_or_default(); let home = env::var("HOME").unwrap_or_default();
result.push_str(&home); result.push_str(&home);
} }
PROC_SUB_OUT =>{
let mut inner = String::new();
while let Some(ch) = chars.next() {
match ch {
PROC_SUB_OUT => break,
_ => inner.push(ch)
}
}
let fd_path = expand_proc_sub(&inner, false)?;
result.push_str(&fd_path);
}
PROC_SUB_IN =>{
let mut inner = String::new();
while let Some(ch) = chars.next() {
match ch {
PROC_SUB_IN => break,
_ => inner.push(ch)
}
}
let fd_path = expand_proc_sub(&inner, true)?;
result.push_str(&fd_path);
}
VAR_SUB => { VAR_SUB => {
flog!(INFO, chars); flog!(INFO, chars);
let expanded = expand_var(chars)?; let expanded = expand_var(chars)?;
@@ -358,6 +384,41 @@ pub fn expand_arithmetic(raw: &str) -> ShResult<String> {
Ok(result.to_string()) Ok(result.to_string())
} }
pub fn expand_proc_sub(raw: &str, is_input: bool) -> ShResult<String> {
// FIXME: Still a lot of issues here
// Seems like debugging will be a massive effort
let (rpipe, wpipe) = IoMode::get_pipes();
let rpipe_raw = rpipe.src_fd();
let wpipe_raw = wpipe.src_fd();
let (proc_fd, register_fd, redir_type, path) = match is_input {
false => (wpipe, rpipe, RedirType::Output, format!("/proc/self/fd/{}", rpipe_raw)),
true => (rpipe, wpipe, RedirType::Input, format!("/proc/self/fd/{}", wpipe_raw)),
};
match unsafe { fork()? } {
ForkResult::Child => {
let redir = Redir::new(proc_fd, redir_type);
let io_frame = IoFrame::from_redir(redir);
let mut io_stack = IoStack::new();
io_stack.push_frame(io_frame);
if let Err(e) = exec_input(raw.to_string(), Some(io_stack)) {
eprintln!("{e}");
exit(1);
}
exit(0);
}
ForkResult::Parent { child } => {
write_jobs(|j| j.register_fd(child, register_fd));
let registered = read_jobs(|j| j.registered_fds().to_vec());
flog!(DEBUG,registered);
// Do not wait; process may run in background
Ok(path)
}
}
}
/// Get the command output of a given command input as a String /// Get the command output of a given command input as a String
pub fn expand_cmd_sub(raw: &str) -> ShResult<String> { pub fn expand_cmd_sub(raw: &str) -> ShResult<String> {
flog!(DEBUG, "in expand_cmd_sub"); flog!(DEBUG, "in expand_cmd_sub");
@@ -369,17 +430,14 @@ pub fn expand_cmd_sub(raw: &str) -> ShResult<String> {
} }
let (rpipe,wpipe) = IoMode::get_pipes(); let (rpipe,wpipe) = IoMode::get_pipes();
let cmd_sub_redir = Redir::new(wpipe, RedirType::Output); let cmd_sub_redir = Redir::new(wpipe, RedirType::Output);
let mut cmd_sub_io_frame = IoFrame::from_redir(cmd_sub_redir); let cmd_sub_io_frame = IoFrame::from_redir(cmd_sub_redir);
let mut io_stack = IoStack::new();
let mut io_buf = IoBuf::new(rpipe); let mut io_buf = IoBuf::new(rpipe);
match unsafe { fork()? } { match unsafe { fork()? } {
ForkResult::Child => { ForkResult::Child => {
if let Err(e) = cmd_sub_io_frame.redirect() { io_stack.push_frame(cmd_sub_io_frame);
eprintln!("{e}"); if let Err(e) = exec_input(raw.to_string(), Some(io_stack)) {
exit(1);
}
if let Err(e) = exec_input(raw.to_string()) {
eprintln!("{e}"); eprintln!("{e}");
exit(1); exit(1);
} }
@@ -411,7 +469,6 @@ pub fn unescape_str(raw: &str) -> String {
while let Some(ch) = chars.next() { while let Some(ch) = chars.next() {
flog!(DEBUG,result);
match ch { match ch {
'~' if first_char => { '~' if first_char => {
result.push(TILDE_SUB) result.push(TILDE_SUB)
@@ -464,10 +521,9 @@ pub fn unescape_str(raw: &str) -> String {
result.push(VAR_SUB); result.push(VAR_SUB);
if chars.peek() == Some(&'(') { if chars.peek() == Some(&'(') {
chars.next(); chars.next();
let mut cmdsub_count = 1; let mut paren_count = 1;
result.push(SUBSH); result.push(SUBSH);
while let Some(subsh_ch) = chars.next() { while let Some(subsh_ch) = chars.next() {
flog!(DEBUG, subsh_ch);
match subsh_ch { match subsh_ch {
'\\' => { '\\' => {
result.push(subsh_ch); result.push(subsh_ch);
@@ -475,13 +531,13 @@ pub fn unescape_str(raw: &str) -> String {
result.push(next_ch) result.push(next_ch)
} }
} }
'$' if chars.peek() == Some(&'(') => { '(' => {
result.push(subsh_ch); result.push(subsh_ch);
cmdsub_count += 1; paren_count += 1;
} }
')' => { ')' => {
cmdsub_count -= 1; paren_count -= 1;
if cmdsub_count <= 0 { if paren_count <= 0 {
result.push(SUBSH); result.push(SUBSH);
break break
} else { } else {
@@ -513,12 +569,69 @@ pub fn unescape_str(raw: &str) -> String {
} }
} }
} }
'<' if chars.peek() == Some(&'(') => {
chars.next();
let mut paren_count = 1;
result.push(PROC_SUB_OUT);
while let Some(subsh_ch) = chars.next() {
match subsh_ch {
'\\' => {
result.push(subsh_ch);
if let Some(next_ch) = chars.next() {
result.push(next_ch)
}
}
'(' => {
result.push(subsh_ch);
paren_count += 1;
}
')' => {
paren_count -= 1;
if paren_count <= 0 {
result.push(PROC_SUB_OUT);
break
} else {
result.push(subsh_ch);
}
}
_ => result.push(subsh_ch),
}
}
}
'>' if chars.peek() == Some(&'(') => {
chars.next();
let mut paren_count = 1;
result.push(PROC_SUB_IN);
while let Some(subsh_ch) = chars.next() {
match subsh_ch {
'\\' => {
result.push(subsh_ch);
if let Some(next_ch) = chars.next() {
result.push(next_ch)
}
}
'(' => {
result.push(subsh_ch);
paren_count += 1;
}
')' => {
paren_count -= 1;
if paren_count <= 0 {
result.push(PROC_SUB_IN);
break
} else {
result.push(subsh_ch);
}
}
_ => result.push(subsh_ch),
}
}
}
'$' => result.push(VAR_SUB), '$' => result.push(VAR_SUB),
_ => result.push(ch) _ => result.push(ch)
} }
first_char = false; first_char = false;
} }
flog!(DEBUG, result);
result result
} }
@@ -1277,16 +1390,16 @@ pub fn expand_prompt(raw: &str) -> ShResult<String> {
} }
} }
PromptTk::Pwd => { PromptTk::Pwd => {
let mut pwd = std::env::var("PWD")?; let mut pwd = std::env::var("PWD").unwrap();
let home = std::env::var("HOME")?; let home = std::env::var("HOME").unwrap();
if pwd.starts_with(&home) { if pwd.starts_with(&home) {
pwd = pwd.replacen(&home, "~", 1); pwd = pwd.replacen(&home, "~", 1);
} }
result.push_str(&pwd); result.push_str(&pwd);
} }
PromptTk::PwdShort => { PromptTk::PwdShort => {
let mut path = std::env::var("PWD")?; let mut path = std::env::var("PWD").unwrap();
let home = std::env::var("HOME")?; let home = std::env::var("HOME").unwrap();
if path.starts_with(&home) { if path.starts_with(&home) {
path = path.replacen(&home, "~", 1); path = path.replacen(&home, "~", 1);
} }
@@ -1305,17 +1418,17 @@ pub fn expand_prompt(raw: &str) -> ShResult<String> {
result.push_str(&path_rebuilt); result.push_str(&path_rebuilt);
} }
PromptTk::Hostname => { PromptTk::Hostname => {
let hostname = std::env::var("HOSTNAME")?; let hostname = std::env::var("HOST").unwrap();
result.push_str(&hostname); result.push_str(&hostname);
} }
PromptTk::HostnameShort => todo!(), PromptTk::HostnameShort => todo!(),
PromptTk::ShellName => result.push_str("fern"), PromptTk::ShellName => result.push_str("fern"),
PromptTk::Username => { PromptTk::Username => {
let username = std::env::var("USER")?; let username = std::env::var("USER").unwrap();
result.push_str(&username); result.push_str(&username);
} }
PromptTk::PromptSymbol => { PromptTk::PromptSymbol => {
let uid = std::env::var("UID")?; let uid = std::env::var("UID").unwrap();
let symbol = if &uid == "0" { let symbol = if &uid == "0" {
'#' '#'
} else { } else {

View File

@@ -24,7 +24,7 @@ use crate::signal::sig_setup;
use crate::state::source_rc; use crate::state::source_rc;
use crate::prelude::*; use crate::prelude::*;
use clap::Parser; use clap::Parser;
use state::write_vars; use state::{read_vars, write_vars};
#[derive(Parser,Debug)] #[derive(Parser,Debug)]
struct FernArgs { struct FernArgs {
@@ -37,8 +37,20 @@ struct FernArgs {
version: bool version: bool
} }
/// Force evaluation of lazily-initialized values early in shell startup.
///
/// In particular, this ensures that the variable table is initialized, which populates
/// environment variables from the system. If this initialization is deferred too long,
/// features like prompt expansion may fail due to missing environment variables.
///
/// This function triggers initialization by calling `read_vars` with a no-op closure,
/// which forces access to the variable table and causes its `LazyLock` constructor to run.
fn kickstart_lazy_evals() {
read_vars(|_| {});
}
fn main() { fn main() {
kickstart_lazy_evals();
let args = FernArgs::parse(); let args = FernArgs::parse();
if args.version { if args.version {
println!("fern {}", env!("CARGO_PKG_VERSION")); println!("fern {}", env!("CARGO_PKG_VERSION"));
@@ -68,7 +80,7 @@ fn run_script<P: AsRef<Path>>(path: P, args: Vec<String>) {
write_vars(|v| v.bpush_arg(arg)) write_vars(|v| v.bpush_arg(arg))
} }
if let Err(e) = exec_input(input) { if let Err(e) = exec_input(input,None) {
eprintln!("{e}"); eprintln!("{e}");
exit(1); exit(1);
} }
@@ -94,7 +106,7 @@ fn fern_interactive() {
Err(e) => { Err(e) => {
eprintln!("{e}"); eprintln!("{e}");
readline_err_count += 1; readline_err_count += 1;
if readline_err_count == 5 { if readline_err_count == 20 {
eprintln!("reached maximum readline error count, exiting"); eprintln!("reached maximum readline error count, exiting");
break break
} else { } else {
@@ -103,7 +115,7 @@ fn fern_interactive() {
} }
}; };
if let Err(e) = exec_input(input) { if let Err(e) = exec_input(input,None) {
eprintln!("{e}"); eprintln!("{e}");
} }
} }

View File

@@ -1,4 +1,4 @@
use crate::{libsh::{error::ShResult, term::{Style, Styled}}, prelude::*, procio::borrow_fd, state::{self, set_status, write_jobs}}; use crate::{libsh::{error::ShResult, term::{Style, Styled}}, prelude::*, procio::{borrow_fd, IoMode}, state::{self, set_status, write_jobs}};
pub const SIG_EXIT_OFFSET: i32 = 128; pub const SIG_EXIT_OFFSET: i32 = 128;
@@ -120,12 +120,19 @@ impl ChildProc {
} }
} }
#[derive(Clone,Debug)]
pub struct RegisteredFd {
pub fd: IoMode,
pub owner_pid: Pid,
}
#[derive(Default,Debug)] #[derive(Default,Debug)]
pub struct JobTab { pub struct JobTab {
fg: Option<Job>, fg: Option<Job>,
order: Vec<usize>, order: Vec<usize>,
new_updates: Vec<usize>, new_updates: Vec<usize>,
jobs: Vec<Option<Job>> jobs: Vec<Option<Job>>,
fd_registry: Vec<RegisteredFd>
} }
impl JobTab { impl JobTab {
@@ -154,6 +161,19 @@ impl JobTab {
pub fn prev_job(&self) -> Option<usize> { pub fn prev_job(&self) -> Option<usize> {
self.order.last().copied() self.order.last().copied()
} }
pub fn close_job_fds(&mut self, pid: Pid) {
self.fd_registry.retain(|fd| fd.owner_pid != pid)
}
pub fn registered_fds(&self) -> &[RegisteredFd] {
&self.fd_registry
}
pub fn register_fd(&mut self, owner_pid: Pid, fd: IoMode) {
let registered_fd = RegisteredFd {
fd,
owner_pid
};
self.fd_registry.push(registered_fd)
}
fn prune_jobs(&mut self) { fn prune_jobs(&mut self) {
while let Some(job) = self.jobs.last() { while let Some(job) = self.jobs.last() {
if job.is_none() { if job.is_none() {

View File

@@ -39,7 +39,7 @@ impl ExecArgs {
} }
} }
pub fn exec_input(input: String) -> ShResult<()> { pub fn exec_input(input: String, io_stack: Option<IoStack>) -> ShResult<()> {
write_meta(|m| m.start_timer()); write_meta(|m| m.start_timer());
let log_tab = LOGIC_TABLE.read().unwrap(); let log_tab = LOGIC_TABLE.read().unwrap();
let input = expand_aliases(input, HashSet::new(), &log_tab); let input = expand_aliases(input, HashSet::new(), &log_tab);
@@ -53,6 +53,9 @@ pub fn exec_input(input: String) -> ShResult<()> {
} }
let mut dispatcher = Dispatcher::new(parser.extract_nodes()); let mut dispatcher = Dispatcher::new(parser.extract_nodes());
if let Some(mut stack) = io_stack {
dispatcher.io_stack.extend(stack.drain(..));
}
dispatcher.begin_dispatch() dispatcher.begin_dispatch()
} }
@@ -176,7 +179,7 @@ impl Dispatcher {
let subsh_body = subsh.0.to_string(); let subsh_body = subsh.0.to_string();
let snapshot = get_snapshots(); let snapshot = get_snapshots();
if let Err(e) = exec_input(subsh_body) { if let Err(e) = exec_input(subsh_body, None) {
restore_snapshot(snapshot); restore_snapshot(snapshot);
return Err(e) return Err(e)
} }

View File

@@ -2,7 +2,7 @@ use std::{collections::VecDeque, fmt::Display, iter::Peekable, ops::{Bound, Dere
use bitflags::bitflags; use bitflags::bitflags;
use crate::{builtin::BUILTINS, libsh::{error::{ShErr, ShErrKind, ShResult}, utils::CharDequeUtils}, parse::parse_err_full, prelude::*}; use crate::{builtin::BUILTINS, libsh::{error::{ShErr, ShErrKind, ShResult}, utils::CharDequeUtils}, prelude::*};
pub const KEYWORDS: [&str;16] = [ pub const KEYWORDS: [&str;16] = [
"if", "if",
@@ -136,15 +136,16 @@ impl Display for Tk {
bitflags! { bitflags! {
#[derive(Debug,Clone,Copy,PartialEq,Default)] #[derive(Debug,Clone,Copy,PartialEq,Default)]
pub struct TkFlags: u32 { pub struct TkFlags: u32 {
const KEYWORD = 0b0000000000000001; const KEYWORD = 0b0000000000000001;
/// This is a keyword that opens a new block statement, like 'if' and 'while' /// This is a keyword that opens a new block statement, like 'if' and 'while'
const OPENER = 0b0000000000000010; const OPENER = 0b0000000000000010;
const IS_CMD = 0b0000000000000100; const IS_CMD = 0b0000000000000100;
const IS_SUBSH = 0b0000000000001000; const IS_SUBSH = 0b0000000000001000;
const IS_CMDSUB = 0b0000000000010000; const IS_CMDSUB = 0b0000000000010000;
const IS_OP = 0b0000000000100000; const IS_OP = 0b0000000000100000;
const ASSIGN = 0b0000000001000000; const ASSIGN = 0b0000000001000000;
const BUILTIN = 0b0000000010000000; const BUILTIN = 0b0000000010000000;
const IS_PROCSUB = 0b0000000100000000;
} }
} }
@@ -242,6 +243,9 @@ impl LexStream {
while let Some(ch) = chars.next() { while let Some(ch) = chars.next() {
match ch { match ch {
'>' => { '>' => {
if chars.peek() == Some(&'(') {
return None // It's a process sub
}
pos += 1; pos += 1;
if let Some('>') = chars.peek() { if let Some('>') = chars.peek() {
chars.next(); chars.next();
@@ -277,6 +281,9 @@ impl LexStream {
} }
} }
'<' => { '<' => {
if chars.peek() == Some(&'(') {
return None // It's a process sub
}
pos += 1; pos += 1;
for _ in 0..2 { for _ in 0..2 {
@@ -327,7 +334,6 @@ impl LexStream {
} }
while let Some(ch) = chars.next() { while let Some(ch) = chars.next() {
flog!(DEBUG, "we are in the loop");
match ch { match ch {
_ if self.flags.contains(LexFlags::RAW) => { _ if self.flags.contains(LexFlags::RAW) => {
if ch.is_whitespace() { if ch.is_whitespace() {
@@ -342,61 +348,6 @@ impl LexStream {
pos += ch.len_utf8(); pos += ch.len_utf8();
} }
} }
'`' => {
let arith_pos = pos;
pos += 1;
let mut closed = false;
while let Some(arith_ch) = chars.next() {
match arith_ch {
'\\' => {
pos += 1;
if let Some(ch) = chars.next() {
pos += ch.len_utf8();
}
}
'`' => {
pos += 1;
closed = true;
break
}
'$' if chars.peek() == Some(&'(') => {
pos += 2;
chars.next();
let mut cmdsub_count = 1;
while let Some(cmdsub_ch) = chars.next() {
match cmdsub_ch {
'$' if chars.peek() == Some(&'(') => {
pos += 2;
chars.next();
cmdsub_count += 1;
}
')' => {
pos += 1;
cmdsub_count -= 1;
if cmdsub_count == 0 {
break
}
}
_ => pos += cmdsub_ch.len_utf8()
}
}
}
_ => pos += arith_ch.len_utf8()
}
}
flog!(DEBUG, "we have left the loop");
flog!(DEBUG, closed);
if !closed && !self.flags.contains(LexFlags::LEX_UNFINISHED) {
self.cursor = pos;
return Err(
ShErr::full(
ShErrKind::ParseErr,
"Unclosed arithmetic substitution",
Span::new(arith_pos..arith_pos + 1, self.source.clone())
)
)
}
}
'$' if chars.peek() == Some(&'{') => { '$' if chars.peek() == Some(&'{') => {
pos += 2; pos += 2;
chars.next(); chars.next();
@@ -424,6 +375,90 @@ impl LexStream {
} }
} }
} }
'<' if chars.peek() == Some(&'(') => {
pos += 2;
chars.next();
let mut paren_count = 1;
let paren_pos = pos;
while let Some(ch) = chars.next() {
match ch {
'\\' => {
pos += 1;
if let Some(next_ch) = chars.next() {
pos += next_ch.len_utf8();
}
}
'(' => {
pos += 1;
paren_count += 1;
}
')' => {
pos += 1;
paren_count -= 1;
if paren_count <= 0 {
break
}
}
_ => pos += ch.len_utf8()
}
}
if !paren_count == 0 && !self.flags.contains(LexFlags::LEX_UNFINISHED) {
self.cursor = pos;
return Err(
ShErr::full(
ShErrKind::ParseErr,
"Unclosed subshell",
Span::new(paren_pos..paren_pos + 1, self.source.clone())
)
)
}
let mut proc_sub_tk = self.get_token(self.cursor..pos, TkRule::Str);
proc_sub_tk.flags |= TkFlags::IS_PROCSUB;
self.cursor = pos;
return Ok(proc_sub_tk)
}
'>' if chars.peek() == Some(&'(') => {
pos += 2;
chars.next();
let mut paren_count = 1;
let paren_pos = pos;
while let Some(ch) = chars.next() {
match ch {
'\\' => {
pos += 1;
if let Some(next_ch) = chars.next() {
pos += next_ch.len_utf8();
}
}
'(' => {
pos += 1;
paren_count += 1;
}
')' => {
pos += 1;
paren_count -= 1;
if paren_count <= 0 {
break
}
}
_ => pos += ch.len_utf8()
}
}
if !paren_count == 0 && !self.flags.contains(LexFlags::LEX_UNFINISHED) {
self.cursor = pos;
return Err(
ShErr::full(
ShErrKind::ParseErr,
"Unclosed subshell",
Span::new(paren_pos..paren_pos + 1, self.source.clone())
)
)
}
let mut proc_sub_tk = self.get_token(self.cursor..pos, TkRule::Str);
proc_sub_tk.flags |= TkFlags::IS_PROCSUB;
self.cursor = pos;
return Ok(proc_sub_tk)
}
'$' if chars.peek() == Some(&'(') => { '$' if chars.peek() == Some(&'(') => {
pos += 2; pos += 2;
chars.next(); chars.next();
@@ -507,7 +542,6 @@ impl LexStream {
subsh_tk.flags |= TkFlags::IS_SUBSH; subsh_tk.flags |= TkFlags::IS_SUBSH;
self.cursor = pos; self.cursor = pos;
self.set_next_is_cmd(true); self.set_next_is_cmd(true);
flog!(DEBUG, "returning subsh tk");
return Ok(subsh_tk) return Ok(subsh_tk)
} }
'{' if pos == self.cursor && self.next_is_cmd() => { '{' if pos == self.cursor && self.next_is_cmd() => {
@@ -672,8 +706,6 @@ impl LexStream {
impl Iterator for LexStream { impl Iterator for LexStream {
type Item = ShResult<Tk>; type Item = ShResult<Tk>;
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
flog!(DEBUG,self.cursor);
flog!(DEBUG,self.source.len());
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
if self.cursor == self.source.len() { if self.cursor == self.source.len() {

View File

@@ -529,7 +529,6 @@ impl ParseStream {
/// Ordered from specialized to general, with more generally matchable stuff appearing at the bottom /// Ordered from specialized to general, with more generally matchable stuff appearing at the bottom
/// The check_pipelines parameter is used to prevent left-recursion issues in self.parse_pipeln() /// The check_pipelines parameter is used to prevent left-recursion issues in self.parse_pipeln()
fn parse_block(&mut self, check_pipelines: bool) -> ShResult<Option<Node>> { fn parse_block(&mut self, check_pipelines: bool) -> ShResult<Option<Node>> {
flog!(DEBUG, self.tokens);
try_match!(self.parse_func_def()?); try_match!(self.parse_func_def()?);
try_match!(self.parse_brc_grp(false /* from_func_def */)?); try_match!(self.parse_brc_grp(false /* from_func_def */)?);
try_match!(self.parse_case()?); try_match!(self.parse_case()?);
@@ -618,15 +617,12 @@ impl ParseStream {
fn parse_test(&mut self) -> ShResult<Option<Node>> { fn parse_test(&mut self) -> ShResult<Option<Node>> {
let mut node_tks: Vec<Tk> = vec![]; let mut node_tks: Vec<Tk> = vec![];
let mut cases: Vec<TestCase> = vec![]; let mut cases: Vec<TestCase> = vec![];
flog!(INFO, self.check_keyword("[["));
if !self.check_keyword("[[") || !self.next_tk_is_some() { if !self.check_keyword("[[") || !self.next_tk_is_some() {
return Ok(None) return Ok(None)
} }
node_tks.push(self.next_tk().unwrap()); node_tks.push(self.next_tk().unwrap());
let mut case_builder = TestCaseBuilder::new(); let mut case_builder = TestCaseBuilder::new();
while let Some(tk) = self.next_tk() { while let Some(tk) = self.next_tk() {
flog!(DEBUG, case_builder);
flog!(DEBUG, tk.as_str());
node_tks.push(tk.clone()); node_tks.push(tk.clone());
if tk.as_str() == "]]" { if tk.as_str() == "]]" {
if case_builder.can_build() { if case_builder.can_build() {
@@ -642,24 +638,19 @@ impl ParseStream {
} }
} }
if case_builder.is_empty() { if case_builder.is_empty() {
flog!(DEBUG, "case builder is empty");
match tk.as_str() { match tk.as_str() {
_ if TEST_UNARY_OPS.contains(&tk.as_str()) => case_builder = case_builder.with_operator(tk.clone()), _ if TEST_UNARY_OPS.contains(&tk.as_str()) => case_builder = case_builder.with_operator(tk.clone()),
_ => case_builder = case_builder.with_lhs(tk.clone()) _ => case_builder = case_builder.with_lhs(tk.clone())
} }
continue continue
} else if case_builder.operator.is_some() && case_builder.rhs.is_none() { } else if case_builder.operator.is_some() && case_builder.rhs.is_none() {
flog!(DEBUG, "op is some, rhs is none");
case_builder = case_builder.with_rhs(tk.clone()); case_builder = case_builder.with_rhs(tk.clone());
continue continue
} else if case_builder.lhs.is_some() && case_builder.operator.is_none() { } else if case_builder.lhs.is_some() && case_builder.operator.is_none() {
flog!(DEBUG, "lhs is some, op is none");
// we got lhs, then rhs → treat it as operator maybe? // we got lhs, then rhs → treat it as operator maybe?
case_builder = case_builder.with_operator(tk.clone()); case_builder = case_builder.with_operator(tk.clone());
continue continue
} else if let TkRule::And | TkRule::Or = tk.class { } else if let TkRule::And | TkRule::Or = tk.class {
flog!(DEBUG, "found conjunction");
flog!(DEBUG, tk.class);
if case_builder.can_build() { if case_builder.can_build() {
if case_builder.conjunct.is_some() { if case_builder.conjunct.is_some() {
return Err( return Err(
@@ -674,7 +665,6 @@ impl ParseStream {
case_builder = case_builder.with_conjunction(op); case_builder = case_builder.with_conjunction(op);
let case = case_builder.build_and_take(); let case = case_builder.build_and_take();
cases.push(case); cases.push(case);
flog!(DEBUG, case_builder);
continue continue
} else { } else {
return Err( return Err(
@@ -694,7 +684,6 @@ impl ParseStream {
redirs: vec![], redirs: vec![],
tokens: node_tks tokens: node_tks
}; };
flog!(DEBUG, node);
Ok(Some(node)) Ok(Some(node))
} }
fn parse_brc_grp(&mut self, from_func_def: bool) -> ShResult<Option<Node>> { fn parse_brc_grp(&mut self, from_func_def: bool) -> ShResult<Option<Node>> {
@@ -1114,7 +1103,6 @@ impl ParseStream {
node_tks.push(loop_tk); node_tks.push(loop_tk);
self.catch_separator(&mut node_tks); self.catch_separator(&mut node_tks);
flog!(DEBUG, node_tks);
let Some(cond) = self.parse_block(true)? else { let Some(cond) = self.parse_block(true)? else {
self.panic_mode(&mut node_tks); self.panic_mode(&mut node_tks);
return Err(parse_err_full( return Err(parse_err_full(
@@ -1225,7 +1213,6 @@ impl ParseStream {
return Ok(None) return Ok(None)
} }
} }
flog!(DEBUG, argv);
if argv.is_empty() && assignments.is_empty() { if argv.is_empty() && assignments.is_empty() {
return Ok(None) return Ok(None)

View File

@@ -6,7 +6,7 @@ use std::path::Path;
use readline::FernReadline; use readline::FernReadline;
use rustyline::{error::ReadlineError, history::FileHistory, ColorMode, Config, Editor}; use rustyline::{error::ReadlineError, history::FileHistory, ColorMode, Config, Editor};
use crate::{expand::expand_prompt, libsh::{error::ShResult, term::{Style, Styled}}, prelude::*, state::read_shopts}; use crate::{expand::expand_prompt, libsh::error::ShResult, prelude::*, state::read_shopts};
/// Initialize the line editor /// Initialize the line editor
fn init_rl() -> ShResult<Editor<FernReadline,FileHistory>> { fn init_rl() -> ShResult<Editor<FernReadline,FileHistory>> {
@@ -45,7 +45,11 @@ fn init_rl() -> ShResult<Editor<FernReadline,FileHistory>> {
fn get_prompt() -> ShResult<String> { fn get_prompt() -> ShResult<String> {
let Ok(prompt) = env::var("PS1") else { let Ok(prompt) = env::var("PS1") else {
return Ok("$ ".styled(Style::Green | Style::Bold)) // username@hostname
// short/path/to/pwd/
// $
let default = "\\e[1;0m\\u\\e[1;36m@\\e[1;31m\\h\\n\\e[1;36m\\W\\e[1;32m/\\n\\e[1;32m\\$ ";
return Ok(format!("\n{}",expand_prompt(default)?))
}; };
Ok(format!("\n{}",expand_prompt(&prompt)?)) Ok(format!("\n{}",expand_prompt(&prompt)?))

View File

@@ -120,6 +120,7 @@ pub fn child_exited(pid: Pid, status: WtStat) -> ShResult<()> {
* We can reasonably assume that if it is not a foreground job, then it exists in the job table * We can reasonably assume that if it is not a foreground job, then it exists in the job table
* If this assumption is incorrect, the code has gone wrong somewhere. * If this assumption is incorrect, the code has gone wrong somewhere.
*/ */
write_jobs(|j| j.close_job_fds(pid));
if let Some(( if let Some((
pgid, pgid,
is_fg, is_fg,
@@ -132,6 +133,7 @@ pub fn child_exited(pid: Pid, status: WtStat) -> ShResult<()> {
job.update_by_id(JobID::Pid(pid), status).unwrap(); job.update_by_id(JobID::Pid(pid), status).unwrap();
let is_finished = !job.running(); let is_finished = !job.running();
if let Some(child) = job.children_mut().iter_mut().find(|chld| pid == chld.pid()) { if let Some(child) = job.children_mut().iter_mut().find(|chld| pid == chld.pid()) {
child.set_stat(status); child.set_stat(status);
} }

View File

@@ -170,7 +170,7 @@ impl VarTab {
let hostname = gethostname().map(|hname| hname.to_string_lossy().to_string()).unwrap_or_default(); let hostname = gethostname().map(|hname| hname.to_string_lossy().to_string()).unwrap_or_default();
env::set_var("IFS", " \t\n"); env::set_var("IFS", " \t\n");
env::set_var("HOSTNAME", hostname); env::set_var("HOST", hostname.clone());
env::set_var("UID", uid.to_string()); env::set_var("UID", uid.to_string());
env::set_var("PPID", getppid().to_string()); env::set_var("PPID", getppid().to_string());
env::set_var("TMPDIR", "/tmp"); env::set_var("TMPDIR", "/tmp");
@@ -448,6 +448,6 @@ pub fn source_file(path: PathBuf) -> ShResult<()> {
let mut buf = String::new(); let mut buf = String::new();
file.read_to_string(&mut buf)?; file.read_to_string(&mut buf)?;
exec_input(buf)?; exec_input(buf,None)?;
Ok(()) Ok(())
} }