diff --git a/src/builtin/echo.rs b/src/builtin/echo.rs index afdc168..2faa719 100644 --- a/src/builtin/echo.rs +++ b/src/builtin/echo.rs @@ -68,7 +68,9 @@ pub fn echo(node: Node, shenv: &mut ShEnv) -> ShResult<()> { break } } - let argv = argv_iter.collect::>().as_strings(shenv); + let mut argv = argv_iter.collect::>().as_strings(shenv); + argv.retain(|arg| arg != "\n"); + log!(DEBUG,argv); let mut formatted = argv.join(" "); if !echo_flags.contains(EchoFlags::NO_NEWLINE) { formatted.push('\n'); diff --git a/src/expand/brace.rs b/src/expand/brace.rs new file mode 100644 index 0000000..4a08187 --- /dev/null +++ b/src/expand/brace.rs @@ -0,0 +1,149 @@ +use crate::{expand::vars::expand_string, prelude::*}; + +pub fn expand_brace_token(token: Token, shenv: &mut ShEnv) -> ShResult> { + let raw = token.as_raw(shenv); + let raw_exp = expand_string(&raw, shenv)?; + log!(DEBUG, raw_exp); + let expanded = expand_brace_string(&raw_exp); + log!(DEBUG, expanded); + let mut new_tokens = shenv.expand_input(&expanded, token.span()); + new_tokens.retain(|tk| tk.rule() != TkRule::Whitespace); + log!(DEBUG, new_tokens); + Ok(new_tokens) +} + +pub fn expand_brace_string(raw: &str) -> String { + let mut result = VecDeque::new(); + let mut stack = vec![]; + stack.push(raw.to_string()); + + while let Some(current) = stack.pop() { + if let Some((prefix,braces,suffix)) = get_brace_positions(¤t) { + let expanded = expand_brace_inner(&braces); + for part in expanded { + let formatted = format!("{prefix}{part}{suffix}"); + stack.push(formatted); + } + } else { + result.fpush(current); + } + } + + result.into_iter().collect::>().join(" ") +} + +pub fn get_brace_positions(slice: &str) -> Option<(String, String, String)> { + let mut chars = slice.chars().enumerate(); + let mut start = None; + let mut brc_count = 0; + while let Some((i,ch)) = chars.next() { + match ch { + '{' => { + if brc_count == 0 { + start = Some(i); + } + brc_count += 1; + } + '}' => { + brc_count -= 1; + if brc_count == 0 { + if let Some(start) = start { + let prefix = slice[..start].to_string(); + let braces = slice[start+1..i].to_string(); + let suffix = slice[i+1..].to_string(); + return Some((prefix,braces,suffix)) + } + } + } + _ => continue + } + } + None +} + +fn expand_brace_inner(inner: &str) -> Vec { + if inner.split_once("..").is_some() && !inner.contains(['{','}']) { + expand_range(inner) + } else { + split_list(inner) + } +} + +fn split_list(list: &str) -> Vec { + log!(DEBUG, list); + let mut chars = list.chars(); + let mut items = vec![]; + let mut curr_item = String::new(); + let mut brc_count = 0; + + while let Some(ch) = chars.next() { + match ch { + ',' if brc_count == 0 => { + if !curr_item.is_empty() { + items.push(std::mem::take(&mut curr_item)); + } + } + '{' => { + brc_count += 1; + curr_item.push(ch); + } + '}' => { + if brc_count == 0 { + return vec![list.to_string()]; + } + brc_count -= 1; + curr_item.push(ch); + } + _ => curr_item.push(ch), + } + } + if !curr_item.is_empty() { + items.push(std::mem::take(&mut curr_item)) + } + log!(DEBUG,items); + items +} + +fn expand_range(range: &str) -> Vec { + if let Some((left,right)) = range.split_once("..") { + // I know, I know + // This is checking to see if the range looks like "a..b" or "A..B" + // one character on both sides, both are letters, and both are uppercase OR both are lowercase + if (left.len() == 1 && right.len() == 1) && + (left.chars().all(|ch| ch.is_ascii_alphanumeric() && right.chars().all(|ch| ch.is_ascii_alphanumeric()))) && + ( + (left.chars().all(|ch| ch.is_uppercase()) && right.chars().all(|ch| ch.is_uppercase())) || + (left.chars().all(|ch| ch.is_lowercase()) && right.chars().all(|ch| ch.is_lowercase())) + ) + { + expand_range_alpha(left, right) + } + else if right.chars().all(|ch| ch.is_ascii_digit()) && left.chars().all(|ch| ch.is_ascii_digit()) + { + expand_range_numeric(left, right) + } + else + { + vec![range.to_string()] + } + } else { + vec![range.to_string()] + } +} + +fn expand_range_alpha(left: &str, right: &str) -> Vec { + let start = left.chars().next().unwrap() as u8; + let end = right.chars().next().unwrap() as u8; + + if start > end { + (end..=start).rev().map(|c| (c as char).to_string()).collect() + } else { + (start..=end).map(|c| (c as char).to_string()).collect() + } +} + +fn expand_range_numeric(left: &str, right: &str) -> Vec { + let start = left.parse::().unwrap(); + let end = right.parse::().unwrap(); + (start..=end).map(|i| i.to_string()).collect() +} diff --git a/src/expand/cmdsub.rs b/src/expand/cmdsub.rs index fd2ff13..adfe15b 100644 --- a/src/expand/cmdsub.rs +++ b/src/expand/cmdsub.rs @@ -29,5 +29,7 @@ pub fn expand_cmdsub_string(mut s: &str, shenv: &mut ShEnv) -> ShResult close(w_pipe).ok(); } } - Ok(read_to_string(r_pipe)?) + let result = read_to_string(r_pipe); + close(r_pipe)?; + Ok(result?) } diff --git a/src/expand/mod.rs b/src/expand/mod.rs index b49e049..8d3c001 100644 --- a/src/expand/mod.rs +++ b/src/expand/mod.rs @@ -4,8 +4,10 @@ pub mod alias; pub mod cmdsub; pub mod arithmetic; pub mod prompt; +pub mod brace; use arithmetic::expand_arith_token; +use brace::expand_brace_token; use cmdsub::expand_cmdsub_token; use vars::{expand_string, expand_var}; use tilde::expand_tilde_token; @@ -43,6 +45,10 @@ pub fn expand_token(token: Token, shenv: &mut ShEnv) -> ShResult> { let arith_exp = expand_arith_token(token.clone(), shenv)?; processed.push(arith_exp); } + TkRule::BraceExp => { + let mut brace_exp = expand_brace_token(token, shenv)?; + processed.append(&mut brace_exp); + } TkRule::CmdSub => { let mut cmdsub_exp = expand_cmdsub_token(token.clone(), shenv)?; processed.append(&mut cmdsub_exp); diff --git a/src/expand/vars.rs b/src/expand/vars.rs index bb4899b..4996d6d 100644 --- a/src/expand/vars.rs +++ b/src/expand/vars.rs @@ -32,8 +32,9 @@ pub fn expand_string(s: &str, shenv: &mut ShEnv) -> ShResult { break } let ch = chars.next().unwrap(); + log!(DEBUG,var_name); match ch { - '{' => { + '{' if var_name.is_empty() => { in_brace = true; } '}' if in_brace => { @@ -80,7 +81,7 @@ pub fn expand_string(s: &str, shenv: &mut ShEnv) -> ShResult { expanded = true; break } - ' ' | '\t' | '\n' | ';' => { + ' ' | '\t' | '\n' | ';' | ',' | '{' => { let value = shenv.vars().get_var(&var_name); result.push_str(value); result.push(ch); diff --git a/src/libsh/sys.rs b/src/libsh/sys.rs index b4f9cea..0748d1c 100644 --- a/src/libsh/sys.rs +++ b/src/libsh/sys.rs @@ -83,6 +83,11 @@ pub fn sh_quit(code: i32) -> ! { if let Some(termios) = crate::get_saved_termios() { termios::tcsetattr(std::io::stdin(), termios::SetArg::TCSANOW, &termios).unwrap(); } + if code == 0 { + write_err("exit\n").ok(); + } else { + write_err(format!("exit {code}\n")).ok(); + } exit(code); } diff --git a/src/libsh/utils.rs b/src/libsh/utils.rs index 7a66c6c..0dba26e 100644 --- a/src/libsh/utils.rs +++ b/src/libsh/utils.rs @@ -46,7 +46,7 @@ impl ArgVec for Vec { let mut argv_iter = self.into_iter(); let mut argv_processed = vec![]; while let Some(arg) = argv_iter.next() { - let cleaned = clean_string(&arg.as_raw(shenv)); + let cleaned = clean_string(&arg.as_raw(shenv)).trim_matches(' ').to_string(); argv_processed.push(cleaned); } argv_processed diff --git a/src/parse/lex.rs b/src/parse/lex.rs index f94db0f..2ed740f 100644 --- a/src/parse/lex.rs +++ b/src/parse/lex.rs @@ -37,10 +37,11 @@ pub const SEPARATORS: [TkRule;7] = [ TkRule::CasePat ]; -pub const EXPANSIONS: [TkRule;6] = [ +pub const EXPANSIONS: [TkRule;7] = [ TkRule::DQuote, TkRule::VarSub, TkRule::CmdSub, + TkRule::BraceExp, TkRule::ProcSub, TkRule::TildeSub, TkRule::ArithSub @@ -223,6 +224,7 @@ pub enum TkRule { BgOp, RedirOp, FuncName, + BraceExp, BraceGrp, ProcSub, VarSub, @@ -270,6 +272,7 @@ impl TkRule { try_match!(SQuote,input); try_match!(DQuote,input); try_match!(FuncName,input); + try_match!(BraceExp,input); try_match!(BraceGrp,input); try_match!(TildeSub,input); try_match!(Subshell,input); @@ -294,6 +297,50 @@ impl TkRule { } } +tkrule_def!(BraceExp, |input: &str| { + let mut chars = input.chars().peekable(); + let mut len = 0; + let mut brc_count = 0; + let mut is_brc_exp = false; + + while let Some(ch) = chars.next() { + match ch { + '\\' => { + len += 1; + if let Some(ch) = chars.next() { + len += ch.len_utf8(); + } + } + '{' => { + len += 1; + brc_count += 1; + } + '}' => { + if brc_count == 0 { + return None + } + len += 1; + brc_count -= 1; + if brc_count == 0 { + is_brc_exp = true; + } + } + ' ' | '\t' | '\n' | ';' => { + match is_brc_exp { + true => return Some(len), + false => return None + } + } + _ => len += ch.len_utf8() + } + } + + match is_brc_exp { + true => return Some(len), + false => return None + } +}); + tkrule_def!(Comment, |input: &str| { let mut chars = input.chars().peekable(); let mut len = 0; @@ -932,7 +979,7 @@ tkrule_def!(VarSub, |input: &str| { '{' => { match len { 0 => return None, - _ => { + 1 => { while let Some(ch) = chars.next() { match ch { '\\' => { @@ -947,6 +994,7 @@ tkrule_def!(VarSub, |input: &str| { } } } + _ => return Some(len) } } '$' => { diff --git a/src/parse/mod.rs b/src/parse/mod.rs index 4d72e6c..db167c1 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -1185,6 +1185,7 @@ ndrule_def!(Command, shenv, |tokens: &[Token], shenv: &mut ShEnv| { TkRule::ArithSub | TkRule::CmdSub | TkRule::BraceGrp | + TkRule::BraceExp | TkRule::VarSub => { argv.push(token.clone()); }