about to implement readline myself
This commit is contained in:
161
src/expand.rs
161
src/expand.rs
@@ -5,8 +5,8 @@ use std::str::{Chars, FromStr};
|
||||
use glob::Pattern;
|
||||
use regex::Regex;
|
||||
|
||||
use crate::state::{read_vars, write_meta, write_vars, LogTab};
|
||||
use crate::procio::{IoBuf, IoFrame, IoMode};
|
||||
use crate::state::{read_jobs, read_vars, write_jobs, write_meta, write_vars, LogTab};
|
||||
use crate::procio::{IoBuf, IoFrame, IoMode, IoStack};
|
||||
use crate::prelude::*;
|
||||
use crate::parse::{Redir, RedirType};
|
||||
use crate::parse::execute::exec_input;
|
||||
@@ -23,6 +23,10 @@ pub const SNG_QUOTE: char = '\u{fdd2}';
|
||||
pub const TILDE_SUB: char = '\u{fdd3}';
|
||||
/// Subshell marker
|
||||
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 {
|
||||
/// 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();
|
||||
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 => {
|
||||
flog!(INFO, chars);
|
||||
let expanded = expand_var(chars)?;
|
||||
@@ -358,6 +384,41 @@ pub fn expand_arithmetic(raw: &str) -> ShResult<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
|
||||
pub fn expand_cmd_sub(raw: &str) -> ShResult<String> {
|
||||
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 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);
|
||||
|
||||
match unsafe { fork()? } {
|
||||
ForkResult::Child => {
|
||||
if let Err(e) = cmd_sub_io_frame.redirect() {
|
||||
eprintln!("{e}");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if let Err(e) = exec_input(raw.to_string()) {
|
||||
io_stack.push_frame(cmd_sub_io_frame);
|
||||
if let Err(e) = exec_input(raw.to_string(), Some(io_stack)) {
|
||||
eprintln!("{e}");
|
||||
exit(1);
|
||||
}
|
||||
@@ -411,7 +469,6 @@ pub fn unescape_str(raw: &str) -> String {
|
||||
|
||||
|
||||
while let Some(ch) = chars.next() {
|
||||
flog!(DEBUG,result);
|
||||
match ch {
|
||||
'~' if first_char => {
|
||||
result.push(TILDE_SUB)
|
||||
@@ -464,10 +521,9 @@ pub fn unescape_str(raw: &str) -> String {
|
||||
result.push(VAR_SUB);
|
||||
if chars.peek() == Some(&'(') {
|
||||
chars.next();
|
||||
let mut cmdsub_count = 1;
|
||||
let mut paren_count = 1;
|
||||
result.push(SUBSH);
|
||||
while let Some(subsh_ch) = chars.next() {
|
||||
flog!(DEBUG, subsh_ch);
|
||||
match subsh_ch {
|
||||
'\\' => {
|
||||
result.push(subsh_ch);
|
||||
@@ -475,13 +531,13 @@ pub fn unescape_str(raw: &str) -> String {
|
||||
result.push(next_ch)
|
||||
}
|
||||
}
|
||||
'$' if chars.peek() == Some(&'(') => {
|
||||
'(' => {
|
||||
result.push(subsh_ch);
|
||||
cmdsub_count += 1;
|
||||
paren_count += 1;
|
||||
}
|
||||
')' => {
|
||||
cmdsub_count -= 1;
|
||||
if cmdsub_count <= 0 {
|
||||
paren_count -= 1;
|
||||
if paren_count <= 0 {
|
||||
result.push(SUBSH);
|
||||
break
|
||||
} 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(ch)
|
||||
}
|
||||
first_char = false;
|
||||
}
|
||||
flog!(DEBUG, result);
|
||||
result
|
||||
}
|
||||
|
||||
@@ -1277,16 +1390,16 @@ pub fn expand_prompt(raw: &str) -> ShResult<String> {
|
||||
}
|
||||
}
|
||||
PromptTk::Pwd => {
|
||||
let mut pwd = std::env::var("PWD")?;
|
||||
let home = std::env::var("HOME")?;
|
||||
let mut pwd = std::env::var("PWD").unwrap();
|
||||
let home = std::env::var("HOME").unwrap();
|
||||
if pwd.starts_with(&home) {
|
||||
pwd = pwd.replacen(&home, "~", 1);
|
||||
}
|
||||
result.push_str(&pwd);
|
||||
}
|
||||
PromptTk::PwdShort => {
|
||||
let mut path = std::env::var("PWD")?;
|
||||
let home = std::env::var("HOME")?;
|
||||
let mut path = std::env::var("PWD").unwrap();
|
||||
let home = std::env::var("HOME").unwrap();
|
||||
if path.starts_with(&home) {
|
||||
path = path.replacen(&home, "~", 1);
|
||||
}
|
||||
@@ -1305,17 +1418,17 @@ pub fn expand_prompt(raw: &str) -> ShResult<String> {
|
||||
result.push_str(&path_rebuilt);
|
||||
}
|
||||
PromptTk::Hostname => {
|
||||
let hostname = std::env::var("HOSTNAME")?;
|
||||
let hostname = std::env::var("HOST").unwrap();
|
||||
result.push_str(&hostname);
|
||||
}
|
||||
PromptTk::HostnameShort => todo!(),
|
||||
PromptTk::ShellName => result.push_str("fern"),
|
||||
PromptTk::Username => {
|
||||
let username = std::env::var("USER")?;
|
||||
let username = std::env::var("USER").unwrap();
|
||||
result.push_str(&username);
|
||||
}
|
||||
PromptTk::PromptSymbol => {
|
||||
let uid = std::env::var("UID")?;
|
||||
let uid = std::env::var("UID").unwrap();
|
||||
let symbol = if &uid == "0" {
|
||||
'#'
|
||||
} else {
|
||||
|
||||
20
src/fern.rs
20
src/fern.rs
@@ -24,7 +24,7 @@ use crate::signal::sig_setup;
|
||||
use crate::state::source_rc;
|
||||
use crate::prelude::*;
|
||||
use clap::Parser;
|
||||
use state::write_vars;
|
||||
use state::{read_vars, write_vars};
|
||||
|
||||
#[derive(Parser,Debug)]
|
||||
struct FernArgs {
|
||||
@@ -37,8 +37,20 @@ struct FernArgs {
|
||||
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() {
|
||||
kickstart_lazy_evals();
|
||||
let args = FernArgs::parse();
|
||||
if args.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))
|
||||
}
|
||||
|
||||
if let Err(e) = exec_input(input) {
|
||||
if let Err(e) = exec_input(input,None) {
|
||||
eprintln!("{e}");
|
||||
exit(1);
|
||||
}
|
||||
@@ -94,7 +106,7 @@ fn fern_interactive() {
|
||||
Err(e) => {
|
||||
eprintln!("{e}");
|
||||
readline_err_count += 1;
|
||||
if readline_err_count == 5 {
|
||||
if readline_err_count == 20 {
|
||||
eprintln!("reached maximum readline error count, exiting");
|
||||
break
|
||||
} 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}");
|
||||
}
|
||||
}
|
||||
|
||||
24
src/jobs.rs
24
src/jobs.rs
@@ -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;
|
||||
|
||||
@@ -120,12 +120,19 @@ impl ChildProc {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone,Debug)]
|
||||
pub struct RegisteredFd {
|
||||
pub fd: IoMode,
|
||||
pub owner_pid: Pid,
|
||||
}
|
||||
|
||||
#[derive(Default,Debug)]
|
||||
pub struct JobTab {
|
||||
fg: Option<Job>,
|
||||
order: Vec<usize>,
|
||||
new_updates: Vec<usize>,
|
||||
jobs: Vec<Option<Job>>
|
||||
jobs: Vec<Option<Job>>,
|
||||
fd_registry: Vec<RegisteredFd>
|
||||
}
|
||||
|
||||
impl JobTab {
|
||||
@@ -154,6 +161,19 @@ impl JobTab {
|
||||
pub fn prev_job(&self) -> Option<usize> {
|
||||
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) {
|
||||
while let Some(job) = self.jobs.last() {
|
||||
if job.is_none() {
|
||||
|
||||
@@ -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());
|
||||
let log_tab = LOGIC_TABLE.read().unwrap();
|
||||
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());
|
||||
if let Some(mut stack) = io_stack {
|
||||
dispatcher.io_stack.extend(stack.drain(..));
|
||||
}
|
||||
dispatcher.begin_dispatch()
|
||||
}
|
||||
|
||||
@@ -176,7 +179,7 @@ impl Dispatcher {
|
||||
let subsh_body = subsh.0.to_string();
|
||||
let snapshot = get_snapshots();
|
||||
|
||||
if let Err(e) = exec_input(subsh_body) {
|
||||
if let Err(e) = exec_input(subsh_body, None) {
|
||||
restore_snapshot(snapshot);
|
||||
return Err(e)
|
||||
}
|
||||
|
||||
152
src/parse/lex.rs
152
src/parse/lex.rs
@@ -2,7 +2,7 @@ use std::{collections::VecDeque, fmt::Display, iter::Peekable, ops::{Bound, Dere
|
||||
|
||||
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] = [
|
||||
"if",
|
||||
@@ -145,6 +145,7 @@ bitflags! {
|
||||
const IS_OP = 0b0000000000100000;
|
||||
const ASSIGN = 0b0000000001000000;
|
||||
const BUILTIN = 0b0000000010000000;
|
||||
const IS_PROCSUB = 0b0000000100000000;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -242,6 +243,9 @@ impl LexStream {
|
||||
while let Some(ch) = chars.next() {
|
||||
match ch {
|
||||
'>' => {
|
||||
if chars.peek() == Some(&'(') {
|
||||
return None // It's a process sub
|
||||
}
|
||||
pos += 1;
|
||||
if let Some('>') = chars.peek() {
|
||||
chars.next();
|
||||
@@ -277,6 +281,9 @@ impl LexStream {
|
||||
}
|
||||
}
|
||||
'<' => {
|
||||
if chars.peek() == Some(&'(') {
|
||||
return None // It's a process sub
|
||||
}
|
||||
pos += 1;
|
||||
|
||||
for _ in 0..2 {
|
||||
@@ -327,7 +334,6 @@ impl LexStream {
|
||||
}
|
||||
|
||||
while let Some(ch) = chars.next() {
|
||||
flog!(DEBUG, "we are in the loop");
|
||||
match ch {
|
||||
_ if self.flags.contains(LexFlags::RAW) => {
|
||||
if ch.is_whitespace() {
|
||||
@@ -342,61 +348,6 @@ impl LexStream {
|
||||
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(&'{') => {
|
||||
pos += 2;
|
||||
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(&'(') => {
|
||||
pos += 2;
|
||||
chars.next();
|
||||
@@ -507,7 +542,6 @@ impl LexStream {
|
||||
subsh_tk.flags |= TkFlags::IS_SUBSH;
|
||||
self.cursor = pos;
|
||||
self.set_next_is_cmd(true);
|
||||
flog!(DEBUG, "returning subsh tk");
|
||||
return Ok(subsh_tk)
|
||||
}
|
||||
'{' if pos == self.cursor && self.next_is_cmd() => {
|
||||
@@ -672,8 +706,6 @@ impl LexStream {
|
||||
impl Iterator for LexStream {
|
||||
type Item = ShResult<Tk>;
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
flog!(DEBUG,self.cursor);
|
||||
flog!(DEBUG,self.source.len());
|
||||
assert!(self.cursor <= self.source.len());
|
||||
// We are at the end of the input
|
||||
if self.cursor == self.source.len() {
|
||||
|
||||
@@ -529,7 +529,6 @@ impl ParseStream {
|
||||
/// 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()
|
||||
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_brc_grp(false /* from_func_def */)?);
|
||||
try_match!(self.parse_case()?);
|
||||
@@ -618,15 +617,12 @@ impl ParseStream {
|
||||
fn parse_test(&mut self) -> ShResult<Option<Node>> {
|
||||
let mut node_tks: Vec<Tk> = vec![];
|
||||
let mut cases: Vec<TestCase> = vec![];
|
||||
flog!(INFO, self.check_keyword("[["));
|
||||
if !self.check_keyword("[[") || !self.next_tk_is_some() {
|
||||
return Ok(None)
|
||||
}
|
||||
node_tks.push(self.next_tk().unwrap());
|
||||
let mut case_builder = TestCaseBuilder::new();
|
||||
while let Some(tk) = self.next_tk() {
|
||||
flog!(DEBUG, case_builder);
|
||||
flog!(DEBUG, tk.as_str());
|
||||
node_tks.push(tk.clone());
|
||||
if tk.as_str() == "]]" {
|
||||
if case_builder.can_build() {
|
||||
@@ -642,24 +638,19 @@ impl ParseStream {
|
||||
}
|
||||
}
|
||||
if case_builder.is_empty() {
|
||||
flog!(DEBUG, "case builder is empty");
|
||||
match tk.as_str() {
|
||||
_ if TEST_UNARY_OPS.contains(&tk.as_str()) => case_builder = case_builder.with_operator(tk.clone()),
|
||||
_ => case_builder = case_builder.with_lhs(tk.clone())
|
||||
}
|
||||
continue
|
||||
} 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());
|
||||
continue
|
||||
} 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?
|
||||
case_builder = case_builder.with_operator(tk.clone());
|
||||
continue
|
||||
} 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.conjunct.is_some() {
|
||||
return Err(
|
||||
@@ -674,7 +665,6 @@ impl ParseStream {
|
||||
case_builder = case_builder.with_conjunction(op);
|
||||
let case = case_builder.build_and_take();
|
||||
cases.push(case);
|
||||
flog!(DEBUG, case_builder);
|
||||
continue
|
||||
} else {
|
||||
return Err(
|
||||
@@ -694,7 +684,6 @@ impl ParseStream {
|
||||
redirs: vec![],
|
||||
tokens: node_tks
|
||||
};
|
||||
flog!(DEBUG, node);
|
||||
Ok(Some(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);
|
||||
self.catch_separator(&mut node_tks);
|
||||
|
||||
flog!(DEBUG, node_tks);
|
||||
let Some(cond) = self.parse_block(true)? else {
|
||||
self.panic_mode(&mut node_tks);
|
||||
return Err(parse_err_full(
|
||||
@@ -1225,7 +1213,6 @@ impl ParseStream {
|
||||
return Ok(None)
|
||||
}
|
||||
}
|
||||
flog!(DEBUG, argv);
|
||||
|
||||
if argv.is_empty() && assignments.is_empty() {
|
||||
return Ok(None)
|
||||
|
||||
@@ -6,7 +6,7 @@ use std::path::Path;
|
||||
use readline::FernReadline;
|
||||
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
|
||||
fn init_rl() -> ShResult<Editor<FernReadline,FileHistory>> {
|
||||
@@ -45,7 +45,11 @@ fn init_rl() -> ShResult<Editor<FernReadline,FileHistory>> {
|
||||
|
||||
fn get_prompt() -> ShResult<String> {
|
||||
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)?))
|
||||
|
||||
@@ -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
|
||||
* If this assumption is incorrect, the code has gone wrong somewhere.
|
||||
*/
|
||||
write_jobs(|j| j.close_job_fds(pid));
|
||||
if let Some((
|
||||
pgid,
|
||||
is_fg,
|
||||
@@ -132,6 +133,7 @@ pub fn child_exited(pid: Pid, status: WtStat) -> ShResult<()> {
|
||||
job.update_by_id(JobID::Pid(pid), status).unwrap();
|
||||
let is_finished = !job.running();
|
||||
|
||||
|
||||
if let Some(child) = job.children_mut().iter_mut().find(|chld| pid == chld.pid()) {
|
||||
child.set_stat(status);
|
||||
}
|
||||
|
||||
@@ -170,7 +170,7 @@ impl VarTab {
|
||||
let hostname = gethostname().map(|hname| hname.to_string_lossy().to_string()).unwrap_or_default();
|
||||
|
||||
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("PPID", getppid().to_string());
|
||||
env::set_var("TMPDIR", "/tmp");
|
||||
@@ -448,6 +448,6 @@ pub fn source_file(path: PathBuf) -> ShResult<()> {
|
||||
|
||||
let mut buf = String::new();
|
||||
file.read_to_string(&mut buf)?;
|
||||
exec_input(buf)?;
|
||||
exec_input(buf,None)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user