From cd216ef1cc3238a6661eb37588f59399717ea8d0 Mon Sep 17 00:00:00 2001 From: pagedmov Date: Sun, 2 Mar 2025 22:49:36 -0500 Subject: [PATCH] Implemented functions and aliases --- src/builtin/alias.rs | 19 +++++++++ src/builtin/jobctl.rs | 10 ++--- src/builtin/mod.rs | 6 ++- src/execute.rs | 90 ++++++++++++++++++++++++++++++++++++--- src/expand/alias.rs | 75 ++++++++++++++++++++++++++++++++ src/expand/expand_vars.rs | 16 ++++--- src/expand/mod.rs | 36 ++++++++++++++++ src/expand/tilde.rs | 9 ++-- src/libsh/error.rs | 10 ++--- src/libsh/utils.rs | 57 +++++++++++-------------- src/main.rs | 7 ++- src/parse/lex.rs | 48 +++++++++++++++------ src/parse/parse.rs | 47 ++++++++++++++++++++ src/prelude.rs | 6 +++ src/prompt/highlight.rs | 42 ++++++++++++++---- src/prompt/mod.rs | 17 ++++++-- src/prompt/readline.rs | 3 +- src/shellenv/jobs.rs | 11 ++--- src/shellenv/logic.rs | 6 +++ src/shellenv/vars.rs | 4 +- 20 files changed, 424 insertions(+), 95 deletions(-) create mode 100644 src/builtin/alias.rs create mode 100644 src/expand/alias.rs diff --git a/src/builtin/alias.rs b/src/builtin/alias.rs new file mode 100644 index 0000000..f7052b0 --- /dev/null +++ b/src/builtin/alias.rs @@ -0,0 +1,19 @@ +use crate::prelude::*; + +pub fn alias(node: Node, shenv: &mut ShEnv) -> ShResult<()> { + let rule = node.into_rule(); + if let NdRule::Command { argv, redirs: _ } = rule { + let argv = argv.drop_first(); + let mut argv_iter = argv.into_iter(); + while let Some(arg) = argv_iter.next() { + let arg_raw = arg.to_string(); + if let Some((alias,body)) = arg_raw.split_once('=') { + let clean_body = trim_quotes(&body); + shenv.logic_mut().set_alias(alias, &clean_body); + } else { + return Err(ShErr::full(ShErrKind::SyntaxErr, "Expected an assignment in alias args", arg.span().clone())) + } + } + } else { unreachable!() } + Ok(()) +} diff --git a/src/builtin/jobctl.rs b/src/builtin/jobctl.rs index b80bbc4..e4dfa51 100644 --- a/src/builtin/jobctl.rs +++ b/src/builtin/jobctl.rs @@ -22,7 +22,7 @@ pub fn continue_job(node: Node, shenv: &mut ShEnv, fg: bool) -> ShResult<()> { let curr_job_id = if let Some(id) = read_jobs(|j| j.curr_job()) { id } else { - return Err(ShErr::full(ShErrKind::ExecFail, "No jobs found".into(), blame)) + return Err(ShErr::full(ShErrKind::ExecFail, "No jobs found", blame)) }; let tabid = match argv_s.next() { @@ -66,7 +66,7 @@ fn parse_job_id(arg: &str, blame: Span) -> ShResult { }); match result { Some(id) => Ok(id), - None => Err(ShErr::full(ShErrKind::InternalErr,"Found a job but no table id in parse_job_id()".into(),blame)) + None => Err(ShErr::full(ShErrKind::InternalErr,"Found a job but no table id in parse_job_id()",blame)) } } } else if arg.chars().all(|ch| ch.is_ascii_digit()) { @@ -86,7 +86,7 @@ fn parse_job_id(arg: &str, blame: Span) -> ShResult { match result { Some(id) => Ok(id), - None => Err(ShErr::full(ShErrKind::InternalErr,"Found a job but no table id in parse_job_id()".into(),blame)) + None => Err(ShErr::full(ShErrKind::InternalErr,"Found a job but no table id in parse_job_id()",blame)) } } else { Err(ShErr::full(ShErrKind::SyntaxErr,format!("Invalid fd arg: {}", arg),blame)) @@ -103,7 +103,7 @@ pub fn jobs(node: Node, shenv: &mut ShEnv) -> ShResult<()> { let arg_s = arg.to_string(); let mut chars = arg_s.chars().peekable(); if chars.peek().is_none_or(|ch| *ch != '-') { - return Err(ShErr::full(ShErrKind::SyntaxErr, "Invalid flag in jobs call".into(), arg.span().clone())) + return Err(ShErr::full(ShErrKind::SyntaxErr, "Invalid flag in jobs call", arg.span().clone())) } chars.next(); while let Some(ch) = chars.next() { @@ -113,7 +113,7 @@ pub fn jobs(node: Node, shenv: &mut ShEnv) -> ShResult<()> { 'n' => JobCmdFlags::NEW_ONLY, 'r' => JobCmdFlags::RUNNING, 's' => JobCmdFlags::STOPPED, - _ => return Err(ShErr::full(ShErrKind::SyntaxErr, "Invalid flag in jobs call".into(), arg.span().clone())) + _ => return Err(ShErr::full(ShErrKind::SyntaxErr, "Invalid flag in jobs call", arg.span().clone())) }; flags |= flag diff --git a/src/builtin/mod.rs b/src/builtin/mod.rs index 15faee3..a7546c7 100644 --- a/src/builtin/mod.rs +++ b/src/builtin/mod.rs @@ -4,8 +4,9 @@ pub mod pwd; pub mod export; pub mod jobctl; pub mod read; +pub mod alias; -pub const BUILTINS: [&str;8] = [ +pub const BUILTINS: [&str;9] = [ "echo", "cd", "pwd", @@ -13,5 +14,6 @@ pub const BUILTINS: [&str;8] = [ "fg", "bg", "jobs", - "read" + "read", + "alias" ]; diff --git a/src/execute.rs b/src/execute.rs index a29b761..c4563dc 100644 --- a/src/execute.rs +++ b/src/execute.rs @@ -47,9 +47,9 @@ fn exec_list(list: Vec<(Option, Node)>, shenv: &mut ShEnv) -> ShResult } log!(TRACE, "{:?}", *cmd.rule()); match *cmd.rule() { - NdRule::Command {..} if cmd.flags().contains(NdFlag::BUILTIN) => exec_builtin(cmd,shenv).try_blame(span)?, - NdRule::Command {..} => exec_cmd(cmd,shenv).try_blame(span)?, + NdRule::Command {..} => dispatch_command(cmd, shenv).try_blame(span)?, NdRule::Subshell {..} => exec_subshell(cmd,shenv).try_blame(span)?, + NdRule::FuncDef {..} => exec_funcdef(cmd,shenv).try_blame(span)?, NdRule::Assignment {..} => exec_assignment(cmd,shenv).try_blame(span)?, NdRule::Pipeline {..} => exec_pipeline(cmd, shenv).try_blame(span)?, _ => unimplemented!() @@ -58,6 +58,83 @@ fn exec_list(list: Vec<(Option, Node)>, shenv: &mut ShEnv) -> ShResult Ok(()) } +fn dispatch_command(mut node: Node, shenv: &mut ShEnv) -> ShResult<()> { + let mut is_builtin = false; + let mut is_func = false; + let mut is_subsh = false; + if let NdRule::Command { ref mut argv, redirs: _ } = node.rule_mut() { + *argv = expand_argv(argv.to_vec(), shenv); + let cmd = argv.first().unwrap().to_string(); + if shenv.logic().get_function(&cmd).is_some() { + is_func = true; + } else if node.flags().contains(NdFlag::BUILTIN) { + is_builtin = true; + } + } else if let NdRule::Subshell { body: _, ref mut argv, redirs: _ } = node.rule_mut() { + *argv = expand_argv(argv.to_vec(), shenv); + is_subsh = true; + } else { unreachable!() } + + if is_builtin { + exec_builtin(node, shenv)?; + } else if is_func { + exec_func(node, shenv)?; + } else if is_subsh { + exec_subshell(node, shenv)?; + } else { + exec_cmd(node, shenv)?; + } + Ok(()) +} + +fn exec_func(node: Node, shenv: &mut ShEnv) -> ShResult<()> { + let rule = node.into_rule(); + if let NdRule::Command { argv, redirs } = rule { + let mut argv_iter = argv.into_iter(); + let func_name = argv_iter.next().unwrap().to_string(); + let body = shenv.logic().get_function(&func_name).unwrap().to_string(); + let snapshot = shenv.clone(); + shenv.vars_mut().reset_params(); + while let Some(arg) = argv_iter.next() { + shenv.vars_mut().bpush_arg(&arg.to_string()); + } + shenv.collect_redirs(redirs); + + let lex_input = Rc::new(body); + let tokens = Lexer::new(lex_input).lex(); + match Parser::new(tokens).parse() { + Ok(syn_tree) => { + match Executor::new(syn_tree, shenv).walk() { + Ok(_) => { /* yippee */ } + Err(e) => { + *shenv = snapshot; + return Err(e.into()) + } + } + } + Err(e) => { + *shenv = snapshot; + return Err(e.into()) + } + } + *shenv = snapshot; + } + Ok(()) +} + +fn exec_funcdef(node: Node, shenv: &mut ShEnv) -> ShResult<()> { + let rule = node.into_rule(); + if let NdRule::FuncDef { name, body } = rule { + let name_raw = name.to_string(); + let name = name_raw.trim_end_matches("()"); + let body_raw = body.to_string(); + let body = body_raw[1..body_raw.len() - 1].trim(); + + shenv.logic_mut().set_function(name, body); + } else { unreachable!() } + Ok(()) +} + fn exec_subshell(node: Node, shenv: &mut ShEnv) -> ShResult<()> { let snapshot = shenv.clone(); shenv.vars_mut().reset_params(); @@ -154,6 +231,7 @@ fn exec_builtin(node: Node, shenv: &mut ShEnv) -> ShResult<()> { "fg" => continue_job(node, shenv, true)?, "bg" => continue_job(node, shenv, false)?, "read" => read_builtin(node, shenv)?, + "alias" => alias(node, shenv)?, _ => unimplemented!("Have not yet implemented support for builtin `{}'",command) } log!(TRACE, "done"); @@ -212,6 +290,8 @@ fn exec_pipeline(node: Node, shenv: &mut ShEnv) -> ShResult<()> { if let NdRule::Command { argv, redirs: _ } = cmd.rule() { let cmd_name = argv.first().unwrap().span().get_slice().to_string(); cmd_names.push(cmd_name); + } else if let NdRule::Subshell {..} = cmd.rule() { + cmd_names.push("subshell".to_string()); } else { unimplemented!() } match unsafe { fork()? } { @@ -234,11 +314,7 @@ fn exec_pipeline(node: Node, shenv: &mut ShEnv) -> ShResult<()> { shenv.ctx_mut().push_rdr(rpipe_redir); } - if cmd.flags().contains(NdFlag::BUILTIN) { - exec_builtin(cmd, shenv).unwrap(); - } else { - exec_cmd(cmd, shenv).unwrap(); - } + dispatch_command(cmd, shenv)?; exit(0); } Parent { child } => { diff --git a/src/expand/alias.rs b/src/expand/alias.rs new file mode 100644 index 0000000..d0de8f7 --- /dev/null +++ b/src/expand/alias.rs @@ -0,0 +1,75 @@ +use crate::{parse::lex::SEPARATORS, prelude::*}; + +pub fn expand_aliases(input: &str, shenv: &mut ShEnv) -> Option { + let mut result = input.to_string(); + let mut expanded_aliases = Vec::new(); + let mut found_in_iteration = true; + + // Loop until no new alias expansion happens. + while found_in_iteration { + found_in_iteration = false; + let mut new_result = String::new(); + let mut chars = result.chars().peekable(); + let mut alias_cand = String::new(); + let mut is_cmd = true; + + while let Some(ch) = chars.next() { + match ch { + ';' | '\n' => { + new_result.push(ch); + is_cmd = true; + // Consume any extra whitespace or delimiters. + while let Some(&next_ch) = chars.peek() { + if matches!(next_ch, ' ' | '\t' | ';' | '\n') { + new_result.push(next_ch); + chars.next(); + } else { + break; + } + } + } + ' ' | '\t' => { + is_cmd = false; + new_result.push(ch); + } + _ if is_cmd => { + // Accumulate token characters. + alias_cand.push(ch); + while let Some(&next_ch) = chars.peek() { + if matches!(next_ch, ' ' | '\t' | ';' | '\n') { + break; + } else { + alias_cand.push(next_ch); + chars.next(); + } + } + // Check for an alias expansion. + if let Some(alias) = shenv.logic().get_alias(&alias_cand) { + // Only expand if we haven't already done so. + if !expanded_aliases.contains(&alias_cand) { + new_result.push_str(alias); + expanded_aliases.push(alias_cand.clone()); + found_in_iteration = true; + } else { + new_result.push_str(&alias_cand); + } + } else { + new_result.push_str(&alias_cand); + } + alias_cand.clear(); + } + _ => { + new_result.push(ch); + } + } + } + result = new_result; + log!(DEBUG, result); + } + + if expanded_aliases.is_empty() { + None + } else { + Some(result) + } +} diff --git a/src/expand/expand_vars.rs b/src/expand/expand_vars.rs index ba44e3f..4bad245 100644 --- a/src/expand/expand_vars.rs +++ b/src/expand/expand_vars.rs @@ -11,11 +11,11 @@ pub fn expand_var(var_sub: Token, shenv: &mut ShEnv) -> Vec { Lexer::new(value).lex() // Automatically handles word splitting for us } -pub fn expand_dquote(dquote: Token, shenv: &mut ShEnv) -> String { +pub fn expand_dquote(dquote: Token, shenv: &mut ShEnv) -> Token { let dquote_raw = dquote.to_string(); let mut result = String::new(); let mut var_name = String::new(); - let mut chars = dquote_raw.chars(); + let mut chars = dquote_raw.chars().peekable(); let mut in_brace = false; while let Some(ch) = chars.next() { @@ -25,11 +25,13 @@ pub fn expand_dquote(dquote: Token, shenv: &mut ShEnv) -> String { result.push(next_ch) } } - '"' => continue, '$' => { - while let Some(ch) = chars.next() { + while let Some(ch) = chars.peek() { + if *ch == '"' { + break + } + let ch = chars.next().unwrap(); match ch { - '"' => continue, '{' => { in_brace = true; } @@ -59,5 +61,7 @@ pub fn expand_dquote(dquote: Token, shenv: &mut ShEnv) -> String { } } - result + log!(DEBUG, result); + + Lexer::new(Rc::new(result)).lex().pop().unwrap_or(dquote) } diff --git a/src/expand/mod.rs b/src/expand/mod.rs index 0c572e2..7ac1897 100644 --- a/src/expand/mod.rs +++ b/src/expand/mod.rs @@ -1,2 +1,38 @@ pub mod expand_vars; pub mod tilde; +pub mod alias; + +use alias::expand_aliases; +use expand_vars::{expand_dquote, expand_var}; +use tilde::expand_tilde; + +use crate::prelude::*; + +pub fn expand_argv(argv: Vec, shenv: &mut ShEnv) -> Vec { + let mut processed = vec![]; + for arg in argv { + log!(DEBUG, arg); + log!(DEBUG, processed); + match arg.rule() { + TkRule::DQuote => { + let dquote_exp = expand_dquote(arg.clone(), shenv); + processed.push(dquote_exp); + } + TkRule::VarSub => { + let mut varsub_exp = expand_var(arg.clone(), shenv); + processed.append(&mut varsub_exp); + } + TkRule::TildeSub => { + let tilde_exp = expand_tilde(arg.clone()); + processed.push(tilde_exp); + } + _ => { + if arg.rule() != TkRule::Ident { + log!(WARN, "found this in expand_argv: {:?}", arg.rule()); + } + processed.push(arg.clone()) + } + } + } + processed +} diff --git a/src/expand/tilde.rs b/src/expand/tilde.rs index 0d38b45..b2bc60f 100644 --- a/src/expand/tilde.rs +++ b/src/expand/tilde.rs @@ -1,11 +1,14 @@ use crate::prelude::*; -pub fn expand_tilde(tilde_sub: Token) -> String { +pub fn expand_tilde(tilde_sub: Token) -> Token { let tilde_sub_raw = tilde_sub.to_string(); if tilde_sub_raw.starts_with('~') { let home = std::env::var("HOME").unwrap_or_default(); - tilde_sub_raw.replacen('~', &home, 1) + tilde_sub_raw.replacen('~', &home, 1); + let lex_input = Rc::new(tilde_sub_raw); + let mut tokens = Lexer::new(lex_input).lex(); + tokens.pop().unwrap_or(tilde_sub) } else { - tilde_sub_raw + tilde_sub } } diff --git a/src/libsh/error.rs b/src/libsh/error.rs index 7405c01..fabd417 100644 --- a/src/libsh/error.rs +++ b/src/libsh/error.rs @@ -89,14 +89,14 @@ pub enum ShErr { } impl ShErr { - pub fn simple(kind: ShErrKind, message: &str) -> Self { - Self::Simple { kind, message: message.to_string() } + pub fn simple>(kind: ShErrKind, message: S) -> Self { + Self::Simple { kind, message: message.into() } } pub fn io() -> Self { io::Error::last_os_error().into() } - pub fn full(kind: ShErrKind, message: String, span: Span) -> Self { - Self::Full { kind, message, span } + pub fn full>(kind: ShErrKind, message: S, span: Span) -> Self { + Self::Full { kind, message: message.into(), span } } pub fn try_blame(&mut self, blame: Span) { match self { @@ -178,7 +178,7 @@ impl Display for ShErr { let dist = span.end() - span.start(); let padding = " ".repeat(offset); let line_inner = "~".repeat(dist.saturating_sub(2)); - let err_kind = style_text(&self.display_kind(), Style::Red | Style::Bold); + let err_kind = &self.display_kind().styled(Style::Red | Style::Bold); let stat_line = format!("[{}:{}] - {}{}",line_no,offset,err_kind,message); let indicator_line = if dist == 1 { format!("{}^",padding) diff --git a/src/libsh/utils.rs b/src/libsh/utils.rs index 1557d69..a307177 100644 --- a/src/libsh/utils.rs +++ b/src/libsh/utils.rs @@ -5,8 +5,19 @@ use nix::libc::getpgrp; use crate::{expand::{expand_vars::{expand_dquote, expand_var}, tilde::expand_tilde}, prelude::*}; +use super::term::StyleSet; + pub trait StrOps { - fn trim_quotes(&self) -> String; + /// This function operates on anything that implements `AsRef` and `Display`, which is mainly strings. + /// It takes a 'Style' which can be passed as a single Style object like `Style::Cyan` or a Bit OR of many styles, + /// For instance: `Style::Red | Style::Bold | Style::Italic` + fn styled>(self, style: S) -> String; +} + +impl + Display> StrOps for T { + fn styled>(self, style: S) -> String { + style_text(&self, style) + } } pub trait ArgVec { @@ -16,30 +27,12 @@ pub trait ArgVec { impl ArgVec for Vec { /// Converts the contained tokens into strings. - /// This function also performs token expansion. fn as_strings(self, shenv: &mut ShEnv) -> Vec { let mut argv_iter = self.into_iter(); let mut argv_processed = vec![]; while let Some(arg) = argv_iter.next() { - match arg.rule() { - TkRule::VarSub => { - let mut tokens = expand_var(arg, shenv).into_iter(); - while let Some(token) = tokens.next() { - argv_processed.push(token.to_string()) - } - } - TkRule::TildeSub => { - let expanded = expand_tilde(arg); - argv_processed.push(expanded); - } - TkRule::DQuote => { - let expanded = expand_dquote(arg, shenv); - argv_processed.push(expanded) - } - _ => { - argv_processed.push(arg.to_string()) - } - } + let cleaned = trim_quotes(&arg); + argv_processed.push(cleaned); } argv_processed } @@ -72,11 +65,11 @@ pub enum LogLevel { impl Display for LogLevel { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - ERROR => write!(f,"{}",style_text("ERROR", Style::Red | Style::Bold)), - WARN => write!(f,"{}",style_text("WARN", Style::Yellow | Style::Bold)), - INFO => write!(f,"{}",style_text("INFO", Style::Green | Style::Bold)), - DEBUG => write!(f,"{}",style_text("DEBUG", Style::Magenta | Style::Bold)), - TRACE => write!(f,"{}",style_text("TRACE", Style::Blue | Style::Bold)), + ERROR => write!(f,"{}","ERROR".styled(Style::Red | Style::Bold)), + WARN => write!(f,"{}","WARN".styled(Style::Yellow | Style::Bold)), + INFO => write!(f,"{}","INFO".styled(Style::Green | Style::Bold)), + DEBUG => write!(f,"{}","DEBUG".styled(Style::Magenta | Style::Bold)), + TRACE => write!(f,"{}","TRACE".styled(Style::Blue | Style::Bold)), NULL => write!(f,"") } } @@ -89,9 +82,9 @@ macro_rules! log { let var_name = stringify!($var); if $level <= log_level() { let file = file!(); - let file_styled = style_text(file,Style::Cyan); + let file_styled = file.styled(Style::Cyan); let line = line!(); - let line_styled = style_text(line,Style::Cyan); + let line_styled = line.to_string().styled(Style::Cyan); let logged = format!("[{}][{}:{}] {} = {:#?}",$level, file_styled,line_styled,var_name, &$var); write(borrow_fd(2),format!("{}\n",logged).as_bytes()).unwrap(); @@ -102,9 +95,9 @@ macro_rules! log { ($level:expr, $lit:literal) => {{ if $level <= log_level() { let file = file!(); - let file_styled = style_text(file, Style::Cyan); + let file_styled = file.styled(Style::Cyan); let line = line!(); - let line_styled = style_text(line, Style::Cyan); + let line_styled = line.to_string().styled(Style::Cyan); let logged = format!("[{}][{}:{}] {}", $level, file_styled, line_styled, $lit); write(borrow_fd(2), format!("{}\n", logged).as_bytes()).unwrap(); } @@ -114,9 +107,9 @@ macro_rules! log { if $level <= log_level() { let formatted = format!($($arg)*); let file = file!(); - let file_styled = style_text(file, Style::Cyan); + let file_styled = file.styled(Style::Cyan); let line = line!(); - let line_styled = style_text(line, Style::Cyan); + let line_styled = line.to_string().styled(Style::Cyan); let logged = format!("[{}][{}:{}] {}", $level, file_styled, line_styled, formatted); write(borrow_fd(2), format!("{}\n", logged).as_bytes()).unwrap(); } diff --git a/src/main.rs b/src/main.rs index c7908d9..587b1ea 100644 --- a/src/main.rs +++ b/src/main.rs @@ -22,16 +22,21 @@ pub fn main() { loop { log!(TRACE, "Entered loop"); - let line = match prompt::read_line(&mut shenv) { + let mut line = match prompt::read_line(&mut shenv) { Ok(line) => line, Err(e) => { eprintln!("{}",e); continue; } }; + if let Some(line_exp) = expand_aliases(&line, &mut shenv) { + line = line_exp; + } let input = Rc::new(line); log!(INFO, "New input: {:?}", input); let token_stream = Lexer::new(input).lex(); + log!(DEBUG, token_stream); + log!(DEBUG, token_stream); log!(TRACE, "Token stream: {:?}", token_stream); match Parser::new(token_stream).parse() { Err(e) => { diff --git a/src/parse/lex.rs b/src/parse/lex.rs index 6af40a7..b9eac0e 100644 --- a/src/parse/lex.rs +++ b/src/parse/lex.rs @@ -1,4 +1,4 @@ -use std::fmt::Display; +use std::fmt::{Debug, Display}; use crate::prelude::*; @@ -74,7 +74,7 @@ impl Lexer { } } -#[derive(Debug,Clone)] +#[derive(Clone)] pub struct Token { rule: TkRule, span: Span @@ -98,6 +98,13 @@ impl Token { } } +impl Debug for Token { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let info = (self.rule(),self.to_string(),self.span.start,self.span.end); + write!(f,"{:?}",info) + } +} + impl Display for Token { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let slice = self.span.get_slice(); @@ -462,7 +469,7 @@ tkrule_def!(DQuote, |input: &str| { // Double quoted strings let mut chars = input.chars(); let mut len = 0; - let mut quoted = false; + let mut quote_count = 0; while let Some(ch) = chars.next() { match ch { @@ -470,21 +477,38 @@ tkrule_def!(DQuote, |input: &str| { chars.next(); len += 2; } - '"' if !quoted => { + '"' => { len += 1; - quoted = true; + quote_count += 1; } - '"' if quoted => { - len += 1; - return Some(len) - } - _ if !quoted => { - return None + ' ' | '\t' | ';' | '\n' if quote_count % 2 == 0 => { + if quote_count > 0 { + if quote_count % 2 == 0 { + return Some(len) + } else { + return None + } + } else { + return None + } } _ => len += 1 } } - None + match len { + 0 => None, + _ => { + if quote_count > 0 { + if quote_count % 2 == 0 { + return Some(len) + } else { + return None + } + } else { + return None + } + } + } }); tkrule_def!(ProcSub, |input: &str| { diff --git a/src/parse/parse.rs b/src/parse/parse.rs index 02f760f..0e807be 100644 --- a/src/parse/parse.rs +++ b/src/parse/parse.rs @@ -83,6 +83,7 @@ pub enum NdRule { Main { cmd_lists: Vec }, Command { argv: Vec, redirs: Vec }, Assignment { assignments: Vec, cmd: Option> }, + FuncDef { name: Token, body: Token }, Subshell { body: Token, argv: Vec, redirs: Vec }, CmdList { cmds: Vec<(Option,Node)> }, Pipeline { cmds: Vec } @@ -255,6 +256,7 @@ ndrule_def!(CmdList, |tokens: &[Token]| { ndrule_def!(Expr, |tokens: &[Token]| { try_rules!(tokens, + ShellCmd, Pipeline, Subshell, Assignment, @@ -264,12 +266,57 @@ ndrule_def!(Expr, |tokens: &[Token]| { // Used in pipelines to avoid recursion ndrule_def!(ExprNoPipeline, |tokens: &[Token]| { try_rules!(tokens, + ShellCmd, Subshell, Assignment, Command ); }); +ndrule_def!(ShellCmd, |tokens: &[Token]| { + try_rules!(tokens, + FuncDef + ); +}); + +ndrule_def!(FuncDef, |tokens: &[Token]| { + let mut tokens_iter = tokens.iter(); + let mut node_toks = vec![]; + let name: Token; + let body: Token; + + if let Some(token) = tokens_iter.next() { + if let TkRule::FuncName = token.rule() { + node_toks.push(token.clone()); + name = token.clone(); + } else { + return Ok(None) + } + } else { + return Ok(None) + } + + if let Some(token) = tokens_iter.next() { + if let TkRule::BraceGrp = token.rule() { + node_toks.push(token.clone()); + body = token.clone(); + } else { + return Ok(None) + } + } else { + return Ok(None) + } + + let span = get_span(&node_toks)?; + let node = Node { + node_rule: NdRule::FuncDef { name, body }, + tokens: node_toks, + span, + flags: NdFlag::empty() + }; + Ok(Some(node)) +}); + ndrule_def!(Subshell, |tokens: &[Token]| { let mut tokens_iter = tokens.iter(); let mut node_toks = vec![]; diff --git a/src/prelude.rs b/src/prelude.rs index a0559d3..d40286b 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -86,6 +86,7 @@ pub use crate::{ Redir, RedirType, RedirBldr, + StrOps, RedirTarget, CmdRedirs, borrow_fd, @@ -112,12 +113,17 @@ pub use crate::{ cd::cd, pwd::pwd, read::read_builtin, + alias::alias, jobctl::{ continue_job, jobs }, BUILTINS }, + expand::{ + expand_argv, + alias::expand_aliases + }, shellenv::{ self, wait_fg, diff --git a/src/prompt/highlight.rs b/src/prompt/highlight.rs index 162af32..2582141 100644 --- a/src/prompt/highlight.rs +++ b/src/prompt/highlight.rs @@ -16,7 +16,7 @@ impl<'a> Highlighter for SynHelper<'a> { let raw = token.to_string(); match token.rule() { TkRule::Comment => { - let styled = style_text(&raw, Style::BrightBlack); + let styled = &raw.styled(Style::BrightBlack); result.push_str(&styled); } TkRule::ErrPipeOp | @@ -26,46 +26,70 @@ impl<'a> Highlighter for SynHelper<'a> { TkRule::RedirOp | TkRule::BgOp => { is_command = true; - let styled = style_text(&raw, Style::Cyan); + let styled = &raw.styled(Style::Cyan); result.push_str(&styled); } + TkRule::FuncName => { + let name = raw.strip_suffix("()").unwrap_or(&raw); + let styled = name.styled(Style::Cyan); + let rebuilt = format!("{styled}()"); + result.push_str(&rebuilt); + } TkRule::Keyword => { if &raw == "for" { in_array = true; } - let styled = style_text(&raw, Style::Yellow); + let styled = &raw.styled(Style::Yellow); result.push_str(&styled); } + TkRule::BraceGrp => { + let body = &raw[1..raw.len() - 1]; + let highlighted = self.highlight(body, 0).to_string(); + let styled_o_brace = "{".styled(Style::BrightBlue); + let styled_c_brace = "}".styled(Style::BrightBlue); + let rebuilt = format!("{styled_o_brace}{highlighted}{styled_c_brace}"); + + is_command = false; + result.push_str(&rebuilt); + } TkRule::Subshell => { let body = &raw[1..raw.len() - 1]; let highlighted = self.highlight(body, 0).to_string(); - let styled_o_paren = style_text("(", Style::BrightBlue); - let styled_c_paren = style_text(")", Style::BrightBlue); + let styled_o_paren = "(".styled(Style::BrightBlue); + let styled_c_paren = ")".styled(Style::BrightBlue); let rebuilt = format!("{styled_o_paren}{highlighted}{styled_c_paren}"); + is_command = false; result.push_str(&rebuilt); } TkRule::Ident => { if in_array { if &raw == "in" { - let styled = style_text(&raw, Style::Yellow); + let styled = &raw.styled(Style::Yellow); result.push_str(&styled); } else { - let styled = style_text(&raw, Style::Magenta); + let styled = &raw.styled(Style::Magenta); result.push_str(&styled); } + + } else if &raw == "{" || &raw == "}" { + result.push_str(&raw); + } else if is_command { if get_bin_path(&token.to_string(), self.shenv).is_some() || self.shenv.logic().get_alias(&raw).is_some() || self.shenv.logic().get_function(&raw).is_some() || BUILTINS.contains(&raw.as_str()) { - let styled = style_text(&raw, Style::Green); + let styled = &raw.styled(Style::Green); result.push_str(&styled); + } else { - let styled = style_text(&raw, Style::Red | Style::Bold); + let styled = &raw.styled(Style::Red | Style::Bold); result.push_str(&styled); } + is_command = false; + } else { result.push_str(&raw); } diff --git a/src/prompt/mod.rs b/src/prompt/mod.rs index df16e90..dee0be3 100644 --- a/src/prompt/mod.rs +++ b/src/prompt/mod.rs @@ -1,6 +1,6 @@ use crate::prelude::*; use readline::SynHelper; -use rustyline::{config::Configurer, history::DefaultHistory, ColorMode, CompletionType, Config, DefaultEditor, EditMode, Editor}; +use rustyline::{config::Configurer, history::{DefaultHistory, History}, ColorMode, CompletionType, Config, DefaultEditor, EditMode, Editor}; pub mod readline; pub mod highlight; @@ -27,10 +27,19 @@ fn init_rl<'a>(shenv: &'a mut ShEnv) -> Editor, DefaultHistory> { pub fn read_line<'a>(shenv: &'a mut ShEnv) -> ShResult { log!(TRACE, "Entering prompt"); - let prompt = &style_text("$ ", Style::Green | Style::Bold); + let prompt = "$ ".styled(Style::Green | Style::Bold); let mut editor = init_rl(shenv); - match editor.readline(prompt) { - Ok(line) => Ok(line), + match editor.readline(&prompt) { + Ok(line) => { + if !line.is_empty() { + let hist_path = std::env::var("FERN_HIST").ok(); + editor.history_mut().add(&line).unwrap(); + if let Some(path) = hist_path { + editor.history_mut().save(&PathBuf::from(path)).unwrap(); + } + } + Ok(line) + }, Err(rustyline::error::ReadlineError::Eof) => { kill(Pid::this(), Signal::SIGQUIT)?; Ok(String::new()) diff --git a/src/prompt/readline.rs b/src/prompt/readline.rs index 33b17eb..a30c0f2 100644 --- a/src/prompt/readline.rs +++ b/src/prompt/readline.rs @@ -54,7 +54,7 @@ pub struct SynHint { impl SynHint { pub fn new(text: String) -> Self { - let styled = style_text(&text, Style::BrightBlack); + let styled = (&text).styled(Style::BrightBlack); Self { text, styled } } pub fn empty() -> Self { @@ -78,7 +78,6 @@ impl Hint for SynHint { impl<'a> Hinter for SynHelper<'a> { type Hint = SynHint; fn hint(&self, line: &str, pos: usize, ctx: &rustyline::Context<'_>) -> Option { - return Some(SynHint::empty()); if line.is_empty() { return None } diff --git a/src/shellenv/jobs.rs b/src/shellenv/jobs.rs index adb5c51..9c72912 100644 --- a/src/shellenv/jobs.rs +++ b/src/shellenv/jobs.rs @@ -99,9 +99,10 @@ impl<'a> ChildProc { } else { WtStat::Exited(pid, 0) }; + log!(DEBUG, command); let mut child = Self { pgid: pid, pid, command, stat }; if let Some(pgid) = pgid { - child.set_pgid(pgid)?; + child.set_pgid(pgid).ok(); } Ok(child) } @@ -345,14 +346,14 @@ impl Job { stat_line = format!("{}{} ",pid,stat_line); stat_line = format!("{} {}", stat_line, cmd); stat_line = match job_stat { - WtStat::Stopped(..) | WtStat::Signaled(..) => style_text(stat_line, Style::Magenta), + WtStat::Stopped(..) | WtStat::Signaled(..) => stat_line.styled(Style::Magenta), WtStat::Exited(_, code) => { match code { - 0 => style_text(stat_line, Style::Green), - _ => style_text(stat_line, Style::Red), + 0 => stat_line.styled(Style::Green), + _ => stat_line.styled(Style::Red), } } - _ => style_text(stat_line, Style::Cyan) + _ => stat_line.styled(Style::Cyan) }; if i != self.get_cmds().len() - 1 { stat_line = format!("{} |",stat_line); diff --git a/src/shellenv/logic.rs b/src/shellenv/logic.rs index c9bdfef..7377703 100644 --- a/src/shellenv/logic.rs +++ b/src/shellenv/logic.rs @@ -16,7 +16,13 @@ impl LogTab { pub fn get_alias(&self,name: &str) -> Option<&str> { self.aliases.get(name).map(|a| a.as_str()) } + pub fn set_alias(&mut self, name: &str, body: &str) { + self.aliases.insert(name.to_string(),body.trim().to_string()); + } pub fn get_function(&self,name: &str) -> Option<&str> { self.functions.get(name).map(|a| a.as_str()) } + pub fn set_function(&mut self, name: &str, body: &str) { + self.functions.insert(name.to_string(),body.trim().to_string()); + } } diff --git a/src/shellenv/vars.rs b/src/shellenv/vars.rs index 4c15665..352ee16 100644 --- a/src/shellenv/vars.rs +++ b/src/shellenv/vars.rs @@ -92,8 +92,8 @@ impl VarTab { env::set_var("HOME", home.clone()); env_vars.insert("SHELL".into(), pathbuf_to_string(std::env::current_exe())); env::set_var("SHELL", pathbuf_to_string(std::env::current_exe())); - env_vars.insert("HIST_FILE".into(),format!("{}/.fern_hist",home)); - env::set_var("HIST_FILE",format!("{}/.fern_hist",home)); + env_vars.insert("FERN_HIST".into(),format!("{}/.fern_hist",home)); + env::set_var("FERN_HIST",format!("{}/.fern_hist",home)); env_vars }