Implemented functions and aliases
This commit is contained in:
19
src/builtin/alias.rs
Normal file
19
src/builtin/alias.rs
Normal file
@@ -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(())
|
||||
}
|
||||
@@ -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<usize> {
|
||||
});
|
||||
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<usize> {
|
||||
|
||||
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
|
||||
|
||||
@@ -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"
|
||||
];
|
||||
|
||||
@@ -47,9 +47,9 @@ fn exec_list(list: Vec<(Option<CmdGuard>, 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<CmdGuard>, 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 } => {
|
||||
|
||||
75
src/expand/alias.rs
Normal file
75
src/expand/alias.rs
Normal file
@@ -0,0 +1,75 @@
|
||||
use crate::{parse::lex::SEPARATORS, prelude::*};
|
||||
|
||||
pub fn expand_aliases(input: &str, shenv: &mut ShEnv) -> Option<String> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -11,11 +11,11 @@ pub fn expand_var(var_sub: Token, shenv: &mut ShEnv) -> Vec<Token> {
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -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<Token>, shenv: &mut ShEnv) -> Vec<Token> {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<S: Into<String>>(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<S: Into<String>>(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)
|
||||
|
||||
@@ -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<str>` 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<S: Into<StyleSet>>(self, style: S) -> String;
|
||||
}
|
||||
|
||||
impl<T: AsRef<str> + Display> StrOps for T {
|
||||
fn styled<S: Into<StyleSet>>(self, style: S) -> String {
|
||||
style_text(&self, style)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ArgVec {
|
||||
@@ -16,30 +27,12 @@ pub trait ArgVec {
|
||||
|
||||
impl ArgVec for Vec<Token> {
|
||||
/// Converts the contained tokens into strings.
|
||||
/// This function also performs token expansion.
|
||||
fn as_strings(self, shenv: &mut ShEnv) -> Vec<String> {
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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| {
|
||||
|
||||
@@ -83,6 +83,7 @@ pub enum NdRule {
|
||||
Main { cmd_lists: Vec<Node> },
|
||||
Command { argv: Vec<Token>, redirs: Vec<Redir> },
|
||||
Assignment { assignments: Vec<Token>, cmd: Option<Box<Node>> },
|
||||
FuncDef { name: Token, body: Token },
|
||||
Subshell { body: Token, argv: Vec<Token>, redirs: Vec<Redir> },
|
||||
CmdList { cmds: Vec<(Option<CmdGuard>,Node)> },
|
||||
Pipeline { cmds: Vec<Node> }
|
||||
@@ -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![];
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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<SynHelper<'a>, DefaultHistory> {
|
||||
|
||||
pub fn read_line<'a>(shenv: &'a mut ShEnv) -> ShResult<String> {
|
||||
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())
|
||||
|
||||
@@ -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<Self::Hint> {
|
||||
return Some(SynHint::empty());
|
||||
if line.is_empty() {
|
||||
return None
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user