From 62c76e70a666110322842db8652b7a8370421e70 Mon Sep 17 00:00:00 2001 From: pagedmov Date: Tue, 18 Mar 2025 15:54:06 -0400 Subject: [PATCH] Implemented prompt expansion, and display for errors --- src/builtin/jobctl.rs | 4 +- src/builtin/mod.rs | 8 +- src/builtin/shift.rs | 2 +- src/expand.rs | 431 ++++++++++++++++++++++++++++++++++++++++- src/fern.rs | 39 ++-- src/libsh/error.rs | 176 +++++++++++++++-- src/libsh/utils.rs | 4 +- src/parse/execute.rs | 82 ++++---- src/parse/lex.rs | 70 +++---- src/parse/mod.rs | 288 +++++++++++++++------------ src/procio.rs | 2 +- src/prompt/mod.rs | 30 ++- src/prompt/readline.rs | 3 +- src/state.rs | 171 +++++++++++++--- 14 files changed, 1022 insertions(+), 288 deletions(-) diff --git a/src/builtin/jobctl.rs b/src/builtin/jobctl.rs index c91853b..9f17c50 100644 --- a/src/builtin/jobctl.rs +++ b/src/builtin/jobctl.rs @@ -1,4 +1,4 @@ -use crate::{jobs::{ChildProc, JobBldr, JobCmdFlags, JobID}, libsh::error::{ErrSpan, ShErr, ShErrKind, ShResult}, parse::{execute::prepare_argv, lex::Span, NdRule, Node}, prelude::*, procio::{borrow_fd, IoStack}, state::{self, read_jobs, write_jobs}}; +use crate::{jobs::{ChildProc, JobBldr, JobCmdFlags, JobID}, libsh::error::{ShErr, ShErrKind, ShResult}, parse::{execute::prepare_argv, lex::Span, NdRule, Node}, prelude::*, procio::{borrow_fd, IoStack}, state::{self, read_jobs, write_jobs}}; use super::setup_builtin; @@ -8,7 +8,7 @@ pub enum JobBehavior { } pub fn continue_job(node: Node, job: &mut JobBldr, behavior: JobBehavior) -> ShResult<()> { - let blame = ErrSpan::from(node.get_span()); + let blame = node.get_span().clone(); let cmd = match behavior { JobBehavior::Foregound => "fg", JobBehavior::Background => "bg" diff --git a/src/builtin/mod.rs b/src/builtin/mod.rs index 9cecd69..d7be3e1 100644 --- a/src/builtin/mod.rs +++ b/src/builtin/mod.rs @@ -44,11 +44,11 @@ pub const BUILTINS: [&str;9] = [ /// * If redirections are given to this function, the caller must call `IoFrame.restore()` on the returned `IoFrame` /// * If redirections are given, the second field of the resulting tuple will *always* be `Some()` /// * If no redirections are given, the second field will *always* be `None` -pub fn setup_builtin<'t>( - argv: Vec>, - job: &'t mut JobBldr, +pub fn setup_builtin( + argv: Vec, + job: &mut JobBldr, io_mode: Option<(&mut IoStack,Vec)>, -) -> ShResult<(Vec<(String,Span<'t>)>, Option)> { +) -> ShResult<(Vec<(String,Span)>, Option)> { let mut argv: Vec<(String,Span)> = prepare_argv(argv); let child_pgid = if let Some(pgid) = job.pgid() { diff --git a/src/builtin/shift.rs b/src/builtin/shift.rs index 7ceb52b..3b2b401 100644 --- a/src/builtin/shift.rs +++ b/src/builtin/shift.rs @@ -1,4 +1,4 @@ -use crate::{jobs::{ChildProc, JobBldr}, libsh::error::{ErrSpan, ShErr, ShErrKind, ShResult}, parse::{execute::prepare_argv, NdRule, Node}, prelude::*, state::{self, write_vars}}; +use crate::{jobs::{ChildProc, JobBldr}, libsh::error::{ShErr, ShErrKind, ShResult}, parse::{execute::prepare_argv, NdRule, Node}, prelude::*, state::{self, write_vars}}; use super::setup_builtin; diff --git a/src/expand.rs b/src/expand.rs index 56b7473..3165d43 100644 --- a/src/expand.rs +++ b/src/expand.rs @@ -1,4 +1,4 @@ -use crate::{prelude::*, parse::lex::{is_field_sep, is_hard_sep, LexFlags, LexStream, Span, Tk, TkFlags, TkRule}, state::read_vars}; +use crate::{libsh::error::ShResult, parse::lex::{is_field_sep, is_hard_sep, LexFlags, LexStream, Span, Tk, TkFlags, TkRule}, prelude::*, state::{read_meta, read_vars, write_meta}}; /// Variable substitution marker pub const VAR_SUB: char = '\u{fdd0}'; @@ -7,14 +7,14 @@ pub const DUB_QUOTE: char = '\u{fdd1}'; /// Single quote '\\'' marker pub const SNG_QUOTE: char = '\u{fdd2}'; -impl<'t> Tk<'t> { +impl Tk { /// Create a new expanded token /// /// params /// tokens: A vector of raw tokens lexed from the expansion result /// span: The span of the original token that is being expanded /// flags: some TkFlags - pub fn expand(self, span: Span<'t>, flags: TkFlags) -> Self { + pub fn expand(self, span: Span, flags: TkFlags) -> Self { let exp = Expander::new(self).expand(); let class = TkRule::Expanded { exp }; Self { class, span, flags, } @@ -31,12 +31,12 @@ pub struct Expander { raw: String, } -impl<'t> Expander { - pub fn new(raw: Tk<'t>) -> Self { +impl Expander { + pub fn new(raw: Tk) -> Self { let unescaped = unescape_str(raw.span.as_str()); Self { raw: unescaped } } - pub fn expand(&'t mut self) -> Vec { + pub fn expand(&mut self) -> Vec { self.raw = self.expand_raw(); self.split_words() } @@ -85,7 +85,7 @@ impl<'t> Expander { var_name.clear(); break } - _ if is_hard_sep(ch) => { + _ if is_hard_sep(ch) || ch == DUB_QUOTE => { let var_val = read_vars(|v| v.get_var(&var_name)); result.push_str(&var_val); result.push(ch); @@ -122,11 +122,424 @@ pub fn unescape_str(raw: &str) -> String { result.push(next_ch) } } - '"' => result.push(DUB_QUOTE), - '\'' => result.push(SNG_QUOTE), + '"' => { + result.push(DUB_QUOTE); + while let Some(q_ch) = chars.next() { + match q_ch { + '\\' => { + result.push(q_ch); + if let Some(next_ch) = chars.next() { + result.push(next_ch) + } + } + '$' => result.push(VAR_SUB), + '"' => { + result.push(DUB_QUOTE); + break + } + _ => result.push(q_ch) + } + } + } + '\'' => { + result.push(SNG_QUOTE); + while let Some(q_ch) = chars.next() { + match q_ch { + '\'' => { + result.push(SNG_QUOTE); + break + } + _ => result.push(q_ch) + } + } + } '$' => result.push(VAR_SUB), _ => result.push(ch) } } result } + +#[derive(Debug)] +pub enum PromptTk { + AsciiOct(i32), + Text(String), + AnsiSeq(String), + VisGrp, + UserSeq, + Runtime, + Weekday, + Dquote, + Squote, + Return, + Newline, + Pwd, + PwdShort, + Hostname, + HostnameShort, + ShellName, + Username, + PromptSymbol, + ExitCode, + SuccessSymbol, + FailureSymbol, + JobCount +} + +pub fn format_cmd_runtime(dur: std::time::Duration) -> String { + const ETERNITY: u128 = f32::INFINITY as u128; + let mut micros = dur.as_micros(); + let mut millis = 0; + let mut seconds = 0; + let mut minutes = 0; + let mut hours = 0; + let mut days = 0; + let mut weeks = 0; + let mut months = 0; + let mut years = 0; + let mut decades = 0; + let mut centuries = 0; + let mut millennia = 0; + let mut epochs = 0; + let mut aeons = 0; + let mut eternities = 0; + + if micros >= 1000 { + millis = micros / 1000; + micros %= 1000; + } + if millis >= 1000 { + seconds = millis / 1000; + millis %= 1000; + } + if seconds >= 60 { + minutes = seconds / 60; + seconds %= 60; + } + if minutes >= 60 { + hours = minutes / 60; + minutes %= 60; + } + if hours >= 24 { + days = hours / 24; + hours %= 24; + } + if days >= 7 { + weeks = days / 7; + days %= 7; + } + if weeks >= 4 { + months = weeks / 4; + weeks %= 4; + } + if months >= 12 { + years = months / 12; + weeks %= 12; + } + if years >= 10 { + decades = years / 10; + years %= 10; + } + if decades >= 10 { + centuries = decades / 10; + decades %= 10; + } + if centuries >= 10 { + millennia = centuries / 10; + centuries %= 10; + } + if millennia >= 1000 { + epochs = millennia / 1000; + millennia %= 1000; + } + if epochs >= 1000 { + aeons = epochs / 1000; + epochs %= aeons; + } + if aeons == ETERNITY { + eternities = aeons / ETERNITY; + aeons %= ETERNITY; + } + + // Format the result + let mut result = Vec::new(); + if eternities > 0 { + let mut string = format!("{} eternit", eternities); + if eternities > 1 { + string.push_str("ies"); + } else { + string.push('y'); + } + result.push(string) + } + if aeons > 0 { + let mut string = format!("{} aeon", aeons); + if aeons > 1 { + string.push('s') + } + result.push(string) + } + if epochs > 0 { + let mut string = format!("{} epoch", epochs); + if epochs > 1 { + string.push('s') + } + result.push(string) + } + if millennia > 0 { + let mut string = format!("{} millenni", millennia); + if millennia > 1 { + string.push_str("um") + } else { + string.push('a') + } + result.push(string) + } + if centuries > 0 { + let mut string = format!("{} centur", centuries); + if centuries > 1 { + string.push_str("ies") + } else { + string.push('y') + } + result.push(string) + } + if decades > 0 { + let mut string = format!("{} decade", decades); + if decades > 1 { + string.push('s') + } + result.push(string) + } + if years > 0 { + let mut string = format!("{} year", years); + if years > 1 { + string.push('s') + } + result.push(string) + } + if months > 0 { + let mut string = format!("{} month", months); + if months > 1 { + string.push('s') + } + result.push(string) + } + if weeks > 0 { + let mut string = format!("{} week", weeks); + if weeks > 1 { + string.push('s') + } + result.push(string) + } + if days > 0 { + let mut string = format!("{} day", days); + if days > 1 { + string.push('s') + } + result.push(string) + } + if hours > 0 { + let string = format!("{}h", hours); + result.push(string); + } + if minutes > 0 { + let string = format!("{}m", minutes); + result.push(string); + } + if seconds > 0 { + let string = format!("{}s", seconds); + result.push(string); + } + if millis > 0 { + let string = format!("{}ms",millis); + result.push(string); + } + if result.is_empty() && micros > 0 { + let string = format!("{}µs",micros); + result.push(string); + } + + result.join(" ") +} + +fn tokenize_prompt(raw: &str) -> Vec { + let mut chars = raw.chars().peekable(); + let mut tk_text = String::new(); + let mut tokens = vec![]; + + while let Some(ch) = chars.next() { + match ch { + '\\' => { + // Push any accumulated text as a token + if !tk_text.is_empty() { + tokens.push(PromptTk::Text(std::mem::take(&mut tk_text))); + } + + // Handle the escape sequence + if let Some(ch) = chars.next() { + match ch { + 'w' => tokens.push(PromptTk::Pwd), + 'W' => tokens.push(PromptTk::PwdShort), + 'h' => tokens.push(PromptTk::Hostname), + 'H' => tokens.push(PromptTk::HostnameShort), + 's' => tokens.push(PromptTk::ShellName), + 'u' => tokens.push(PromptTk::Username), + '$' => tokens.push(PromptTk::PromptSymbol), + 'n' => tokens.push(PromptTk::Text("\n".into())), + 'r' => tokens.push(PromptTk::Text("\r".into())), + 'T' => tokens.push(PromptTk::Runtime), + '\\' => tokens.push(PromptTk::Text("\\".into())), + '"' => tokens.push(PromptTk::Text("\"".into())), + '\'' => tokens.push(PromptTk::Text("'".into())), + 'e' => { + if chars.next() == Some('[') { + let mut params = String::new(); + + // Collect parameters and final character + while let Some(ch) = chars.next() { + match ch { + '0'..='9' | ';' | '?' | ':' => params.push(ch), // Valid parameter characters + 'A'..='Z' | 'a'..='z' => { // Final character (letter) + params.push(ch); + break; + } + _ => { + // Invalid character in ANSI sequence + tokens.push(PromptTk::Text(format!("\x1b[{params}"))); + break; + } + } + } + + tokens.push(PromptTk::AnsiSeq(format!("\x1b[{params}"))); + } else { + // Handle case where 'e' is not followed by '[' + tokens.push(PromptTk::Text("\\e".into())); + } + } + '0'..='7' => { + // Handle octal escape + let mut octal_str = String::new(); + octal_str.push(ch); + + // Collect up to 2 more octal digits + for _ in 0..2 { + if let Some(&next_ch) = chars.peek() { + if next_ch >= '0' && next_ch <= '7' { + octal_str.push(chars.next().unwrap()); + } else { + break; + } + } else { + break; + } + } + + // Parse the octal string into an integer + if let Ok(octal) = i32::from_str_radix(&octal_str, 8) { + tokens.push(PromptTk::AsciiOct(octal)); + } else { + // Fallback: treat as raw text + tokens.push(PromptTk::Text(format!("\\{octal_str}"))); + } + } + _ => { + // Unknown escape sequence: treat as raw text + tokens.push(PromptTk::Text(format!("\\{ch}"))); + } + } + } else { + // Handle trailing backslash + tokens.push(PromptTk::Text("\\".into())); + } + } + _ => { + // Accumulate non-escape characters + tk_text.push(ch); + } + } + } + + // Push any remaining text as a token + if !tk_text.is_empty() { + tokens.push(PromptTk::Text(tk_text)); + } + + tokens +} + +pub fn expand_prompt(raw: &str) -> ShResult { + let mut tokens = tokenize_prompt(raw).into_iter(); + let mut result = String::new(); + + while let Some(token) = tokens.next() { + match token { + PromptTk::AsciiOct(_) => todo!(), + PromptTk::Text(txt) => result.push_str(&txt), + PromptTk::AnsiSeq(params) => result.push_str(¶ms), + PromptTk::Runtime => { + flog!(INFO, "getting runtime"); + if let Some(runtime) = write_meta(|m| m.stop_timer()) { + flog!(DEBUG, runtime); + let runtime_fmt = format_cmd_runtime(runtime); + result.push_str(&runtime_fmt); + } + } + PromptTk::Pwd => { + let mut pwd = std::env::var("PWD")?; + let home = std::env::var("HOME")?; + 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")?; + if path.starts_with(&home) { + path = path.replacen(&home, "~", 1); + } + let pathbuf = PathBuf::from(&path); + let mut segments = pathbuf.iter().count(); + let mut path_iter = pathbuf.into_iter(); + while segments > 4 { + path_iter.next(); + segments -= 1; + } + let path_rebuilt: PathBuf = path_iter.collect(); + let mut path_rebuilt = path_rebuilt.to_str().unwrap().to_string(); + if path_rebuilt.starts_with(&home) { + path_rebuilt = path_rebuilt.replacen(&home, "~", 1); + } + result.push_str(&path_rebuilt); + } + PromptTk::Hostname => { + let hostname = std::env::var("HOSTNAME")?; + result.push_str(&hostname); + } + PromptTk::HostnameShort => todo!(), + PromptTk::ShellName => result.push_str("fern"), + PromptTk::Username => { + let username = std::env::var("USER")?; + result.push_str(&username); + } + PromptTk::PromptSymbol => { + let uid = std::env::var("UID")?; + let symbol = if &uid == "0" { + '#' + } else { + '$' + }; + result.push(symbol); + } + PromptTk::ExitCode => todo!(), + PromptTk::SuccessSymbol => todo!(), + PromptTk::FailureSymbol => todo!(), + PromptTk::JobCount => todo!(), + _ => unimplemented!() + } + } + + Ok(result) +} diff --git a/src/fern.rs b/src/fern.rs index b07b63d..0ad319e 100644 --- a/src/fern.rs +++ b/src/fern.rs @@ -12,14 +12,16 @@ pub mod signal; pub mod tests; use libsh::error::ShResult; -use parse::{execute::Dispatcher, lex::{LexFlags, LexStream}, ParseStream}; +use parse::{execute::Dispatcher, lex::{LexFlags, LexStream}, Ast, ParseStream, ParsedSrc}; use procio::IoFrame; use signal::sig_setup; +use state::write_meta; use termios::{LocalFlags, Termios}; use crate::prelude::*; pub static mut SAVED_TERMIOS: Option> = None; + pub fn save_termios() { unsafe { SAVED_TERMIOS = Some(if isatty(std::io::stdin().as_raw_fd()).unwrap() { @@ -47,28 +49,15 @@ fn set_termios() { } } -pub fn exec_input(input: &str, io_frame: Option) -> ShResult<()> { - let parse_start = Instant::now(); - let mut tokens = vec![]; - for token in LexStream::new(&input, LexFlags::empty()) { - tokens.push(token?); - } - - let mut nodes = vec![]; - for result in ParseStream::new(tokens) { - nodes.push(result?); - } - flog!(INFO, "parse duration: {:?}", parse_start.elapsed()); +pub fn exec_input(input: String) -> ShResult<()> { + write_meta(|m| m.start_timer()); + let mut parser = ParsedSrc::new(Rc::new(input)); + parser.parse_src()?; let exec_start = Instant::now(); - let mut dispatcher = Dispatcher::new(nodes); - if let Some(frame) = io_frame { - dispatcher.io_stack.push(frame) - } - dispatcher.begin_dispatch()?; - flog!(INFO, "cmd duration: {:?}", exec_start.elapsed()); - flog!(INFO, "total duration: {:?}", parse_start.elapsed()); + let mut dispatcher = Dispatcher::new(parser.extract_nodes()); + dispatcher.begin_dispatch()?; Ok(()) } @@ -79,9 +68,15 @@ fn main() { loop { - let input = prompt::read_line().unwrap(); + let input = match prompt::read_line() { + Ok(line) => line, + Err(e) => { + eprintln!("{e}"); + continue + } + }; - if let Err(e) = exec_input(&input,None) { + if let Err(e) = exec_input(input) { eprintln!("{e}"); } } diff --git a/src/libsh/error.rs b/src/libsh/error.rs index 6671a6e..ca8acfb 100644 --- a/src/libsh/error.rs +++ b/src/libsh/error.rs @@ -1,45 +1,63 @@ -use std::{fmt::Display, ops::Range}; +use std::fmt::Display; -use crate::{parse::lex::Span, prelude::*}; +use crate::{ + libsh::term::{Style, Styled}, + parse::lex::Span, + prelude::* +}; pub type ShResult = Result; -#[derive(Debug)] -pub struct ErrSpan { - range: Range, - source: String +pub trait ShResultExt { + fn blame(self, span: Span) -> Self; + fn try_blame(self, span: Span) -> Self; } -impl<'s> From> for ErrSpan { - fn from(value: Span<'s>) -> Self { - let range = value.range(); - let source = value.get_source().to_string(); - Self { range, source } +impl ShResultExt for Result { + /// Blame a span for an error + fn blame(self, new_span: Span) -> Self { + let Err(e) = self else { + return self + }; + match e { + ShErr::Simple { kind, msg } | + ShErr::Full { kind, msg, span: _ } => Err(ShErr::full(kind, msg, new_span)), + } + } + /// Blame a span if no blame has been assigned yet + fn try_blame(self, new_span: Span) -> Self { + let Err(e) = &self else { + return self + }; + match e { + ShErr::Simple { kind, msg } => Err(ShErr::full(*kind, msg, new_span)), + ShErr::Full { kind: _, msg: _, span: _ } => self + } } } #[derive(Debug)] pub enum ShErr { Simple { kind: ShErrKind, msg: String }, - Full { kind: ShErrKind, msg: String, span: ErrSpan } + Full { kind: ShErrKind, msg: String, span: Span } } -impl<'s> ShErr { +impl ShErr { pub fn simple(kind: ShErrKind, msg: impl Into) -> Self { let msg = msg.into(); Self::Simple { kind, msg } } - pub fn full(kind: ShErrKind, msg: impl Into, span: ErrSpan) -> Self { + pub fn full(kind: ShErrKind, msg: impl Into, span: Span) -> Self { let msg = msg.into(); Self::Full { kind, msg, span } } - pub fn unpack(self) -> (ShErrKind,String,Option) { + pub fn unpack(self) -> (ShErrKind,String,Option) { match self { ShErr::Simple { kind, msg } => (kind,msg,None), ShErr::Full { kind, msg, span } => (kind,msg,Some(span)) } } - pub fn with_span(sherr: ShErr, span: Span<'s>) -> Self { + pub fn with_span(sherr: ShErr, span: Span) -> Self { let (kind,msg,_) = sherr.unpack(); let span = span.into(); Self::Full { kind, msg, span } @@ -50,13 +68,112 @@ impl<'s> ShErr { ShErr::Full { kind, msg: _, span: _ } => kind } } + pub fn get_window(&self) -> Vec<(usize,String)> { + let ShErr::Full { kind: _, msg: _, span } = self else { + unreachable!() + }; + let mut total_len: usize = 0; + let mut total_lines: usize = 1; + let mut lines = vec![]; + let mut cur_line = String::new(); + + let src = span.get_source(); + let mut chars = src.chars(); + + while let Some(ch) = chars.next() { + total_len += ch.len_utf8(); + cur_line.push(ch); + if ch == '\n' { + total_lines += 1; + + if total_len >= span.start { + let line = ( + total_lines, + mem::take(&mut cur_line) + ); + lines.push(line); + } + if total_len >= span.end { + break + } + } + } + + if !cur_line.is_empty() { + let line = ( + total_lines, + mem::take(&mut cur_line) + ); + lines.push(line); + } + + lines + } + pub fn get_line_col(&self) -> (usize,usize) { + let ShErr::Full { kind: _, msg: _, span } = self else { + unreachable!() + }; + + let mut lineno = 1; + let mut colno = 1; + let src = span.get_source(); + let mut chars = src.chars().enumerate(); + while let Some((pos,ch)) = chars.next() { + if pos >= span.start { + break + } + if ch == '\n' { + lineno += 1; + colno = 1; + } else { + colno += 1; + } + } + (lineno,colno) + } } impl Display for ShErr { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::Simple { msg, kind: _ } => writeln!(f, "{}", msg), - Self::Full { msg, kind: _, span: _ } => writeln!(f, "{}", msg) + Self::Full { msg, kind, span: _ } => { + let window = self.get_window(); + let mut lineno_pad_count = 0; + for (lineno,_) in window.clone() { + if lineno.to_string().len() > lineno_pad_count { + lineno_pad_count = lineno.to_string().len() + 1 + } + } + let (line,col) = self.get_line_col(); + let line = line.styled(Style::Cyan | Style::Bold); + let col = col.styled(Style::Cyan | Style::Bold); + let kind = kind.styled(Style::Red | Style::Bold); + let padding = " ".repeat(lineno_pad_count); + let arrow = "->".styled(Style::Cyan | Style::Bold); + writeln!(f, + "{padding}{arrow} [{line};{col}] - {kind}", + )?; + + let mut bar = format!("{padding}|"); + bar = bar.styled(Style::Cyan | Style::Bold); + writeln!(f,"{bar}")?; + + for (lineno,line) in window { + let lineno = lineno.to_string(); + let mut prefix = format!("{padding}|"); + prefix.replace_range(0..lineno.len(), &lineno); + prefix = prefix.styled(Style::Cyan | Style::Bold); + writeln!(f,"{prefix} {line}")?; + } + + writeln!(f,"{bar}")?; + + let bar_break = "-".styled(Style::Cyan | Style::Bold); + writeln!(f, + "{padding}{bar_break} {msg}", + ) + } } } } @@ -86,7 +203,7 @@ impl From for ShErr { } } -#[derive(Debug)] +#[derive(Debug,Clone,Copy)] pub enum ShErrKind { IoErr, SyntaxErr, @@ -104,3 +221,26 @@ pub enum ShErrKind { LoopBreak, Null } + +impl Display for ShErrKind { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let output = match self { + ShErrKind::IoErr => "I/O Error", + ShErrKind::SyntaxErr => "Syntax Error", + ShErrKind::ParseErr => "Parse Error", + ShErrKind::InternalErr => "Internal Error", + ShErrKind::ExecFail => "Execution Failed", + ShErrKind::ResourceLimitExceeded => "Resource Limit Exceeded", + ShErrKind::BadPermission => "Bad Permissions", + ShErrKind::Errno => "ERRNO", + ShErrKind::FileNotFound => "File Not Found", + ShErrKind::CmdNotFound => "Command Not Found", + ShErrKind::CleanExit => "", + ShErrKind::FuncReturn => "", + ShErrKind::LoopContinue => "", + ShErrKind::LoopBreak => "", + ShErrKind::Null => "", + }; + write!(f,"{output}") + } +} diff --git a/src/libsh/utils.rs b/src/libsh/utils.rs index 0e8ee1c..6f28eeb 100644 --- a/src/libsh/utils.rs +++ b/src/libsh/utils.rs @@ -26,8 +26,8 @@ impl VecDequeExt for VecDeque { } } -impl<'t> TkVecUtils> for Vec> { - fn get_span(&self) -> Option> { +impl TkVecUtils for Vec { + fn get_span(&self) -> Option { if let Some(first_tk) = self.first() { if let Some(last_tk) = self.last() { Some( diff --git a/src/parse/execute.rs b/src/parse/execute.rs index 9927155..a7620d8 100644 --- a/src/parse/execute.rs +++ b/src/parse/execute.rs @@ -1,9 +1,9 @@ use std::collections::VecDeque; -use crate::{builtin::{cd::cd, echo::echo, export::export, jobctl::{continue_job, jobs, JobBehavior}, pwd::pwd, shift::shift, source::source}, exec_input, jobs::{dispatch_job, ChildProc, Job, JobBldr, JobStack}, libsh::{error::{ErrSpan, ShErr, ShErrKind, ShResult}, utils::RedirVecUtils}, prelude::*, procio::{IoFrame, IoMode, IoStack}, state::{self, read_logic, read_vars, write_logic, write_vars}}; +use crate::{builtin::{cd::cd, echo::echo, export::export, jobctl::{continue_job, jobs, JobBehavior}, pwd::pwd, shift::shift, source::source}, exec_input, jobs::{dispatch_job, ChildProc, Job, JobBldr, JobStack}, libsh::{error::{ShErr, ShErrKind, ShResult, ShResultExt}, utils::RedirVecUtils}, prelude::*, procio::{IoFrame, IoMode, IoStack}, state::{self, read_logic, read_vars, write_logic, write_vars, ShFunc, VarTab}}; -use super::{lex::{LexFlags, LexStream, Span, Tk, TkFlags}, AssignKind, CondNode, ConjunctNode, ConjunctOp, LoopKind, NdFlags, NdRule, Node, ParseStream, Redir, RedirType}; +use super::{lex::{LexFlags, LexStream, Span, Tk, TkFlags}, AssignKind, CondNode, ConjunctNode, ConjunctOp, LoopKind, NdFlags, NdRule, Node, ParseStream, ParsedSrc, Redir, RedirType}; pub enum AssignBehavior { Export, @@ -37,25 +37,26 @@ impl ExecArgs { } } -pub struct Dispatcher<'t> { - nodes: VecDeque>, +pub struct Dispatcher { + nodes: VecDeque, pub io_stack: IoStack, pub job_stack: JobStack } -impl<'t> Dispatcher<'t> { - pub fn new(nodes: Vec>) -> Self { +impl Dispatcher { + pub fn new(nodes: Vec) -> Self { let nodes = VecDeque::from(nodes); Self { nodes, io_stack: IoStack::new(), job_stack: JobStack::new() } } pub fn begin_dispatch(&mut self) -> ShResult<()> { flog!(TRACE, "beginning dispatch"); - while let Some(list) = self.nodes.pop_front() { - self.dispatch_node(list)?; + while let Some(node) = self.nodes.pop_front() { + let blame = node.get_span(); + self.dispatch_node(node).try_blame(blame)?; } Ok(()) } - pub fn dispatch_node(&mut self, node: Node<'t>) -> ShResult<()> { + pub fn dispatch_node(&mut self, node: Node) -> ShResult<()> { match node.class { NdRule::Conjunction {..} => self.exec_conjunction(node)?, NdRule::Pipeline {..} => self.exec_pipeline(node)?, @@ -68,7 +69,7 @@ impl<'t> Dispatcher<'t> { } Ok(()) } - pub fn dispatch_cmd(&mut self, node: Node<'t>) -> ShResult<()> { + pub fn dispatch_cmd(&mut self, node: Node) -> ShResult<()> { let Some(cmd) = node.get_command() else { return self.exec_cmd(node) // Argv is empty, probably an assignment }; @@ -80,7 +81,7 @@ impl<'t> Dispatcher<'t> { self.exec_cmd(node) } } - pub fn exec_conjunction(&mut self, conjunction: Node<'t>) -> ShResult<()> { + pub fn exec_conjunction(&mut self, conjunction: Node) -> ShResult<()> { let NdRule::Conjunction { elements } = conjunction.class else { unreachable!() }; @@ -99,42 +100,51 @@ impl<'t> Dispatcher<'t> { } Ok(()) } - pub fn exec_func_def(&mut self, func_def: Node<'t>) -> ShResult<()> { + pub fn exec_func_def(&mut self, func_def: Node) -> ShResult<()> { let NdRule::FuncDef { name, body } = func_def.class else { unreachable!() }; let body_span = body.get_span(); - let body = body_span.as_str(); + let body = body_span.as_str().to_string(); let name = name.span.as_str().strip_suffix("()").unwrap(); - write_logic(|l| l.insert_func(name, body)); + + let mut func_parser = ParsedSrc::new(Rc::new(body)); + func_parser.parse_src()?; // Parse the function + + let func = ShFunc::new(func_parser); + write_logic(|l| l.insert_func(name, func)); // Store the AST Ok(()) } - pub fn exec_func(&mut self, func: Node<'t>) -> ShResult<()> { - let blame: ErrSpan = func.get_span().into(); - // TODO: Find a way to store functions as pre-parsed nodes so we don't have to re-parse them + pub fn exec_func(&mut self, func: Node) -> ShResult<()> { + let blame = func.get_span().clone(); let NdRule::Command { assignments, mut argv } = func.class else { unreachable!() }; self.set_assignments(assignments, AssignBehavior::Export); - let mut io_frame = self.io_stack.pop_frame(); - io_frame.extend(func.redirs); + self.io_stack.append_to_frame(func.redirs); let func_name = argv.remove(0).span.as_str().to_string(); - if let Some(func_body) = read_logic(|l| l.get_func(&func_name)) { - let saved_sh_args = read_vars(|v| v.sh_argv().clone()); + if let Some(func) = read_logic(|l| l.get_func(&func_name)) { + let scope_snapshot = read_vars(|v| v.clone()); + // Set up the inner scope write_vars(|v| { + **v = VarTab::new(); v.clear_args(); for arg in argv { v.bpush_arg(arg.to_string()); } }); - let result = exec_input(&func_body, Some(io_frame)); + if let Err(e) = self.exec_brc_grp((*func).clone()) { + write_vars(|v| **v = scope_snapshot); + return Err(e.into()) + } - write_vars(|v| *v.sh_argv_mut() = saved_sh_args); - Ok(result?) + // Return to the outer scope + write_vars(|v| **v = scope_snapshot); + Ok(()) } else { Err( ShErr::full( @@ -145,7 +155,7 @@ impl<'t> Dispatcher<'t> { ) } } - pub fn exec_brc_grp(&mut self, brc_grp: Node<'t>) -> ShResult<()> { + pub fn exec_brc_grp(&mut self, brc_grp: Node) -> ShResult<()> { let NdRule::BraceGrp { body } = brc_grp.class else { unreachable!() }; @@ -153,13 +163,14 @@ impl<'t> Dispatcher<'t> { io_frame.extend(brc_grp.redirs); for node in body { + let blame = node.get_span(); self.io_stack.push_frame(io_frame.clone()); - self.dispatch_node(node)?; + self.dispatch_node(node).try_blame(blame)?; } Ok(()) } - pub fn exec_loop(&mut self, loop_stmt: Node<'t>) -> ShResult<()> { + pub fn exec_loop(&mut self, loop_stmt: Node) -> ShResult<()> { let NdRule::LoopNode { kind, cond_node } = loop_stmt.class else { unreachable!(); }; @@ -204,7 +215,7 @@ impl<'t> Dispatcher<'t> { Ok(()) } - pub fn exec_if(&mut self, if_stmt: Node<'t>) -> ShResult<()> { + pub fn exec_if(&mut self, if_stmt: Node) -> ShResult<()> { let NdRule::IfNode { cond_nodes, else_block } = if_stmt.class else { unreachable!(); }; @@ -244,7 +255,7 @@ impl<'t> Dispatcher<'t> { Ok(()) } - pub fn exec_pipeline(&mut self, pipeline: Node<'t>) -> ShResult<()> { + pub fn exec_pipeline(&mut self, pipeline: Node) -> ShResult<()> { let NdRule::Pipeline { cmds, pipe_err } = pipeline.class else { unreachable!() }; @@ -254,6 +265,7 @@ impl<'t> Dispatcher<'t> { .into_iter() .zip(cmds); + for ((rpipe,wpipe), cmd) in pipes_and_cmds { if let Some(pipe) = rpipe { self.io_stack.push_to_frame(pipe); @@ -268,7 +280,7 @@ impl<'t> Dispatcher<'t> { dispatch_job(job, is_bg)?; Ok(()) } - pub fn exec_builtin(&mut self, mut cmd: Node<'t>) -> ShResult<()> { + pub fn exec_builtin(&mut self, mut cmd: Node) -> ShResult<()> { let NdRule::Command { ref mut assignments, argv } = &mut cmd.class else { unreachable!() }; @@ -301,7 +313,7 @@ impl<'t> Dispatcher<'t> { } Ok(()) } - pub fn exec_cmd(&mut self, cmd: Node<'t>) -> ShResult<()> { + pub fn exec_cmd(&mut self, cmd: Node) -> ShResult<()> { let NdRule::Command { assignments, argv } = cmd.class else { unreachable!() }; @@ -337,7 +349,7 @@ impl<'t> Dispatcher<'t> { Ok(()) } - pub fn set_assignments(&self, assigns: Vec>, behavior: AssignBehavior) -> Vec { + pub fn set_assignments(&self, assigns: Vec, behavior: AssignBehavior) -> Vec { let mut new_env_vars = vec![]; match behavior { AssignBehavior::Export => { @@ -420,7 +432,7 @@ where } /// The default behavior for the child process after forking -pub fn def_child_action<'t>(mut io_frame: IoFrame, exec_args: Option) { +pub fn def_child_action(mut io_frame: IoFrame, exec_args: Option) { if let Err(e) = io_frame.redirect() { eprintln!("{e}"); } @@ -435,7 +447,7 @@ pub fn def_child_action<'t>(mut io_frame: IoFrame, exec_args: Option) } /// The default behavior for the parent process after forking -pub fn def_parent_action<'t>( +pub fn def_parent_action( io_frame: IoFrame, job: &mut JobBldr, cmd: Option<&str>, @@ -478,7 +490,7 @@ pub fn get_pipe_stack(num_cmds: usize) -> Vec<(Option,Option)> { stack } -pub fn is_func<'t>(tk: Option>) -> bool { +pub fn is_func(tk: Option) -> bool { let Some(tk) = tk else { return false }; diff --git a/src/parse/lex.rs b/src/parse/lex.rs index 1b3cfff..851b7a7 100644 --- a/src/parse/lex.rs +++ b/src/parse/lex.rs @@ -31,14 +31,14 @@ pub const OPENERS: [&'static str;6] = [ ]; #[derive(Clone,PartialEq,Default,Debug)] -pub struct Span<'s> { +pub struct Span { range: Range, - source: &'s str + source: Rc } -impl<'s> Span<'s> { +impl Span { /// New `Span`. Wraps a range and a string slice that it refers to. - pub fn new(range: Range, source: &'s str) -> Self { + pub fn new(range: Range, source: Rc) -> Self { Span { range, source, @@ -48,8 +48,8 @@ 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 get_source(&self) -> Rc { + self.source.clone() } pub fn range(&self) -> Range { self.range.clone() @@ -57,7 +57,7 @@ impl<'s> Span<'s> { } /// Allows simple access to the underlying range wrapped by the span -impl<'s> Deref for Span<'s> { +impl Deref for Span { type Target = Range; fn deref(&self) -> &Self::Target { &self.range @@ -90,15 +90,15 @@ impl Default for TkRule { } #[derive(Clone,Debug,PartialEq,Default)] -pub struct Tk<'s> { +pub struct Tk { pub class: TkRule, - pub span: Span<'s>, + pub span: Span, pub flags: TkFlags } // There's one impl here and then another in expand.rs which has the expansion logic -impl<'s> Tk<'s> { - pub fn new(class: TkRule, span: Span<'s>) -> Self { +impl Tk { + pub fn new(class: TkRule, span: Span) -> Self { Self { class, span, flags: TkFlags::empty() } } pub fn to_string(&self) -> String { @@ -107,12 +107,12 @@ impl<'s> Tk<'s> { _ => self.span.as_str().to_string() } } - pub fn source(&self) -> &'s str { - self.span.source + pub fn source(&self) -> Rc { + self.span.source.clone() } } -impl<'s> Display for Tk<'s> { +impl Display for Tk { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match &self.class { TkRule::Expanded { exp } => write!(f,"{}",exp.join(" ")), @@ -135,8 +135,8 @@ bitflags! { } } -pub struct LexStream<'t> { - source: &'t str, +pub struct LexStream { + source: Rc, pub cursor: usize, in_quote: bool, flags: LexFlags, @@ -162,8 +162,8 @@ bitflags! { } } -impl<'t> LexStream<'t> { - pub fn new(source: &'t str, flags: LexFlags) -> Self { +impl LexStream { + pub fn new(source: Rc, 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 } @@ -178,7 +178,7 @@ impl<'t> LexStream<'t> { /// `LexStream.slice(..10)` /// `LexStream.slice(1..)` /// - pub fn slice>(&self, range: R) -> Option<&'t str> { + pub fn slice>(&self, range: R) -> Option<&str> { // Sketchy downcast let start = match range.start_bound() { Bound::Included(&start) => start, @@ -192,7 +192,7 @@ impl<'t> LexStream<'t> { }; self.source.get(start..end) } - pub fn slice_from_cursor(&self) -> Option<&'t str> { + pub fn slice_from_cursor(&self) -> Option<&str> { self.slice(self.cursor..) } pub fn in_brc_grp(&self) -> bool { @@ -216,7 +216,7 @@ impl<'t> LexStream<'t> { self.flags &= !LexFlags::NEXT_IS_CMD; } } - pub fn read_redir(&mut self) -> Option>> { + pub fn read_redir(&mut self) -> Option> { assert!(self.cursor <= self.source.len()); let slice = self.slice(self.cursor..)?; let mut pos = self.cursor; @@ -248,7 +248,7 @@ impl<'t> LexStream<'t> { ShErr::full( ShErrKind::ParseErr, "Invalid redirection", - Span::new(self.cursor..pos, self.source).into() + Span::new(self.cursor..pos, self.source.clone()).into() ) )); } else { @@ -294,9 +294,9 @@ impl<'t> LexStream<'t> { self.cursor = pos; Some(Ok(tk)) } - pub fn read_string(&mut self) -> ShResult> { + pub fn read_string(&mut self) -> ShResult { assert!(self.cursor <= self.source.len()); - let slice = self.slice_from_cursor().unwrap(); + let slice = self.slice_from_cursor().unwrap().to_string(); let mut pos = self.cursor; let mut chars = slice.chars(); let mut quote_pos = None; @@ -393,14 +393,14 @@ impl<'t> LexStream<'t> { self.cursor = pos; Ok(new_tk) } - pub fn get_token(&self, range: Range, class: TkRule) -> Tk<'t> { - let span = Span::new(range, self.source); + pub fn get_token(&self, range: Range, class: TkRule) -> Tk { + let span = Span::new(range, self.source.clone()); Tk::new(class, span) } } -impl<'t> Iterator for LexStream<'t> { - type Item = ShResult>; +impl Iterator for LexStream { + type Item = ShResult; fn next(&mut self) -> Option { assert!(self.cursor <= self.source.len()); // We are at the end of the input @@ -432,7 +432,7 @@ impl<'t> Iterator for LexStream<'t> { let pos = self.cursor; if self.slice(pos..pos+2) == Some("\\\n") { self.cursor += 2; - } else if pos < self.source.len() && is_field_sep(get_char(self.source, pos).unwrap()) { + } else if pos < self.source.len() && is_field_sep(get_char(&self.source, pos).unwrap()) { self.cursor += 1; } else { break @@ -443,13 +443,13 @@ impl<'t> Iterator for LexStream<'t> { return None } - let token = match get_char(self.source, self.cursor).unwrap() { + let token = match get_char(&self.source, self.cursor).unwrap() { '\r' | '\n' | ';' => { let ch_idx = self.cursor; self.cursor += 1; self.set_next_is_cmd(true); - while let Some(ch) = get_char(self.source, self.cursor) { + while let Some(ch) = get_char(&self.source, self.cursor) { if is_hard_sep(ch) { // Combine consecutive separators into one, including whitespace self.cursor += 1; } else { @@ -462,7 +462,7 @@ impl<'t> Iterator for LexStream<'t> { let ch_idx = self.cursor; self.cursor += 1; - while let Some(ch) = get_char(self.source, self.cursor) { + while let Some(ch) = get_char(&self.source, self.cursor) { self.cursor += 1; if ch == '\n' { break @@ -476,10 +476,10 @@ impl<'t> Iterator for LexStream<'t> { self.cursor += 1; self.set_next_is_cmd(true); - let tk_type = if let Some('|') = get_char(self.source, self.cursor) { + let tk_type = if let Some('|') = get_char(&self.source, self.cursor) { self.cursor += 1; TkRule::Or - } else if let Some('&') = get_char(self.source, self.cursor) { + } else if let Some('&') = get_char(&self.source, self.cursor) { self.cursor += 1; TkRule::ErrPipe } else { @@ -493,7 +493,7 @@ impl<'t> Iterator for LexStream<'t> { self.cursor += 1; self.set_next_is_cmd(true); - let tk_type = if let Some('&') = get_char(self.source, self.cursor) { + let tk_type = if let Some('&') = get_char(&self.source, self.cursor) { self.cursor += 1; TkRule::And } else { diff --git a/src/parse/mod.rs b/src/parse/mod.rs index 67c22ff..84fa59b 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -2,7 +2,7 @@ use std::str::FromStr; use bitflags::bitflags; use fmt::Display; -use lex::{Span, Tk, TkFlags, TkRule}; +use lex::{LexFlags, LexStream, Span, Tk, TkFlags, TkRule}; use crate::{libsh::{error::{ShErr, ShErrKind, ShResult}, utils::TkVecUtils}, prelude::*, procio::IoMode}; @@ -15,30 +15,78 @@ pub mod execute; /// * If the match fails, execution continues. /// * If the match succeeds, the matched node is returned. macro_rules! try_match { - ($expr:expr) => { - if let Some(node) = $expr { - return Ok(Some(node)) - } - }; + ($expr:expr) => { + if let Some(node) = $expr { + return Ok(Some(node)) + } + }; +} + +/// The parsed AST along with the source input it parsed +/// +/// Uses Rc instead of &str because the reference has to stay alive while errors are propagated upwards +/// The string also has to stay alive in the case of pre-parsed shell function nodes, which live in the logic table +/// Using &str for this use-case dramatically overcomplicates the code +#[derive(Clone,Debug)] +pub struct ParsedSrc { + pub src: Rc, + pub ast: Ast +} + +impl ParsedSrc { + pub fn new(src: Rc) -> Self { + Self { src, ast: Ast::new(vec![]) } + } + pub fn parse_src(&mut self) -> ShResult<()> { + let mut tokens = vec![]; + for token in LexStream::new(self.src.clone(), LexFlags::empty()) { + tokens.push(token?); + } + + let mut nodes = vec![]; + for result in ParseStream::new(tokens) { + nodes.push(result?); + } + *self.ast.tree_mut() = nodes; + Ok(()) + } + pub fn extract_nodes(&mut self) -> Vec { + mem::take(self.ast.tree_mut()) + } } #[derive(Clone,Debug)] -pub struct Node<'t> { - pub class: NdRule<'t>, - pub flags: NdFlags, - pub redirs: Vec, - pub tokens: Vec>, +pub struct Ast(Vec); + +impl Ast { + pub fn new(tree: Vec) -> Self { + Self(tree) + } + pub fn into_inner(self) -> Vec { + self.0 + } + pub fn tree_mut(&mut self) -> &mut Vec { + &mut self.0 + } } -impl<'t> Node<'t> { - pub fn get_command(&'t self) -> Option<&'t Tk<'t>> { +#[derive(Clone,Debug)] +pub struct Node { + pub class: NdRule, + pub flags: NdFlags, + pub redirs: Vec, + pub tokens: Vec, +} + +impl Node { + pub fn get_command(&self) -> Option<&Tk> { let NdRule::Command { assignments: _, argv } = &self.class else { return None }; let command = argv.iter().find(|tk| tk.flags.contains(TkFlags::IS_CMD))?; Some(command) } - pub fn get_span(&'t self) -> Span<'t> { + pub fn get_span(&self) -> Span { let Some(first_tk) = self.tokens.first() else { unreachable!() }; @@ -187,15 +235,15 @@ pub enum RedirType { } #[derive(Clone,Debug)] -pub struct CondNode<'t> { - pub cond: Box>, - pub body: Vec> +pub struct CondNode { + pub cond: Box, + pub body: Vec } #[derive(Clone,Debug)] -pub struct CaseNode<'t> { - pub pattern: Tk<'t>, - pub body: Vec> +pub struct CaseNode { + pub pattern: Tk, + pub body: Vec } #[derive(Clone,Copy,PartialEq,Debug)] @@ -206,8 +254,8 @@ pub enum ConjunctOp { } #[derive(Clone,Debug)] -pub struct ConjunctNode<'t> { - pub cmd: Box>, +pub struct ConjunctNode { + pub cmd: Box, pub operator: ConjunctOp } @@ -229,7 +277,7 @@ impl FromStr for LoopKind { } impl Display for LoopKind { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { LoopKind::While => write!(f,"while"), LoopKind::Until => write!(f,"until") @@ -247,22 +295,22 @@ pub enum AssignKind { } #[derive(Clone,Debug)] -pub enum NdRule<'t> { - IfNode { cond_nodes: Vec>, else_block: Vec> }, - LoopNode { kind: LoopKind, cond_node: CondNode<'t> }, - ForNode { vars: Vec>, arr: Vec>, body: Vec> }, - CaseNode { pattern: Tk<'t>, case_blocks: Vec> }, - Command { assignments: Vec>, argv: Vec> }, - Pipeline { cmds: Vec>, pipe_err: bool }, - Conjunction { elements: Vec> }, - Assignment { kind: AssignKind, var: Tk<'t>, val: Tk<'t> }, - BraceGrp { body: Vec> }, - FuncDef { name: Tk<'t>, body: Box> } +pub enum NdRule { + IfNode { cond_nodes: Vec, else_block: Vec }, + LoopNode { kind: LoopKind, cond_node: CondNode }, + ForNode { vars: Vec, arr: Vec, body: Vec }, + CaseNode { pattern: Tk, case_blocks: Vec }, + Command { assignments: Vec, argv: Vec }, + Pipeline { cmds: Vec, pipe_err: bool }, + Conjunction { elements: Vec }, + Assignment { kind: AssignKind, var: Tk, val: Tk }, + BraceGrp { body: Vec }, + FuncDef { name: Tk, body: Box } } #[derive(Debug)] -pub struct ParseStream<'t> { - pub tokens: Vec>, +pub struct ParseStream { + pub tokens: Vec, pub flags: ParseFlags } @@ -273,8 +321,8 @@ bitflags! { } } -impl<'t> ParseStream<'t> { - pub fn new(tokens: Vec>) -> Self { +impl ParseStream { + pub fn new(tokens: Vec) -> Self { Self { tokens, flags: ParseFlags::empty() } } fn next_tk_class(&self) -> &TkRule { @@ -284,10 +332,10 @@ impl<'t> ParseStream<'t> { &TkRule::Null } } - fn peek_tk(&self) -> Option<&Tk<'t>> { + fn peek_tk(&self) -> Option<&Tk> { self.tokens.first() } - fn next_tk(&mut self) -> Option> { + fn next_tk(&mut self) -> Option { if !self.tokens.is_empty() { if *self.next_tk_class() == TkRule::EOI { return None @@ -306,20 +354,20 @@ impl<'t> ParseStream<'t> { /// fi /// ``` /// are valid syntax - fn catch_separator(&mut self, node_tks: &mut Vec>) { + fn catch_separator(&mut self, node_tks: &mut Vec) { if *self.next_tk_class() == TkRule::Sep { node_tks.push(self.next_tk().unwrap()); } } - fn assert_separator(&mut self, node_tks: &mut Vec>) -> ShResult<()> { + fn assert_separator(&mut self, node_tks: &mut Vec) -> ShResult<()> { let next_class = self.next_tk_class(); match next_class { TkRule::EOI | - TkRule::Or | - TkRule::Bg | - TkRule::And | - TkRule::BraceGrpEnd | - TkRule::Pipe => Ok(()), + TkRule::Or | + TkRule::Bg | + TkRule::And | + TkRule::BraceGrpEnd | + TkRule::Pipe => Ok(()), TkRule::Sep => { if let Some(tk) = self.next_tk() { @@ -352,7 +400,7 @@ impl<'t> ParseStream<'t> { assert!(num_consumed <= self.tokens.len()); self.tokens = self.tokens[num_consumed..].to_vec(); } - fn parse_cmd_list(&mut self) -> ShResult>> { + fn parse_cmd_list(&mut self) -> ShResult> { let mut elements = vec![]; let mut node_tks = vec![]; @@ -390,7 +438,7 @@ impl<'t> ParseStream<'t> { /// 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 left-recursion issues in self.parse_pipeline() - fn parse_block(&mut self, check_pipelines: bool) -> ShResult>> { + fn parse_block(&mut self, check_pipelines: bool) -> ShResult> { try_match!(self.parse_func_def()?); try_match!(self.parse_brc_grp(false /* from_func_def */)?); try_match!(self.parse_loop()?); @@ -402,7 +450,7 @@ impl<'t> ParseStream<'t> { } Ok(None) } - fn parse_func_def(&mut self) -> ShResult>> { + fn parse_func_def(&mut self) -> ShResult> { let mut node_tks: Vec = vec![]; let name; let body; @@ -418,7 +466,7 @@ impl<'t> ParseStream<'t> { return Err(parse_err_full( "Expected a brace group after function name", &node_tks.get_span().unwrap() - ) + ) ) }; body = Box::new(brc_grp); @@ -429,11 +477,10 @@ impl<'t> ParseStream<'t> { redirs: vec![], tokens: node_tks }; - flog!(DEBUG,node); Ok(Some(node)) } - fn parse_brc_grp(&mut self, from_func_def: bool) -> ShResult>> { + fn parse_brc_grp(&mut self, from_func_def: bool) -> ShResult> { let mut node_tks: Vec = vec![]; let mut body: Vec = vec![]; let mut redirs: Vec = vec![]; @@ -456,7 +503,7 @@ impl<'t> ParseStream<'t> { return Err(parse_err_full( "Expected a closing brace for this brace group", &node_tks.get_span().unwrap() - ) + ) ) } } @@ -507,10 +554,9 @@ impl<'t> ParseStream<'t> { redirs, tokens: node_tks }; - flog!(DEBUG, node); Ok(Some(node)) } - fn parse_if(&mut self) -> ShResult>> { + fn parse_if(&mut self) -> ShResult> { // Needs at last one 'if-then', // Any number of 'elif-then', // Zero or one 'else' @@ -532,16 +578,16 @@ impl<'t> ParseStream<'t> { }; let Some(cond) = self.parse_block(true)? else { return Err(parse_err_full( - &format!("Expected an expression after '{prefix_keywrd}'"), - &node_tks.get_span().unwrap() + &format!("Expected an expression after '{prefix_keywrd}'"), + &node_tks.get_span().unwrap() )); }; node_tks.extend(cond.tokens.clone()); if !self.check_keyword("then") || !self.next_tk_is_some() { return Err(parse_err_full( - &format!("Expected 'then' after '{prefix_keywrd}' condition"), - &node_tks.get_span().unwrap() + &format!("Expected 'then' after '{prefix_keywrd}' condition"), + &node_tks.get_span().unwrap() )); } node_tks.push(self.next_tk().unwrap()); @@ -554,8 +600,8 @@ impl<'t> ParseStream<'t> { } if body_blocks.is_empty() { return Err(parse_err_full( - "Expected an expression after 'then'", - &node_tks.get_span().unwrap() + "Expected an expression after 'then'", + &node_tks.get_span().unwrap() )); }; let cond_node = CondNode { cond: Box::new(cond), body: body_blocks }; @@ -577,8 +623,8 @@ impl<'t> ParseStream<'t> { } if else_block.is_empty() { return Err(parse_err_full( - "Expected an expression after 'else'", - &node_tks.get_span().unwrap() + "Expected an expression after 'else'", + &node_tks.get_span().unwrap() )); } } @@ -639,10 +685,10 @@ impl<'t> ParseStream<'t> { }; Ok(Some(node)) } - fn parse_loop(&mut self) -> ShResult>> { + fn parse_loop(&mut self) -> ShResult> { // Requires a single CondNode and a LoopKind let loop_kind: LoopKind; - let cond_node: CondNode<'t>; + let cond_node: CondNode; let mut node_tks = vec![]; if (!self.check_keyword("while") && !self.check_keyword("until")) || !self.next_tk_is_some() { @@ -653,57 +699,57 @@ impl<'t> ParseStream<'t> { .as_str() .parse() // LoopKind implements FromStr .unwrap(); - node_tks.push(loop_tk); - self.catch_separator(&mut node_tks); + node_tks.push(loop_tk); + self.catch_separator(&mut node_tks); - let Some(cond) = self.parse_block(true)? else { - return Err(parse_err_full( - &format!("Expected an expression after '{loop_kind}'"), // It also implements Display - &node_tks.get_span().unwrap() - )) - }; - node_tks.extend(cond.tokens.clone()); + let Some(cond) = self.parse_block(true)? else { + return Err(parse_err_full( + &format!("Expected an expression after '{loop_kind}'"), // It also implements Display + &node_tks.get_span().unwrap() + )) + }; + node_tks.extend(cond.tokens.clone()); - if !self.check_keyword("do") || !self.next_tk_is_some() { - return Err(parse_err_full( - "Expected 'do' after loop condition", - &node_tks.get_span().unwrap() - )) - } - node_tks.push(self.next_tk().unwrap()); - self.catch_separator(&mut node_tks); + if !self.check_keyword("do") || !self.next_tk_is_some() { + return Err(parse_err_full( + "Expected 'do' after loop condition", + &node_tks.get_span().unwrap() + )) + } + node_tks.push(self.next_tk().unwrap()); + self.catch_separator(&mut node_tks); - let mut body = vec![]; - while let Some(block) = self.parse_block(true)? { - node_tks.extend(block.tokens.clone()); - body.push(block); - } - if body.is_empty() { - return Err(parse_err_full( - "Expected an expression after 'do'", - &node_tks.get_span().unwrap() - )) - }; + let mut body = vec![]; + while let Some(block) = self.parse_block(true)? { + node_tks.extend(block.tokens.clone()); + body.push(block); + } + if body.is_empty() { + return Err(parse_err_full( + "Expected an expression after 'do'", + &node_tks.get_span().unwrap() + )) + }; - if !self.check_keyword("done") || !self.next_tk_is_some() { - return Err(parse_err_full( - "Expected 'done' after loop body", - &node_tks.get_span().unwrap() - )) - } - node_tks.push(self.next_tk().unwrap()); - self.assert_separator(&mut node_tks)?; + if !self.check_keyword("done") || !self.next_tk_is_some() { + return Err(parse_err_full( + "Expected 'done' after loop body", + &node_tks.get_span().unwrap() + )) + } + node_tks.push(self.next_tk().unwrap()); + self.assert_separator(&mut node_tks)?; - cond_node = CondNode { cond: Box::new(cond), body }; - let loop_node = Node { - class: NdRule::LoopNode { kind: loop_kind, cond_node }, - flags: NdFlags::empty(), - redirs: vec![], - tokens: node_tks - }; - Ok(Some(loop_node)) + cond_node = CondNode { cond: Box::new(cond), body }; + let loop_node = Node { + class: NdRule::LoopNode { kind: loop_kind, cond_node }, + flags: NdFlags::empty(), + redirs: vec![], + tokens: node_tks + }; + Ok(Some(loop_node)) } - fn parse_pipeline(&mut self) -> ShResult>> { + fn parse_pipeline(&mut self) -> ShResult> { let mut cmds = vec![]; let mut node_tks = vec![]; while let Some(cmd) = self.parse_block(false)? { @@ -732,7 +778,7 @@ impl<'t> ParseStream<'t> { })) } } - fn parse_cmd(&mut self) -> ShResult>> { + fn parse_cmd(&mut self) -> ShResult> { let tk_slice = self.tokens.as_slice(); let mut tk_iter = tk_slice.iter(); let mut node_tks = vec![]; @@ -804,8 +850,8 @@ impl<'t> ParseStream<'t> { let Ok(file) = get_redir_file(redir_class, pathbuf) else { self.flags |= ParseFlags::ERROR; return Err(parse_err_full( - "Error opening file for redirection", - &path_tk.span + "Error opening file for redirection", + &path_tk.span )); }; @@ -827,7 +873,7 @@ impl<'t> ParseStream<'t> { redirs, })) } - fn parse_assignment(&self, token: &Tk<'t>) -> Option> { + fn parse_assignment(&self, token: &Tk) -> Option { let mut chars = token.span.as_str().chars(); let mut var_name = String::new(); let mut name_range = token.span.start..token.span.start; @@ -930,8 +976,8 @@ impl<'t> ParseStream<'t> { } } -impl<'t> Iterator for ParseStream<'t> { - type Item = ShResult>; +impl Iterator for ParseStream { + type Item = ShResult; fn next(&mut self) -> Option { // Empty token vector or only SOI/EOI tokens, nothing to do if self.tokens.is_empty() || self.tokens.len() == 2 { @@ -960,7 +1006,7 @@ impl<'t> Iterator for ParseStream<'t> { } } -fn node_is_punctuated<'t>(tokens: &Vec) -> bool { +fn node_is_punctuated(tokens: &Vec) -> bool { tokens.last().is_some_and(|tk| { matches!(tk.class, TkRule::Sep) }) @@ -992,7 +1038,7 @@ fn get_redir_file(class: RedirType, path: PathBuf) -> ShResult { Ok(result?) } -fn parse_err_full<'t>(reason: &str, blame: &Span<'t>) -> ShErr { +fn parse_err_full(reason: &str, blame: &Span) -> ShErr { ShErr::full( ShErrKind::ParseErr, reason, @@ -1000,9 +1046,9 @@ fn parse_err_full<'t>(reason: &str, blame: &Span<'t>) -> ShErr { ) } -fn is_func_name<'t>(tk: Option<&Tk<'t>>) -> bool { +fn is_func_name(tk: Option<&Tk>) -> bool { tk.is_some_and(|tk| { tk.flags.contains(TkFlags::KEYWORD) && - (tk.span.as_str().ends_with("()") && !tk.span.as_str().ends_with("\\()")) + (tk.span.as_str().ends_with("()") && !tk.span.as_str().ends_with("\\()")) }) } diff --git a/src/procio.rs b/src/procio.rs index 51a9971..7fc0123 100644 --- a/src/procio.rs +++ b/src/procio.rs @@ -137,7 +137,7 @@ impl DerefMut for IoFrame { /// 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)] +#[derive(Debug,Default)] pub struct IoStack { stack: Vec, } diff --git a/src/prompt/mod.rs b/src/prompt/mod.rs index 5608d47..1142226 100644 --- a/src/prompt/mod.rs +++ b/src/prompt/mod.rs @@ -6,9 +6,9 @@ use std::path::Path; use readline::FernReadline; use rustyline::{error::ReadlineError, history::FileHistory, Editor}; -use crate::{libsh::{error::ShResult, term::{Style, Styled}}, prelude::*}; +use crate::{expand::expand_prompt, libsh::{error::ShResult, term::{Style, Styled}}, prelude::*}; -fn init_rl<'s>() -> ShResult> { +fn init_rl() -> ShResult> { let rl = FernReadline::new(); let mut editor = Editor::new()?; editor.set_helper(Some(rl)); @@ -16,15 +16,35 @@ fn init_rl<'s>() -> ShResult> { Ok(editor) } -pub fn read_line<'s>() -> ShResult { +fn get_prompt() -> ShResult { + let Ok(prompt) = env::var("PS1") else { + return Ok("$ ".styled(Style::Green | Style::Bold)) + }; + + Ok(format!("\n{}",expand_prompt(&prompt)?)) +} + +fn get_hist_path() -> ShResult { + if let Ok(path) = env::var("FERN_HIST") { + Ok(PathBuf::from(path)) + } else { + let home = env::var("HOME")?; + Ok(PathBuf::from(format!("{home}/.fernhist"))) + } + +} + +pub fn read_line() -> ShResult { assert!(isatty(STDIN_FILENO).unwrap()); let mut editor = init_rl()?; - let prompt = "$ ".styled(Style::Green | Style::Bold); + let prompt = get_prompt()?; match editor.readline(&prompt) { Ok(line) => { if !line.is_empty() { + let hist_path = get_hist_path()?; + flog!(DEBUG, hist_path); editor.add_history_entry(&line)?; - editor.save_history(&Path::new("/home/pagedmov/.fernhist"))?; + editor.save_history(&hist_path)?; } Ok(line) } diff --git a/src/prompt/readline.rs b/src/prompt/readline.rs index e96b13d..fee404f 100644 --- a/src/prompt/readline.rs +++ b/src/prompt/readline.rs @@ -3,6 +3,7 @@ use std::borrow::Cow; use rustyline::{completion::Completer, highlight::Highlighter, hint::{Hint, Hinter}, validate::{ValidationResult, Validator}, Helper}; use crate::{libsh::term::{Style, Styled}, parse::{lex::{LexFlags, LexStream}, ParseStream}}; +use crate::prelude::*; pub struct FernReadline { } @@ -67,7 +68,7 @@ impl Validator for FernReadline { fn validate(&self, ctx: &mut rustyline::validate::ValidationContext) -> rustyline::Result { return Ok(ValidationResult::Valid(None)); let mut tokens = vec![]; - let tk_stream = LexStream::new(ctx.input(), LexFlags::empty()); + let tk_stream = LexStream::new(Rc::new(ctx.input().to_string()), LexFlags::empty()); for tk in tk_stream { if tk.is_err() { return Ok(ValidationResult::Incomplete) diff --git a/src/state.rs b/src/state.rs index fc398ab..ef4014b 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,23 +1,59 @@ -use std::{cell::RefCell, collections::{HashMap, VecDeque}, ops::Range, sync::{LazyLock, RwLock, RwLockReadGuard, RwLockWriteGuard}}; +use std::{collections::{HashMap, VecDeque}, ops::{Deref, Range}, sync::{LazyLock, RwLock, RwLockReadGuard, RwLockWriteGuard}, time::Duration}; -use crate::{exec_input, jobs::JobTab, libsh::{error::ShResult, utils::VecDequeExt}, parse::{lex::{get_char, Tk}, Node}, prelude::*}; +use nix::unistd::{gethostname, getppid, User}; + +use crate::{exec_input, jobs::JobTab, libsh::{error::ShResult, utils::VecDequeExt}, parse::{lex::{get_char, Tk}, ConjunctNode, NdRule, Node, ParsedSrc}, prelude::*}; pub static JOB_TABLE: LazyLock> = LazyLock::new(|| RwLock::new(JobTab::new())); pub static VAR_TABLE: LazyLock> = LazyLock::new(|| RwLock::new(VarTab::new())); -pub static LOGIC_TABLE: LazyLock> = LazyLock::new(|| RwLock::new(LogTab::new())); +pub static META_TABLE: LazyLock> = LazyLock::new(|| RwLock::new(MetaTab::new())); + thread_local! { - pub static LAST_INPUT: RefCell = RefCell::new(String::new()); + pub static LOGIC_TABLE: LazyLock> = LazyLock::new(|| RwLock::new(LogTab::new())); +} + +/// A shell function +/// +/// Consists of the BraceGrp Node and the stored ParsedSrc that the node refers to +/// The Node must be stored with the ParsedSrc because the tokens of the node contain an Rc +/// Which refers to the String held in ParsedSrc +/// +/// Can be dereferenced to pull out the wrapped Node +#[derive(Clone,Debug)] +pub struct ShFunc(Node); + +impl ShFunc { + pub fn new(mut src: ParsedSrc) -> Self { + let body = Self::extract_brc_grp_hack(src.extract_nodes()); + Self(body) + } + fn extract_brc_grp_hack(mut tree: Vec) -> Node { + // FIXME: find a better way to do this + let conjunction = tree.pop().unwrap(); + let NdRule::Conjunction { mut elements } = conjunction.class else { + unreachable!() + }; + let conjunct_node = elements.pop().unwrap(); + let ConjunctNode { cmd, operator: _ } = conjunct_node; + *cmd + } +} + +impl Deref for ShFunc { + type Target = Node; + fn deref(&self) -> &Self::Target { + &self.0 + } } /// The logic table for the shell /// /// Contains aliases and functions pub struct LogTab { - // TODO: Find a way to store actual owned nodes instead of strings that must be re-parsed - functions: HashMap, + functions: HashMap, aliases: HashMap } @@ -25,10 +61,10 @@ impl LogTab { pub fn new() -> Self { Self { functions: HashMap::new(), aliases: HashMap::new() } } - pub fn insert_func(&mut self, name: &str, body: &str) { - self.functions.insert(name.into(), body.into()); + pub fn insert_func(&mut self, name: &str, src: ShFunc) { + self.functions.insert(name.into(), src); } - pub fn get_func(&self, name: &str) -> Option { + pub fn get_func(&self, name: &str) -> Option { self.functions.get(name).cloned() } pub fn insert_alias(&mut self, name: &str, body: &str) { @@ -39,6 +75,7 @@ impl LogTab { } } +#[derive(Clone)] pub struct VarTab { vars: HashMap, params: HashMap, @@ -49,6 +86,7 @@ impl VarTab { pub fn new() -> Self { let vars = HashMap::new(); let params = Self::init_params(); + Self::init_env(); let mut var_tab = Self { vars, params, sh_argv: VecDeque::new() }; var_tab.init_sh_argv(); var_tab @@ -62,6 +100,51 @@ impl VarTab { params.insert('!', "".into()); // PID of the last background job (if any) params } + fn init_env() { + let pathbuf_to_string = |pb: Result| pb.unwrap_or_default().to_string_lossy().to_string(); + // First, inherit any env vars from the parent process + let term = { + if isatty(1).unwrap() { + if let Ok(term) = std::env::var("TERM") { + term + } else { + "linux".to_string() + } + } else { + "xterm-256color".to_string() + } + }; + let home; + let username; + let uid; + if let Some(user) = User::from_uid(nix::unistd::Uid::current()).ok().flatten() { + home = user.dir; + username = user.name; + uid = user.uid; + } else { + home = PathBuf::new(); + username = "unknown".into(); + uid = 0.into(); + } + let home = pathbuf_to_string(Ok(home)); + 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("UID", uid.to_string()); + env::set_var("PPID", getppid().to_string()); + env::set_var("TMPDIR", "/tmp"); + env::set_var("TERM", term); + env::set_var("LANG", "en_US.UTF-8"); + env::set_var("USER", username.clone()); + env::set_var("LOGNAME", username); + env::set_var("PWD", pathbuf_to_string(std::env::current_dir())); + env::set_var("OLDPWD", pathbuf_to_string(std::env::current_dir())); + env::set_var("HOME", home.clone()); + env::set_var("SHELL", pathbuf_to_string(std::env::current_exe())); + env::set_var("FERN_HIST",format!("{}/.fern_hist",home)); + env::set_var("FERN_RC",format!("{}/.fernrc",home)); + } pub fn init_sh_argv(&mut self) { for arg in env::args() { self.bpush_arg(arg); @@ -74,11 +157,15 @@ impl VarTab { &mut self.sh_argv } pub fn clear_args(&mut self) { - self.sh_argv.clear() + self.sh_argv.clear(); + // Push the current exe again + // This makes sure that $0 is always the current shell, no matter what + // It also updates the arg parameters '@' and '#' as well + self.bpush_arg(env::current_exe().unwrap().to_str().unwrap().to_string()); } fn update_arg_params(&mut self) { - self.set_param('@', &self.sh_argv.clone().to_vec().join(" ")); - self.set_param('#', &self.sh_argv.len().to_string()); + self.set_param('@', &self.sh_argv.clone().to_vec()[1..].join(" ")); + self.set_param('#', &(self.sh_argv.len() - 1).to_string()); } /// Push an arg to the front of the arg deque pub fn fpush_arg(&mut self, arg: String) { @@ -139,7 +226,8 @@ impl VarTab { .to_string() .parse::() .unwrap(); - return self.sh_argv.get(argv_idx).map(|s| s.to_string()).unwrap_or_default() + let arg = self.sh_argv.get(argv_idx).map(|s| s.to_string()).unwrap_or_default(); + arg } else if param == '?' { self.params.get(¶m).map(|s| s.to_string()).unwrap_or("0".into()) } else { @@ -148,6 +236,25 @@ impl VarTab { } } +/// A table of metadata for the shell +pub struct MetaTab { + runtime_start: Option +} + +impl MetaTab { + pub fn new() -> Self { + Self { runtime_start: None } + } + pub fn start_timer(&mut self) { + self.runtime_start = Some(Instant::now()); + } + pub fn stop_timer(&mut self) -> Option { + self.runtime_start + .take() // runtime_start returns to None + .map(|start| start.elapsed()) // return the duration, if any + } +} + /// Read from the job table pub fn read_jobs) -> T>(f: F) -> T { let lock = JOB_TABLE.read().unwrap(); @@ -172,30 +279,30 @@ pub fn write_vars) -> T>(f: F) -> T { f(lock) } +pub fn read_meta) -> T>(f: F) -> T { + let lock = META_TABLE.read().unwrap(); + f(lock) +} + +/// Write to the variable table +pub fn write_meta) -> T>(f: F) -> T { + let lock = &mut META_TABLE.write().unwrap(); + f(lock) +} + /// Read from the logic table pub fn read_logic) -> T>(f: F) -> T { - let lock = LOGIC_TABLE.read().unwrap(); - f(lock) + LOGIC_TABLE.with(|log| { + let lock = log.read().unwrap(); + f(lock) + }) } /// Write to the logic table pub fn write_logic) -> T>(f: F) -> T { - let lock = &mut LOGIC_TABLE.write().unwrap(); - f(lock) -} - -pub fn set_last_input(input: &str) { - LAST_INPUT.with(|input_ref| { - let mut last_input = input_ref.borrow_mut(); - last_input.clear(); - last_input.push_str(input); - }) -} - -pub fn slice_last_input(range: Range) -> String { - LAST_INPUT.with(|input_ref| { - let input = input_ref.borrow(); - input[range].to_string() + LOGIC_TABLE.with(|log| { + let lock = &mut log.write().unwrap(); + f(lock) }) } @@ -213,6 +320,6 @@ pub fn source_file(path: PathBuf) -> ShResult<()> { let mut buf = String::new(); file.read_to_string(&mut buf)?; - exec_input(&buf, None)?; + exec_input(buf)?; Ok(()) }