diff --git a/src/builtin/alias.rs b/src/builtin/alias.rs index f7052b0..e64cf32 100644 --- a/src/builtin/alias.rs +++ b/src/builtin/alias.rs @@ -6,12 +6,16 @@ pub fn alias(node: Node, shenv: &mut ShEnv) -> ShResult<()> { 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(); + let arg_raw = shenv.input_slice(arg.span()).to_string(); if let Some((alias,body)) = arg_raw.split_once('=') { + log!(DEBUG, "{:?}",arg.span()); + log!(DEBUG, arg_raw); + log!(DEBUG, body); let clean_body = trim_quotes(&body); + log!(DEBUG, clean_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())) + return Err(ShErr::full(ShErrKind::SyntaxErr, "Expected an assignment in alias args", shenv.get_input(), arg.span().clone())) } } } else { unreachable!() } diff --git a/src/builtin/cd.rs b/src/builtin/cd.rs index 714c982..ca3e431 100644 --- a/src/builtin/cd.rs +++ b/src/builtin/cd.rs @@ -5,7 +5,7 @@ pub fn cd(node: Node, shenv: &mut ShEnv) -> ShResult<()> { if let NdRule::Command { argv, redirs: _ } = rule { let mut argv_iter = argv.into_iter(); argv_iter.next(); // Ignore 'cd' - let dir_raw = argv_iter.next().map(|arg| arg.to_string()).unwrap_or(std::env::var("HOME")?); + let dir_raw = argv_iter.next().map(|arg| shenv.input_slice(arg.span()).into()).unwrap_or(std::env::var("HOME")?); let dir = PathBuf::from(&dir_raw); std::env::set_current_dir(dir)?; shenv.vars_mut().export("PWD",&dir_raw); diff --git a/src/builtin/control_flow.rs b/src/builtin/control_flow.rs new file mode 100644 index 0000000..09536f8 --- /dev/null +++ b/src/builtin/control_flow.rs @@ -0,0 +1,20 @@ +use crate::prelude::*; + +pub fn sh_flow(node: Node, shenv: &mut ShEnv, kind: ShErrKind) -> ShResult<()> { + let rule = node.into_rule(); + let mut code: i32 = 0; + if let NdRule::Command { argv, redirs } = rule { + let mut argv_iter = argv.into_iter(); + while let Some(arg) = argv_iter.next() { + if let Ok(code_arg) = shenv.input_slice(arg.span()).parse() { + code = code_arg + } + } + } else { unreachable!() } + shenv.set_code(code); + // Our control flow keywords are used as ShErrKinds + // This design will halt the execution flow and start heading straight back upward + // Function returns and loop breaks/continues will be caught in the proper context to allow + // Execution to continue at the proper return point. + Err(ShErr::simple(kind, "")) +} diff --git a/src/builtin/export.rs b/src/builtin/export.rs index 0ffc594..fa2232c 100644 --- a/src/builtin/export.rs +++ b/src/builtin/export.rs @@ -6,7 +6,7 @@ pub fn export(node: Node, shenv: &mut ShEnv) -> ShResult<()> { let mut argv_iter = argv.into_iter(); argv_iter.next(); // Ignore 'export' while let Some(arg) = argv_iter.next() { - let arg_raw = arg.to_string(); + let arg_raw = shenv.input_slice(arg.span()).to_string(); if let Some((var,val)) = arg_raw.split_once('=') { shenv.vars_mut().export(var, val); } else { diff --git a/src/builtin/jobctl.rs b/src/builtin/jobctl.rs index e4dfa51..873cffd 100644 --- a/src/builtin/jobctl.rs +++ b/src/builtin/jobctl.rs @@ -14,6 +14,7 @@ pub fn continue_job(node: Node, shenv: &mut ShEnv, fg: bool) -> ShResult<()> { ShErr::full( ShErrKind::InternalErr, format!("Somehow called {} with an existing foreground job",cmd), + shenv.get_input(), blame ) ) @@ -22,11 +23,11 @@ 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", blame)) + return Err(ShErr::full(ShErrKind::ExecFail, "No jobs found", shenv.get_input(), blame)) }; let tabid = match argv_s.next() { - Some(arg) => parse_job_id(&arg, blame.clone())?, + Some(arg) => parse_job_id(&arg, blame.clone(),shenv)?, None => curr_job_id }; @@ -36,7 +37,14 @@ pub fn continue_job(node: Node, shenv: &mut ShEnv, fg: bool) -> ShResult<()> { if query_result.is_some() { Ok(j.remove_job(id.clone()).unwrap()) } else { - Err(ShErr::full(ShErrKind::ExecFail, format!("Job id `{}' not found", tabid), blame)) + Err( + ShErr::full( + ShErrKind::ExecFail, + format!("Job id `{}' not found", tabid), + shenv.get_input(), + blame + ) + ) } })?; @@ -54,7 +62,7 @@ pub fn continue_job(node: Node, shenv: &mut ShEnv, fg: bool) -> ShResult<()> { Ok(()) } -fn parse_job_id(arg: &str, blame: Span) -> ShResult { +fn parse_job_id(arg: &str, blame: Rc>, shenv: &mut ShEnv) -> ShResult { if arg.starts_with('%') { let arg = arg.strip_prefix('%').unwrap(); if arg.chars().all(|ch| ch.is_ascii_digit()) { @@ -66,7 +74,14 @@ 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()",blame)) + None => Err( + ShErr::full( + ShErrKind::InternalErr, + "Found a job but no table id in parse_job_id()", + shenv.get_input(), + blame + ) + ) } } } else if arg.chars().all(|ch| ch.is_ascii_digit()) { @@ -86,10 +101,24 @@ 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()",blame)) + None => Err( + ShErr::full( + ShErrKind::InternalErr, + "Found a job but no table id in parse_job_id()", + shenv.get_input(), + blame + ) + ) } } else { - Err(ShErr::full(ShErrKind::SyntaxErr,format!("Invalid fd arg: {}", arg),blame)) + Err( + ShErr::full( + ShErrKind::SyntaxErr, + format!("Invalid fd arg: {}", arg), + shenv.get_input(), + blame + ) + ) } } @@ -100,10 +129,17 @@ pub fn jobs(node: Node, shenv: &mut ShEnv) -> ShResult<()> { let mut flags = JobCmdFlags::empty(); while let Some(arg) = argv.next() { - let arg_s = arg.to_string(); + let arg_s = shenv.input_slice(arg.span()); 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", arg.span().clone())) + return Err( + ShErr::full( + ShErrKind::SyntaxErr, + "Invalid flag in jobs call", + shenv.get_input(), + arg.span() + ) + ) } chars.next(); while let Some(ch) = chars.next() { @@ -113,7 +149,14 @@ 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", arg.span().clone())) + _ => return Err( + ShErr::full( + ShErrKind::SyntaxErr, + "Invalid flag in jobs call", + shenv.get_input(), + arg.span() + ) + ) }; flags |= flag diff --git a/src/builtin/mod.rs b/src/builtin/mod.rs index a7546c7..b4c1310 100644 --- a/src/builtin/mod.rs +++ b/src/builtin/mod.rs @@ -5,8 +5,9 @@ pub mod export; pub mod jobctl; pub mod read; pub mod alias; +pub mod control_flow; -pub const BUILTINS: [&str;9] = [ +pub const BUILTINS: [&str;13] = [ "echo", "cd", "pwd", @@ -15,5 +16,9 @@ pub const BUILTINS: [&str;9] = [ "bg", "jobs", "read", - "alias" + "alias", + "exit", + "continue", + "return", + "break", ]; diff --git a/src/builtin/read.rs b/src/builtin/read.rs index 66feb88..9e1a772 100644 --- a/src/builtin/read.rs +++ b/src/builtin/read.rs @@ -27,7 +27,8 @@ pub fn read_builtin(node: Node, shenv: &mut ShEnv) -> ShResult<()> { shenv.vars_mut().set_var(&first_var.to_string(), &read_input); } */ - shenv.vars_mut().set_var(&var.to_string(), &read_input); + let var_name = shenv.input_slice(var.span()).to_string(); + shenv.vars_mut().set_var(&var_name, &read_input); } } else { unreachable!() diff --git a/src/execute/ifthen.rs b/src/execute/ifthen.rs new file mode 100644 index 0000000..8aff436 --- /dev/null +++ b/src/execute/ifthen.rs @@ -0,0 +1,28 @@ +use crate::{parse::parse::SynTree, prelude::*}; + +pub fn exec_if(node: Node, shenv: &mut ShEnv) -> ShResult<()> { + let rule = node.into_rule(); + if let NdRule::IfThen { cond_blocks, else_block } = rule { + if shenv.ctx().flags().contains(ExecFlags::NO_FORK) { + shenv.ctx_mut().unset_flag(ExecFlags::NO_FORK); + } + let mut cond_blocks = cond_blocks.into_iter(); + + while let Some(block) = cond_blocks.next() { + let cond = block.0; + let body = block.1; + let ast = SynTree::from_vec(cond); + Executor::new(ast,shenv).walk()?; + if shenv.get_code() == 0 { + let ast = SynTree::from_vec(body); + return Executor::new(ast,shenv).walk() + } + } + + if let Some(block) = else_block { + let ast = SynTree::from_vec(block); + Executor::new(ast,shenv).walk()?; + } + } else { unreachable!() } + Ok(()) +} diff --git a/src/execute.rs b/src/execute/mod.rs similarity index 79% rename from src/execute.rs rename to src/execute/mod.rs index c4563dc..52a4af6 100644 --- a/src/execute.rs +++ b/src/execute/mod.rs @@ -4,6 +4,33 @@ use shellenv::jobs::{ChildProc, JobBldr}; use crate::{builtin::export::export, libsh::{error::Blame, sys::{execvpe, get_bin_path}, utils::{ArgVec, StrOps}}, parse::{lex::Token, parse::{CmdGuard, NdFlag, Node, NdRule, SynTree}}, prelude::*}; +pub mod ifthen; + +pub fn exec_input>(input: S, shenv: &mut ShEnv) -> ShResult<()> { + let input = input.into(); + shenv.new_input(&input); + log!(INFO, "New input: {:?}", input); + + let token_stream = Lexer::new(input,shenv).lex(); + + let token_stream = expand_aliases(token_stream, shenv); + for token in &token_stream { + log!(DEBUG, token); + log!(DEBUG, "{}",token.as_raw(shenv)); + } + + let syn_tree = Parser::new(token_stream,shenv).parse()?; + if let Err(e) = Executor::new(syn_tree, shenv).walk() { + if let ShErrKind::CleanExit = e.kind() { + let code = shenv.get_code(); + sh_quit(code); + } else { + return Err(e.into()) + } + } + Ok(()) +} + pub struct Executor<'a> { ast: SynTree, shenv: &'a mut ShEnv @@ -16,10 +43,9 @@ impl<'a> Executor<'a> { pub fn walk(&mut self) -> ShResult<()> { log!(DEBUG, "Starting walk"); while let Some(node) = self.ast.next_node() { - let span = node.span(); if let NdRule::CmdList { cmds } = node.clone().into_rule() { log!(TRACE, "{:?}", cmds); - exec_list(cmds, self.shenv).try_blame(span)? + exec_list(cmds, self.shenv).try_blame(node.as_raw(self.shenv),node.span())? } else { unreachable!() } } Ok(()) @@ -33,6 +59,7 @@ fn exec_list(list: Vec<(Option, Node)>, shenv: &mut ShEnv) -> ShResult let guard = cmd_info.0; let cmd = cmd_info.1; let span = cmd.span(); + let cmd_raw = cmd.as_raw(shenv); if let Some(guard) = guard { let code = shenv.get_code(); @@ -47,11 +74,12 @@ fn exec_list(list: Vec<(Option, Node)>, shenv: &mut ShEnv) -> ShResult } log!(TRACE, "{:?}", *cmd.rule()); match *cmd.rule() { - 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)?, + NdRule::Command {..} => dispatch_command(cmd, shenv).try_blame(cmd_raw, span)?, + NdRule::Subshell {..} => exec_subshell(cmd,shenv).try_blame(cmd_raw, span)?, + NdRule::IfThen {..} => ifthen::exec_if(cmd, shenv).try_blame(cmd_raw, span)?, + NdRule::FuncDef {..} => exec_funcdef(cmd,shenv).try_blame(cmd_raw, span)?, + NdRule::Assignment {..} => exec_assignment(cmd,shenv).try_blame(cmd_raw, span)?, + NdRule::Pipeline {..} => exec_pipeline(cmd, shenv).try_blame(cmd_raw, span)?, _ => unimplemented!() } } @@ -64,7 +92,7 @@ fn dispatch_command(mut node: Node, shenv: &mut ShEnv) -> ShResult<()> { 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(); + let cmd = argv.first().unwrap().as_raw(shenv); if shenv.logic().get_function(&cmd).is_some() { is_func = true; } else if node.flags().contains(NdFlag::BUILTIN) { @@ -91,33 +119,32 @@ 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 func_name = argv_iter.next().unwrap().as_raw(shenv); 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()); + let arg_raw = shenv.input_slice(arg.span()).to_string(); + shenv.vars_mut().bpush_arg(&arg_raw); } 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()) - } - } + match exec_input(body, shenv) { + Ok(()) => { + *shenv = snapshot; + return Ok(()) + } + Err(e) if e.kind() == ShErrKind::FuncReturn => { + let code = shenv.get_code(); + *shenv = snapshot; + shenv.set_code(code); + return Ok(()) } Err(e) => { *shenv = snapshot; return Err(e.into()) } } - *shenv = snapshot; } Ok(()) } @@ -125,9 +152,9 @@ fn exec_func(node: Node, shenv: &mut ShEnv) -> ShResult<()> { 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_raw = name.as_raw(shenv); let name = name_raw.trim_end_matches("()"); - let body_raw = body.to_string(); + let body_raw = body.as_raw(shenv); let body = body_raw[1..body_raw.len() - 1].trim(); shenv.logic_mut().set_function(name, body); @@ -148,26 +175,18 @@ fn exec_subshell(node: Node, shenv: &mut ShEnv) -> ShResult<()> { exit(1); } for arg in argv { - shenv.vars_mut().bpush_arg(&arg.to_string()); + let arg_raw = &arg.as_raw(shenv); + shenv.vars_mut().bpush_arg(arg_raw); } - let body_raw = body.to_string(); - let lexer_input = Rc::new( - body_raw[1..body_raw.len() - 1].to_string() - ); - let token_stream = Lexer::new(lexer_input).lex(); - match Parser::new(token_stream).parse() { - Ok(syn_tree) => { - if let Err(e) = Executor::new(syn_tree, shenv).walk() { - write_err(e)?; - exit(1); - } - } + let body_raw = body.as_raw(shenv); + + match exec_input(body_raw, shenv) { + Ok(()) => sh_quit(0), Err(e) => { - write_err(e)?; - exit(1); + eprintln!("{}",e); + sh_quit(1); } } - exit(0); } else { match unsafe { fork()? } { Child => { @@ -177,26 +196,17 @@ fn exec_subshell(node: Node, shenv: &mut ShEnv) -> ShResult<()> { exit(1); } for arg in argv { - shenv.vars_mut().bpush_arg(&arg.to_string()); + let arg_raw = &arg.as_raw(shenv); + shenv.vars_mut().bpush_arg(arg_raw); } - let body_raw = body.to_string(); - let lexer_input = Rc::new( - body_raw[1..body_raw.len() - 1].to_string() - ); - let token_stream = Lexer::new(lexer_input).lex(); - match Parser::new(token_stream).parse() { - Ok(syn_tree) => { - if let Err(e) = Executor::new(syn_tree, shenv).walk() { - write_err(e)?; - exit(1); - } - } + let body_raw = body.as_raw(shenv); + match exec_input(body_raw, shenv) { + Ok(()) => sh_quit(0), Err(e) => { - write_err(e)?; - exit(1); + eprintln!("{}",e); + sh_quit(1); } } - exit(0); } Parent { child } => { *shenv = snapshot; @@ -218,7 +228,7 @@ fn exec_subshell(node: Node, shenv: &mut ShEnv) -> ShResult<()> { fn exec_builtin(node: Node, shenv: &mut ShEnv) -> ShResult<()> { log!(DEBUG, "Executing builtin"); let command = if let NdRule::Command { argv, redirs: _ } = node.rule() { - argv.first().unwrap().to_string() + argv.first().unwrap().as_raw(shenv) } else { unreachable!() }; log!(TRACE, "{}", command.as_str()); @@ -232,6 +242,8 @@ fn exec_builtin(node: Node, shenv: &mut ShEnv) -> ShResult<()> { "bg" => continue_job(node, shenv, false)?, "read" => read_builtin(node, shenv)?, "alias" => alias(node, shenv)?, + "exit" => sh_flow(node, shenv, ShErrKind::CleanExit)?, + "return" => sh_flow(node, shenv, ShErrKind::FuncReturn)?, _ => unimplemented!("Have not yet implemented support for builtin `{}'",command) } log!(TRACE, "done"); @@ -247,7 +259,7 @@ fn exec_assignment(node: Node, shenv: &mut ShEnv) -> ShResult<()> { let mut assigns = assignments.into_iter(); if let Some(cmd) = cmd { while let Some(assign) = assigns.next() { - let assign_raw = assign.to_string(); + let assign_raw = assign.as_raw(shenv); if let Some((var,val)) = assign_raw.split_once('=') { shenv.vars_mut().export(var, val); } @@ -259,7 +271,7 @@ fn exec_assignment(node: Node, shenv: &mut ShEnv) -> ShResult<()> { } } else { while let Some(assign) = assigns.next() { - let assign_raw = assign.to_string(); + let assign_raw = assign.as_raw(shenv); if let Some((var,val)) = assign_raw.split_once('=') { shenv.vars_mut().set_var(var, val); } @@ -288,7 +300,7 @@ fn exec_pipeline(node: Node, shenv: &mut ShEnv) -> ShResult<()> { (Some(r_pipe),Some(w_pipe)) }; if let NdRule::Command { argv, redirs: _ } = cmd.rule() { - let cmd_name = argv.first().unwrap().span().get_slice().to_string(); + let cmd_name = argv.first().unwrap().as_raw(shenv); cmd_names.push(cmd_name); } else if let NdRule::Subshell {..} = cmd.rule() { cmd_names.push("subshell".to_string()); @@ -305,12 +317,12 @@ fn exec_pipeline(node: Node, shenv: &mut ShEnv) -> ShResult<()> { // Create some redirections if let Some(w_pipe) = w_pipe { - let wpipe_redir = Redir::new(1, RedirType::Output, RedirTarget::Fd(w_pipe.as_raw_fd())); + let wpipe_redir = Redir::output(1, w_pipe); shenv.ctx_mut().push_rdr(wpipe_redir); } // Use the r_pipe created in the last iteration if let Some(prev_rpipe) = prev_rpipe { - let rpipe_redir = Redir::new(0, RedirType::Input, RedirTarget::Fd(prev_rpipe.as_raw_fd())); + let rpipe_redir = Redir::input(0, prev_rpipe); shenv.ctx_mut().push_rdr(rpipe_redir); } @@ -348,6 +360,7 @@ fn exec_pipeline(node: Node, shenv: &mut ShEnv) -> ShResult<()> { fn exec_cmd(node: Node, shenv: &mut ShEnv) -> ShResult<()> { log!(DEBUG, "Executing command"); let blame = node.span(); + let blame_raw = node.as_raw(shenv); let rule = node.into_rule(); if let NdRule::Command { argv, redirs } = rule { @@ -395,7 +408,7 @@ fn exec_cmd(node: Node, shenv: &mut ShEnv) -> ShResult<()> { } } } else { - return Err(ShErr::full(ShErrKind::CmdNotFound, format!("{}", command), blame)) + return Err(ShErr::full(ShErrKind::CmdNotFound, format!("{}", command), shenv.get_input(), blame)) } } else { unreachable!("Found this rule in exec_cmd: {:?}", rule) } Ok(()) diff --git a/src/expand/alias.rs b/src/expand/alias.rs index d0de8f7..f969f84 100644 --- a/src/expand/alias.rs +++ b/src/expand/alias.rs @@ -1,75 +1,62 @@ 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; +pub fn expand_alias(candidate: Token, shenv: &mut ShEnv) -> Vec { + let mut tokens = vec![]; + let mut work_stack = VecDeque::new(); + let mut expanded_aliases = vec![]; + let logic = shenv.logic().clone(); - // 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; + // Start with the candidate token in the work queue + work_stack.bpush(candidate); - 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; - } + // Process until there are no more tokens in the queue + while let Some(token) = work_stack.fpop() { + if token.rule() == TkRule::Ident { + let candidate_str = token.as_raw(shenv); + if let Some(alias) = logic.get_alias(&candidate_str) { + // Expand the alias only if it hasn't been expanded yet + if !expanded_aliases.contains(&candidate_str) { + expanded_aliases.push(candidate_str); + let mut new_tokens = shenv.expand_input(alias, token.span()); + for token in new_tokens.iter_mut() { + work_stack.bpush(token.clone()); } + } else { + // If already expanded, just add the token to the output + tokens.push(token); } - ' ' | '\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); + } else { + tokens.push(token); + } + } else { + tokens.push(token); + } + } + tokens +} + +pub fn expand_aliases(tokens: Vec, shenv: &mut ShEnv) -> Vec { + let mut stream = tokens.iter(); + let mut processed = vec![]; + let mut is_command = true; + while let Some(token) = stream.next() { + match token.rule() { + _ if SEPARATORS.contains(&token.rule()) => { + is_command = true; + processed.push(token.clone()); + } + TkRule::Ident if is_command => { + is_command = false; + let mut alias_tokens = expand_alias(token.clone(), shenv); + log!(DEBUG, alias_tokens); + if !alias_tokens.is_empty() { + processed.append(&mut alias_tokens); + } else { + processed.push(token.clone()); } } + _ => processed.push(token.clone()), } - result = new_result; - log!(DEBUG, result); - } - - if expanded_aliases.is_empty() { - None - } else { - Some(result) } + processed } diff --git a/src/expand/cmdsub.rs b/src/expand/cmdsub.rs new file mode 100644 index 0000000..ab92ef3 --- /dev/null +++ b/src/expand/cmdsub.rs @@ -0,0 +1,30 @@ +use crate::prelude::*; + +pub fn expand_cmdsub(token: Token, shenv: &mut ShEnv) -> ShResult> { + let mut new_tokens = vec![]; + let cmdsub_raw = token.as_raw(shenv); + let body = &cmdsub_raw[2..cmdsub_raw.len() - 1].to_string(); // From '$(this)' to 'this' + + let (r_pipe,w_pipe) = c_pipe()?; + let pipe_redir = Redir::output(1, w_pipe); + let mut sub_shenv = shenv.clone(); + sub_shenv.ctx_mut().set_flag(ExecFlags::NO_FORK); + sub_shenv.collect_redirs(vec![pipe_redir]); + + match unsafe { fork()? } { + Child => { + close(r_pipe).ok(); + exec_input(body, shenv).abort_if_err(); + sh_quit(0); + } + Parent { child } => { + close(w_pipe).ok(); + } + } + let output = read_to_string(r_pipe)?; + if !output.is_empty() { + let lex_input = Rc::new(output); + } + + Ok(new_tokens) +} diff --git a/src/expand/mod.rs b/src/expand/mod.rs index 7ac1897..812b797 100644 --- a/src/expand/mod.rs +++ b/src/expand/mod.rs @@ -1,9 +1,10 @@ -pub mod expand_vars; +pub mod vars; pub mod tilde; pub mod alias; +pub mod cmdsub; use alias::expand_aliases; -use expand_vars::{expand_dquote, expand_var}; +use vars::{expand_dquote, expand_var}; use tilde::expand_tilde; use crate::prelude::*; @@ -11,7 +12,7 @@ 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, "{}",arg.as_raw(shenv)); log!(DEBUG, processed); match arg.rule() { TkRule::DQuote => { @@ -23,7 +24,7 @@ pub fn expand_argv(argv: Vec, shenv: &mut ShEnv) -> Vec { processed.append(&mut varsub_exp); } TkRule::TildeSub => { - let tilde_exp = expand_tilde(arg.clone()); + let tilde_exp = expand_tilde(arg.clone(), shenv); processed.push(tilde_exp); } _ => { diff --git a/src/expand/tilde.rs b/src/expand/tilde.rs index b2bc60f..f76cc8b 100644 --- a/src/expand/tilde.rs +++ b/src/expand/tilde.rs @@ -1,12 +1,11 @@ use crate::prelude::*; -pub fn expand_tilde(tilde_sub: Token) -> Token { - let tilde_sub_raw = tilde_sub.to_string(); +pub fn expand_tilde(tilde_sub: Token, shenv: &mut ShEnv) -> Token { + let mut tilde_sub_raw = tilde_sub.as_raw(shenv); if tilde_sub_raw.starts_with('~') { let home = std::env::var("HOME").unwrap_or_default(); - tilde_sub_raw.replacen('~', &home, 1); - let lex_input = Rc::new(tilde_sub_raw); - let mut tokens = Lexer::new(lex_input).lex(); + tilde_sub_raw = tilde_sub_raw.replacen('~', &home, 1); + let mut tokens = Lexer::new(tilde_sub_raw,shenv).lex(); tokens.pop().unwrap_or(tilde_sub) } else { tilde_sub diff --git a/src/expand/expand_vars.rs b/src/expand/vars.rs similarity index 79% rename from src/expand/expand_vars.rs rename to src/expand/vars.rs index 4bad245..dcd1c53 100644 --- a/src/expand/expand_vars.rs +++ b/src/expand/vars.rs @@ -1,18 +1,15 @@ use crate::{parse::lex::Token, prelude::*}; pub fn expand_var(var_sub: Token, shenv: &mut ShEnv) -> Vec { - let var_name = var_sub.to_string(); + let var_name = var_sub.as_raw(shenv); let var_name = var_name.trim_start_matches('$').trim_matches(['{','}']); - let value = Rc::new( - shenv.vars() - .get_var(var_name) - .to_string() - ); - Lexer::new(value).lex() // Automatically handles word splitting for us + let value = shenv.vars().get_var(var_name).to_string(); + + shenv.expand_input(&value, var_sub.span()) } pub fn expand_dquote(dquote: Token, shenv: &mut ShEnv) -> Token { - let dquote_raw = dquote.to_string(); + let dquote_raw = dquote.as_raw(shenv); let mut result = String::new(); let mut var_name = String::new(); let mut chars = dquote_raw.chars().peekable(); @@ -63,5 +60,8 @@ pub fn expand_dquote(dquote: Token, shenv: &mut ShEnv) -> Token { log!(DEBUG, result); - Lexer::new(Rc::new(result)).lex().pop().unwrap_or(dquote) + log!(DEBUG, "{:?}",dquote.span()); + let token = shenv.expand_input(&result, dquote.span()).pop().unwrap_or(dquote); + log!(DEBUG, "{}",token.as_raw(shenv)); + token } diff --git a/src/libsh/error.rs b/src/libsh/error.rs index fabd417..cc70a17 100644 --- a/src/libsh/error.rs +++ b/src/libsh/error.rs @@ -5,16 +5,62 @@ use crate::prelude::*; pub type ShResult = Result; +pub trait ResultExt { + fn eprint(self) -> Self; + fn abort_if_err(&self); +} + +#[derive(Clone,Debug)] +pub struct BlamePair { + input: String, + span: Rc> +} + +impl BlamePair { + pub fn new(input: String, span: Rc>) -> Self { + Self { input, span } + } + pub fn start(&self) -> usize { + self.span.borrow().start() + } + pub fn end(&self) -> usize { + self.span.borrow().end() + } + pub fn len(&self) -> usize { + self.input.len() + } +} + +impl Into for BlamePair { + fn into(self) -> String { + self.input + } +} + +impl ResultExt for Result { + fn eprint(self) -> Self { + if let Err(err) = &self { + eprintln!("{}", err); + } + self + } + fn abort_if_err(&self) { + if let Err(err) = &self { + eprintln!("{}", err); + sh_quit(1) + } + } +} pub trait Blame { /// Blame a span for a propagated error. This will convert a ShErr::Simple into a ShErr::Full /// This will also set the span on a ShErr::Builder - fn blame(self, span: Span) -> Self; + fn blame(self, input: String, span: Rc>) -> Self; /// If an error is propagated to this point, then attempt to blame a span. /// If the error in question has already blamed a span, don't overwrite it. /// Used as a last resort in higher level contexts in case an error somehow goes unblamed - fn try_blame(self, span: Span) -> Self; + fn try_blame(self, input: String, span: Rc>) -> Self; } impl From for ShErr { @@ -42,17 +88,17 @@ impl From for ShErr { } impl Blame for Result { - fn blame(self, span: Span) -> Self { + fn blame(self, input: String, span: Rc>) -> Self { if let Err(mut e) = self { - e.blame(span); + e.blame(input,span); Err(e) } else { self } } - fn try_blame(self, span: Span) -> Self { + fn try_blame(self, input: String, span: Rc>) -> Self { if let Err(mut e) = self { - e.try_blame(span); + e.try_blame(input,span); Err(e) } else { self @@ -60,7 +106,7 @@ impl Blame for Result { } } -#[derive(Debug,Clone)] +#[derive(Debug,Copy,Clone,PartialEq,Eq)] pub enum ShErrKind { IoErr, SyntaxErr, @@ -85,7 +131,7 @@ impl Default for ShErrKind { #[derive(Clone,Debug)] pub enum ShErr { Simple { kind: ShErrKind, message: String }, - Full { kind: ShErrKind, message: String, span: Span }, + Full { kind: ShErrKind, message: String, blame: BlamePair }, } impl ShErr { @@ -95,32 +141,35 @@ impl ShErr { pub fn io() -> Self { io::Error::last_os_error().into() } - pub fn full>(kind: ShErrKind, message: S, span: Span) -> Self { - Self::Full { kind, message: message.into(), span } + pub fn full>(kind: ShErrKind, message: S, input: String, span: Rc>) -> Self { + let blame = BlamePair::new(input.to_string(), span); + Self::Full { kind, message: message.into(), blame } } - pub fn try_blame(&mut self, blame: Span) { + pub fn try_blame(&mut self, input: String, span: Rc>) { + let blame_pair = BlamePair::new(input, span); match self { Self::Full {..} => { /* Do not overwrite */ } Self::Simple { kind, message } => { - *self = Self::Full { kind: core::mem::take(kind), message: core::mem::take(message), span: blame } + *self = Self::Full { kind: core::mem::take(kind), message: core::mem::take(message), blame: blame_pair } } } } - pub fn blame(&mut self, blame: Span) { + pub fn blame(&mut self, input: String, span: Rc>) { + let blame_pair = BlamePair::new(input, span); match self { - Self::Full { kind: _, message: _, span } => { - *span = blame; + Self::Full { kind: _, message: _, blame } => { + *blame = blame_pair; } Self::Simple { kind, message } => { - *self = Self::Full { kind: core::mem::take(kind), message: core::mem::take(message), span: blame } + *self = Self::Full { kind: core::mem::take(kind), message: core::mem::take(message), blame: blame_pair } } } } pub fn with_msg(&mut self, new_message: String) { match self { - Self::Full { kind: _, message, span: _ } => { + Self::Full { kind: _, message, blame: _ } => { *message = new_message } Self::Simple { kind: _, message } => { @@ -128,9 +177,19 @@ impl ShErr { } } } + pub fn kind(&self) -> ShErrKind { + match self { + ShErr::Simple { kind, message: _ } => { + *kind + } + ShErr::Full { kind, message: _, blame: _ } => { + *kind + } + } + } pub fn with_kind(&mut self, new_kind: ShErrKind) { match self { - Self::Full { kind, message: _, span: _ } => { + Self::Full { kind, message: _, blame: _ } => { *kind = new_kind } Self::Simple { kind, message: _ } => { @@ -141,7 +200,7 @@ impl ShErr { pub fn display_kind(&self) -> String { match self { ShErr::Simple { kind, message: _ } | - ShErr::Full { kind, message: _, span: _ } => { + ShErr::Full { kind, message: _, blame: _ } => { match kind { ShErrKind::IoErr => "I/O Error: ".into(), ShErrKind::SyntaxErr => "Syntax Error: ".into(), @@ -159,9 +218,31 @@ impl ShErr { } } } + pub fn get_line(&self) -> (usize,usize,String) { + if let ShErr::Full { kind, message, blame } = self { + unsafe { + let mut dist = 0; + let mut line_no = 0; + let window = self.get_window(); + let mut lines = window.lines(); + while let Some(line) = lines.next() { + line_no += 1; + dist += line.len(); + if dist > blame.start() { + dist -= line.len(); + let offset = blame.start() - dist; + return (offset,line_no,line.to_string()) + } + } + } + (0,0,String::new()) + } else { + (0,0,String::new()) + } + } pub fn get_window(&self) -> String { - if let ShErr::Full { kind: _, message: _, span } = self { - let window = span.get_slice(); + if let ShErr::Full { kind: _, message: _, blame } = self.clone() { + let window: String = blame.into(); window.split_once('\n').unwrap_or((&window,"")).0.to_string() } else { String::new() @@ -173,9 +254,10 @@ impl Display for ShErr { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let error_display = match self { ShErr::Simple { kind: _, message } => format!("{}{}",self.display_kind(),message), - ShErr::Full { kind: _, message, span } => { - let (offset,line_no,line_text) = span.get_line(); - let dist = span.end() - span.start(); + ShErr::Full { kind: _, message, blame } => { + let (offset,line_no,line_text) = self.get_line(); + log!(DEBUG, blame); + let dist = blame.len(); let padding = " ".repeat(offset); let line_inner = "~".repeat(dist.saturating_sub(2)); let err_kind = &self.display_kind().styled(Style::Red | Style::Bold); diff --git a/src/libsh/sys.rs b/src/libsh/sys.rs index 52465d8..6e82d2e 100644 --- a/src/libsh/sys.rs +++ b/src/libsh/sys.rs @@ -40,6 +40,31 @@ pub fn c_pipe() -> Result<(RawFd,RawFd),Errno> { Ok((pipes[0],pipes[1])) } +pub fn sh_quit(code: i32) -> ! { + write_jobs(|j| { + for job in j.jobs_mut().iter_mut().flatten() { + job.killpg(Signal::SIGTERM).ok(); + } + }); + exit(code); +} + +pub fn read_to_string(fd: i32) -> ShResult { + let mut buf = Vec::with_capacity(4096); + let mut temp_buf = [0u8;1024]; + + loop { + match read(fd, &mut temp_buf) { + Ok(0) => break, // EOF + Ok(n) => buf.extend_from_slice(&temp_buf[..n]), + Err(Errno::EINTR) => continue, // Retry on EINTR + Err(e) => return Err(e.into()), // Return other errors + } + } + + Ok(String::from_utf8_lossy(&buf).to_string()) +} + pub fn execvpe(cmd: String, argv: Vec, envp: Vec) -> Result<(),Errno> { let cmd_raw = CString::new(cmd).unwrap(); diff --git a/src/libsh/utils.rs b/src/libsh/utils.rs index a307177..cc0bcdb 100644 --- a/src/libsh/utils.rs +++ b/src/libsh/utils.rs @@ -3,10 +3,26 @@ use std::{os::fd::{AsFd, AsRawFd, BorrowedFd, FromRawFd, OwnedFd}, str::FromStr} use nix::libc::getpgrp; -use crate::{expand::{expand_vars::{expand_dquote, expand_var}, tilde::expand_tilde}, prelude::*}; +use crate::prelude::*; use super::term::StyleSet; +pub trait RedirTargetType { + fn as_tgt(self) -> RedirTarget; +} + +impl RedirTargetType for PathBuf { + fn as_tgt(self) -> RedirTarget { + RedirTarget::File(self) + } +} + +impl RedirTargetType for i32 { + fn as_tgt(self) -> RedirTarget { + RedirTarget::Fd(self) + } +} + pub trait StrOps { /// 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, @@ -31,7 +47,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 = trim_quotes(&arg); + let cleaned = trim_quotes(&arg.as_raw(shenv)); argv_processed.push(cleaned); } argv_processed @@ -248,6 +264,12 @@ impl Redir { pub fn new(src: i32, op: RedirType, tgt: RedirTarget) -> Self { Self { src, op, tgt } } + pub fn output(src: i32, tgt: impl RedirTargetType) -> Self { + Self::new(src, RedirType::Output, tgt.as_tgt()) + } + pub fn input(src: i32, tgt: impl RedirTargetType) -> Self { + Self::new(src, RedirType::Input, tgt.as_tgt()) + } } #[derive(Debug,Clone)] diff --git a/src/main.rs b/src/main.rs index 587b1ea..7f57785 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,8 +10,6 @@ pub mod prompt; pub mod builtin; pub mod expand; -use libc::PIPE_BUF; -use nix::unistd::setpgid; use signal::sig_setup; use crate::prelude::*; @@ -22,32 +20,13 @@ pub fn main() { loop { log!(TRACE, "Entered loop"); - let mut line = match prompt::read_line(&mut shenv) { + let 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) => { - eprintln!("{}",e); - } - Ok(syn_tree) => { - if let Err(e) = Executor::new(syn_tree, &mut shenv).walk() { - eprintln!("{}",e); - } - } - } - log!(TRACE, "Finished iteration"); + let _ = exec_input(line, &mut shenv).eprint(); } } diff --git a/src/parse/lex.rs b/src/parse/lex.rs index b9eac0e..7562264 100644 --- a/src/parse/lex.rs +++ b/src/parse/lex.rs @@ -1,66 +1,66 @@ -use std::fmt::{Debug, Display}; +use std::{cell::Ref, fmt::{Debug, Display}}; use crate::prelude::*; -pub const KEYWORDS: [&str;14] = [ - "if", - "then", - "elif", - "else", - "fi", - "while", - "until", - "for", - "in", - "select", - "do", - "done", - "case", - "esac" +pub const KEYWORDS: [TkRule;14] = [ + TkRule::If, + TkRule::Then, + TkRule::Elif, + TkRule::Else, + TkRule::Fi, + TkRule::While, + TkRule::Until, + TkRule::For, + TkRule::In, + TkRule::Select, + TkRule::Do, + TkRule::Done, + TkRule::Case, + TkRule::Esac ]; -pub const SEPARATORS: [TkRule; 7] = [ +pub const SEPARATORS: [TkRule; 6] = [ TkRule::Sep, TkRule::AndOp, TkRule::OrOp, TkRule::PipeOp, TkRule::ErrPipeOp, TkRule::BgOp, - TkRule::Keyword // Keywords don't count as command names ]; pub trait LexRule { fn try_match(input: &str) -> Option; } -pub struct Lexer { - input: Rc, +pub struct Lexer<'a> { + input: String, tokens: Vec, is_command: bool, + shenv: &'a mut ShEnv, consumed: usize } -impl Lexer { - pub fn new(input: Rc) -> Self { - Self { input, tokens: vec![], is_command: true, consumed: 0 } +impl<'a> Lexer<'a> { + pub fn new(input: String, shenv: &'a mut ShEnv) -> Self { + Self { input, tokens: vec![], is_command: true, shenv, consumed: 0 } } pub fn lex(mut self) -> Vec { unsafe { let mut input = self.input.as_str(); while let Some((mut rule,len)) = TkRule::try_match(input) { // If we see a keyword in an argument position, it's actually an ident - if !self.is_command && rule == TkRule::Keyword { + if !self.is_command && KEYWORDS.contains(&rule) { rule = TkRule::Ident // If we are in a command right now, after this we are in arguments - } else if self.is_command && !matches!(rule,TkRule::Keyword | TkRule::Whitespace) { + } else if self.is_command && rule != TkRule::Whitespace && !KEYWORDS.contains(&rule) { self.is_command = false; } // If we see a separator like && or ;, we are now in a command again if SEPARATORS.contains(&rule) { self.is_command = true; } - let span = Span::new(self.input.clone(),self.consumed,self.consumed + len); + let span = self.shenv.inputman_mut().new_span(self.consumed, self.consumed + len); let token = Token::new(rule, span); self.consumed += len; input = &input[len..]; @@ -77,73 +77,43 @@ impl Lexer { #[derive(Clone)] pub struct Token { rule: TkRule, - span: Span + span: Rc> } impl Token { - pub fn new(rule: TkRule, span: Span) -> Self { + pub fn new(rule: TkRule, span: Rc>) -> Self { Self { rule, span } } - pub fn span(&self) -> &Span { - &self.span - } - - pub fn span_mut(&mut self) -> &mut Span { - &mut self.span + pub fn span(&self) -> Rc> { + self.span.clone() } pub fn rule(&self) -> TkRule { self.rule } + + pub fn as_raw(&self, shenv: &mut ShEnv) -> String { + shenv.input_slice(self.span()).to_string() + } } 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); + let info = (self.rule(),self.span.borrow().start,self.span.borrow().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(); - write!(f,"{}",slice) - } -} - #[derive(Debug,Clone)] pub struct Span { - input: Rc, start: usize, end: usize } impl Span { - pub fn new(input: Rc, start: usize, end: usize) -> Self { - Self { input, start, end } } - pub fn get_slice(&self) -> String { - unsafe { - let slice = &self.input[self.start..self.end]; - slice.to_string() - } - } - pub fn get_line(&self) -> (usize,usize,String) { - unsafe { - let mut dist = 0; - let mut line_no = 0; - let mut lines = self.input.lines(); - while let Some(line) = lines.next() { - line_no += 1; - dist += line.len(); - if dist > self.start { - dist -= line.len(); - let offset = self.start - dist; - return (offset,line_no,line.to_string()) - } - } - } - (0,0,String::new()) + pub fn new(start: usize, end: usize) -> Self { + Self { start, end } } pub fn start(&self) -> usize { self.start @@ -151,8 +121,19 @@ impl Span { pub fn end(&self) -> usize { self.end } - pub fn get_input(&self) -> Rc { - self.input.clone() + pub fn clamp_start(&mut self, start: usize) { + if self.start > start { + self.start = start + } + } + pub fn clamp_end(&mut self, end: usize) { + if self.end > end { + self.end = end + } + } + pub fn shift(&mut self, delta: isize) { + self.start = self.start.saturating_add_signed(delta); + self.end = self.end.saturating_add_signed(delta); } } @@ -204,7 +185,20 @@ pub enum TkRule { CmdSub, DQuote, SQuote, - Keyword, + If, + Then, + Elif, + Else, + Fi, + While, + Until, + For, + In, + Select, + Do, + Done, + Case, + Esac, Assign, Ident, Sep, @@ -232,8 +226,21 @@ impl TkRule { try_match!(TildeSub,input); try_match!(Subshell,input); try_match!(Sep,input); - try_match!(Keyword,input); try_match!(Assign,input); + try_match!(If,input); + try_match!(Then,input); + try_match!(Elif,input); + try_match!(Else,input); + try_match!(Fi,input); + try_match!(While,input); + try_match!(Until,input); + try_match!(For,input); + try_match!(In,input); + try_match!(Select,input); + try_match!(Do,input); + try_match!(Done,input); + try_match!(Case,input); + try_match!(Esac,input); try_match!(Ident,input); None } @@ -371,17 +378,159 @@ tkrule_def!(OrOp, |input: &str| { } }); - -tkrule_def!(Keyword, |input: &str| { - for &kw in KEYWORDS.iter() { - if input.starts_with(kw) { - let len = kw.len(); - if input.chars().nth(len).map_or(true, |ch| ch.is_whitespace() || ch == ';') { - return Some(len); - } +tkrule_def!(If, |input: &str| { + if input.starts_with("if") { + match input.chars().nth(2) { + Some(ch) if ch.is_whitespace() || ch == ';' => Some(2), + Some(_) => None, + None => Some(2), // "if" is the entire input } + } else { + None + } +}); +tkrule_def!(Then, |input: &str| { + if input.starts_with("then") { + match input.chars().nth(4) { + Some(ch) if ch.is_whitespace() || ch == ';' => Some(4), + Some(_) => None, + None => Some(4), // "if" is the entire input + } + } else { + None + } +}); +tkrule_def!(Elif, |input: &str| { + if input.starts_with("elif") { + match input.chars().nth(4) { + Some(ch) if ch.is_whitespace() || ch == ';' => Some(4), + Some(_) => None, + None => Some(4), // "if" is the entire input + } + } else { + None + } +}); +tkrule_def!(Else, |input: &str| { + if input.starts_with("else") { + match input.chars().nth(4) { + Some(ch) if ch.is_whitespace() || ch == ';' => Some(4), + Some(_) => None, + None => Some(4), // "if" is the entire input + } + } else { + None + } +}); +tkrule_def!(Fi, |input: &str| { + if input.starts_with("fi") { + match input.chars().nth(2) { + Some(ch) if ch.is_whitespace() || ch == ';' => Some(2), + Some(_) => None, + None => Some(2), // "if" is the entire input + } + } else { + None + } +}); +tkrule_def!(While, |input: &str| { + if input.starts_with("while") { + match input.chars().nth(5) { + Some(ch) if ch.is_whitespace() || ch == ';' => Some(5), + Some(_) => None, + None => Some(5), // "if" is the entire input + } + } else { + None + } +}); +tkrule_def!(Until, |input: &str| { + if input.starts_with("until") { + match input.chars().nth(5) { + Some(ch) if ch.is_whitespace() || ch == ';' => Some(5), + Some(_) => None, + None => Some(5), // "if" is the entire input + } + } else { + None + } +}); +tkrule_def!(For, |input: &str| { + if input.starts_with("for") { + match input.chars().nth(3) { + Some(ch) if ch.is_whitespace() || ch == ';' => Some(3), + Some(_) => None, + None => Some(3), // "if" is the entire input + } + } else { + None + } +}); +tkrule_def!(In, |input: &str| { + if input.starts_with("in") { + match input.chars().nth(2) { + Some(ch) if ch.is_whitespace() || ch == ';' => Some(2), + Some(_) => None, + None => Some(2), // "if" is the entire input + } + } else { + None + } +}); +tkrule_def!(Select, |input: &str| { + if input.starts_with("select") { + match input.chars().nth(6) { + Some(ch) if ch.is_whitespace() || ch == ';' => Some(6), + Some(_) => None, + None => Some(6), // "if" is the entire input + } + } else { + None + } +}); +tkrule_def!(Do, |input: &str| { + if input.starts_with("do") { + match input.chars().nth(2) { + Some(ch) if ch.is_whitespace() || ch == ';' => Some(2), + Some(_) => None, + None => Some(2), // "if" is the entire input + } + } else { + None + } +}); +tkrule_def!(Done, |input: &str| { + if input.starts_with("done") { + match input.chars().nth(4) { + Some(ch) if ch.is_whitespace() || ch == ';' => Some(4), + Some(_) => None, + None => Some(4), // "if" is the entire input + } + } else { + None + } +}); +tkrule_def!(Case, |input: &str| { + if input.starts_with("case") { + match input.chars().nth(4) { + Some(ch) if ch.is_whitespace() || ch == ';' => Some(4), + Some(_) => None, + None => Some(4), // "if" is the entire input + } + } else { + None + } +}); +tkrule_def!(Esac, |input: &str| { + if input.starts_with("esac") { + match input.chars().nth(4) { + Some(ch) if ch.is_whitespace() || ch == ';' => Some(4), + Some(_) => None, + None => Some(4), // "if" is the entire input + } + } else { + None } - None }); tkrule_def!(Ident, |input: &str| { @@ -717,6 +866,14 @@ tkrule_def!(BraceGrp, |input: &str| { }); tkrule_def!(RedirOp, |input: &str| { + if let Some(ch) = input.chars().next() { + match ch { + '>' | + '<' | + '&' => { /* Continue */ } + _ => return None + } + } // Order matters here // For instance, if '>' is checked before '>>', '>' will always match first, and '>>' will never be checked try_match_inner!(RedirCombineAppend,input); // Ex: &>> diff --git a/src/parse/parse.rs b/src/parse/parse.rs index 0e807be..522cdbe 100644 --- a/src/parse/parse.rs +++ b/src/parse/parse.rs @@ -1,9 +1,9 @@ use core::fmt::Display; -use std::str::FromStr; +use std::{cell::Ref, str::FromStr}; use crate::prelude::*; -use super::lex::{TkRule, Span, Token}; +use super::lex::{Span, TkRule, Token, KEYWORDS}; bitflags! { #[derive(Debug,Clone,Copy,PartialEq,Eq)] @@ -17,12 +17,12 @@ bitflags! { pub trait ParseRule { /// Used for cases where a rule is optional - fn try_match(input: &[Token]) -> ShResult>; + fn try_match(input: &[Token], shenv: &mut ShEnv) -> ShResult>; /// Used for cases where a rule is assumed based on context /// For instance, if the "for" keyword is encountered, then it *must* be a for loop /// And if it isn't, return a parse error - fn assert_match(input: &[Token]) -> ShResult { - Self::try_match(input)?.ok_or_else(|| + fn assert_match(input: &[Token], shenv: &mut ShEnv) -> ShResult { + Self::try_match(input,shenv)?.ok_or_else(|| ShErr::simple(ShErrKind::ParseErr, "Parse Error") ) } @@ -40,7 +40,7 @@ pub enum CmdGuard { pub struct Node { node_rule: NdRule, tokens: Vec, - span: Span, + span: Rc>, flags: NdFlag, } @@ -60,9 +60,12 @@ impl Node { pub fn into_rule(self) -> NdRule { self.node_rule } - pub fn span(&self) -> Span { + pub fn span(&self) -> Rc> { self.span.clone() } + pub fn as_raw(&self, shenv: &mut ShEnv) -> String { + shenv.input_slice(self.span()).to_string() + } pub fn flags(&self) -> NdFlag { self.flags } @@ -71,11 +74,10 @@ impl Node { } } -impl Display for Node { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - let raw = self.span().get_slice(); - write!(f, "{}", raw) - } +#[derive(Clone,Debug)] +pub enum LoopKind { + While, + Until } #[derive(Clone,Debug)] @@ -84,6 +86,9 @@ pub enum NdRule { Command { argv: Vec, redirs: Vec }, Assignment { assignments: Vec, cmd: Option> }, FuncDef { name: Token, body: Token }, + IfThen { cond_blocks: Vec<(Vec,Vec)>, else_block: Option> }, + Loop { kind: LoopKind, cond: Vec, body: Vec }, + ForLoop { vars: Vec, arr: Vec, body: Vec }, Subshell { body: Token, argv: Vec, redirs: Vec }, CmdList { cmds: Vec<(Option,Node)> }, Pipeline { cmds: Vec } @@ -91,12 +96,12 @@ pub enum NdRule { /// Define a Node rule. The body of this macro becomes the implementation for the try_match() method for the rule. macro_rules! ndrule_def { - ($name:ident,$try:expr) => { + ($name:ident,$shenv:ident,$try:expr) => { #[derive(Debug)] pub struct $name; impl ParseRule for $name { - fn try_match(input: &[Token]) -> ShResult> { - $try(input) + fn try_match(input: &[Token],shenv: &mut ShEnv) -> ShResult> { + $try(input,shenv) } } }; @@ -105,9 +110,9 @@ macro_rules! ndrule_def { /// This macro attempts to match all of the given Rules. It returns upon finding the first match, so the order matters /// Place the most specialized/specific rules first, and the most general rules last macro_rules! try_rules { - ($tokens:expr, $($name:ident),+) => { + ($tokens:expr,$shenv:expr,$($name:ident),+) => { $( - let result = $name::try_match($tokens)?; + let result = $name::try_match($tokens,$shenv)?; if let Some(node) = result { return Ok(Some(node)) } @@ -125,6 +130,9 @@ impl SynTree { pub fn new() -> Self { Self { tree: VecDeque::new() } } + pub fn from_vec(nodes: Vec) -> Self { + Self { tree: VecDeque::from(nodes) } + } pub fn push_node(&mut self, node: Node) { self.tree.bpush(node) } @@ -133,16 +141,17 @@ impl SynTree { } } -pub struct Parser { +pub struct Parser<'a> { token_stream: Vec, + shenv: &'a mut ShEnv, ast: SynTree } -impl Parser { - pub fn new(mut token_stream: Vec) -> Self { +impl<'a> Parser<'a> { + pub fn new(mut token_stream: Vec, shenv: &'a mut ShEnv) -> Self { log!(TRACE, "New parser"); token_stream.retain(|tk| !matches!(tk.rule(), TkRule::Whitespace | TkRule::Comment)); - Self { token_stream, ast: SynTree::new() } + Self { token_stream, shenv, ast: SynTree::new() } } pub fn parse(mut self) -> ShResult { @@ -150,11 +159,10 @@ impl Parser { let mut lists = VecDeque::new(); let token_slice = &*self.token_stream; // Get the Main rule - if let Some(mut node) = Main::try_match(token_slice)? { + if let Some(mut node) = Main::try_match(token_slice,self.shenv)? { // Extract the inner lists if let NdRule::Main { ref mut cmd_lists } = node.rule_mut() { while let Some(node) = cmd_lists.pop() { - log!(DEBUG, node); lists.bpush(node) } } @@ -167,26 +175,42 @@ impl Parser { } } -fn get_span(toks: &Vec) -> ShResult { +fn get_span(toks: &Vec, shenv: &mut ShEnv) -> ShResult>> { if toks.is_empty() { Err(ShErr::simple(ShErrKind::InternalErr, "Get_span was given an empty token list")) } else { - let start = toks.first().unwrap().span().start(); - let end = toks.iter().last().unwrap().span().end(); - let input = toks.iter().last().unwrap().span().get_input(); - Ok(Span::new(input,start,end)) + let start = toks.first().unwrap().span().borrow().start(); + let end = toks.iter().last().unwrap().span().borrow().end(); + let span = shenv.inputman_mut().new_span(start, end); + Ok(span) } } +fn get_lists(mut tokens: &[Token], shenv: &mut ShEnv) -> (usize,Vec) { + let mut lists = vec![]; + let mut tokens_eaten = 0; + while !tokens.is_empty() { + match CmdList::try_match(tokens, shenv) { + Ok(Some(list)) => { + tokens_eaten += list.len(); + tokens = &tokens[list.len()..]; + lists.push(list); + } + Ok(None) | Err(_) => break + } + } + (tokens_eaten,lists) +} + // TODO: Redirs with FD sources appear to be looping endlessly for some reason -ndrule_def!(Main, |tokens: &[Token]| { +ndrule_def!(Main, shenv, |tokens: &[Token], shenv: &mut ShEnv| { log!(TRACE, "Parsing main"); let mut cmd_lists = vec![]; let mut node_toks = vec![]; let mut token_slice = &*tokens; - while let Some(node) = CmdList::try_match(token_slice)? { + while let Some(node) = CmdList::try_match(token_slice,shenv)? { node_toks.extend(node.tokens().clone()); token_slice = &token_slice[node.len()..]; cmd_lists.push(node); @@ -195,7 +219,7 @@ ndrule_def!(Main, |tokens: &[Token]| { if cmd_lists.is_empty() { return Ok(None) } - let span = get_span(&node_toks)?; + let span = get_span(&node_toks,shenv)?; let node = Node { node_rule: NdRule::Main { cmd_lists }, tokens: node_toks, @@ -205,14 +229,14 @@ ndrule_def!(Main, |tokens: &[Token]| { Ok(Some(node)) }); -ndrule_def!(CmdList, |tokens: &[Token]| { +ndrule_def!(CmdList, shenv, |tokens: &[Token], shenv: &mut ShEnv| { log!(TRACE, "Parsing cmdlist"); let mut commands: Vec<(Option,Node)> = vec![]; let mut node_toks = vec![]; let mut token_slice = &*tokens; let mut cmd_guard = None; // Operators like '&&' and '||' - while let Some(mut node) = Expr::try_match(token_slice)? { + while let Some(mut node) = Expr::try_match(token_slice,shenv)? { // Add sub-node tokens to our tokens node_toks.extend(node.tokens().clone()); // Reflect changes in the token slice @@ -221,8 +245,11 @@ ndrule_def!(CmdList, |tokens: &[Token]| { log!(DEBUG, token_slice); // Push sub-node if let NdRule::Command { argv, redirs: _ } = node.rule() { - if argv.first().is_some_and(|arg| BUILTINS.contains(&arg.to_string().as_str())) { - *node.flags_mut() |= NdFlag::BUILTIN; + if let Some(arg) = argv.first() { + let slice = shenv.input_slice(arg.span().clone()); + if BUILTINS.contains(&slice) { + *node.flags_mut() |= NdFlag::BUILTIN; + } } } commands.push((cmd_guard.take(),node)); @@ -244,7 +271,7 @@ ndrule_def!(CmdList, |tokens: &[Token]| { if node_toks.is_empty() { return Ok(None) } - let span = get_span(&node_toks)?; + let span = get_span(&node_toks,shenv)?; let node = Node { node_rule: NdRule::CmdList { cmds: commands }, tokens: node_toks, @@ -254,8 +281,8 @@ ndrule_def!(CmdList, |tokens: &[Token]| { Ok(Some(node)) }); -ndrule_def!(Expr, |tokens: &[Token]| { - try_rules!(tokens, +ndrule_def!(Expr, shenv, |tokens: &[Token], shenv: &mut ShEnv| { + try_rules!(tokens, shenv, ShellCmd, Pipeline, Subshell, @@ -264,8 +291,8 @@ ndrule_def!(Expr, |tokens: &[Token]| { ); }); // Used in pipelines to avoid recursion -ndrule_def!(ExprNoPipeline, |tokens: &[Token]| { - try_rules!(tokens, +ndrule_def!(ExprNoPipeline, shenv, |tokens: &[Token], shenv: &mut ShEnv| { + try_rules!(tokens, shenv, ShellCmd, Subshell, Assignment, @@ -273,13 +300,352 @@ ndrule_def!(ExprNoPipeline, |tokens: &[Token]| { ); }); -ndrule_def!(ShellCmd, |tokens: &[Token]| { - try_rules!(tokens, +ndrule_def!(ShellCmd, shenv, |tokens: &[Token], shenv: &mut ShEnv| { + try_rules!(tokens, shenv, + IfThen, + Loop, FuncDef ); }); -ndrule_def!(FuncDef, |tokens: &[Token]| { +ndrule_def!(ForLoop, shenv, |mut tokens: &[Token], shenv: &mut ShEnv| { + let err = |msg: &str, span: Rc>, shenv: &mut ShEnv | { + ShErr::full(ShErrKind::ParseErr, msg, shenv.get_input(), span) + }; + let mut tokens_iter = tokens.iter().peekable(); + let mut node_toks = vec![]; + let mut vars = vec![]; + let mut arr = vec![]; + let body: Vec; + + if let Some(token) = tokens_iter.next() { + if let TkRule::For = token.rule() { + node_toks.push(token.clone()); + tokens = &tokens[1..]; + } else { return Ok(None) } + } else { return Ok(None) } + + while let Some(token) = tokens_iter.next() { + if let TkRule::Ident = token.rule() { + node_toks.push(token.clone()); + tokens = &tokens[1..]; + if token.as_raw(shenv) == "in" { break } + vars.push(token.clone()); + } else { + let span = get_span(&node_toks, shenv)?; + return Err(err("Expected an ident in for loop vars",span,shenv)) + } + } + if vars.is_empty() { + let span = get_span(&node_toks, shenv)?; + return Err(err("Expected an ident in for loop vars",span,shenv)) + } + while let Some(token) = tokens_iter.next() { + if let TkRule::Ident = token.rule() { + node_toks.push(token.clone()); + tokens = &tokens[1..]; + if token.rule() == TkRule::Sep { break } + arr.push(token.clone()); + } else { + let span = get_span(&node_toks, shenv)?; + return Err(err("Expected an ident in for loop array",span,shenv)) + } + } + if arr.is_empty() { + let span = get_span(&node_toks, shenv)?; + return Err(err("Expected an ident in for loop array",span,shenv)) + } + + if let Some(token) = tokens_iter.next() { + node_toks.push(token.clone()); + tokens = &tokens[1..]; + if token.rule() != TkRule::Do { + let span = get_span(&node_toks, shenv)?; + return Err(err("Expected `do` after for loop array",span,shenv)) + } + } else { + let span = get_span(&node_toks, shenv)?; + return Err(err("Expected `do` after for loop array",span,shenv)) + } + + let (used,lists) = get_lists(tokens, shenv); + for list in &lists { + node_toks.extend(list.tokens().clone()); + } + tokens = &tokens[used..]; + body = lists; + tokens_iter = tokens.iter().peekable(); + + if let Some(token) = tokens_iter.next() { + node_toks.push(token.clone()); + if token.rule() != TkRule::Done { + let span = get_span(&node_toks, shenv)?; + return Err(err("Expected `done` after for loop",span,shenv)) + } + } else { + let span = get_span(&node_toks, shenv)?; + return Err(err("Expected `done` after for loop",span,shenv)) + } + + let span = get_span(&node_toks, shenv)?; + let node = Node { + node_rule: NdRule::ForLoop { vars, arr, body }, + tokens: node_toks, + span, + flags: NdFlag::empty() + }; + + Ok(Some(node)) +}); + +ndrule_def!(IfThen, shenv, |mut tokens: &[Token], shenv: &mut ShEnv| { + let err = |msg: &str, span: Rc>, shenv: &mut ShEnv | { + ShErr::full(ShErrKind::ParseErr, msg, shenv.get_input(), span) + }; + let mut tokens_iter = tokens.iter().peekable(); + let mut node_toks = vec![]; + let mut cond_blocks = vec![]; + let mut else_block: Option> = None; + + if let Some(token) = tokens_iter.next() { + if let TkRule::If = token.rule() { + node_toks.push(token.clone()); + tokens = &tokens[1..]; + } else { return Ok(None) } + } else { return Ok(None) } + + let (used,lists) = get_lists(tokens, shenv); + for list in &lists { + node_toks.extend(list.tokens().clone()); + } + tokens = &tokens[used..]; + let cond = lists; + tokens_iter = tokens.iter().peekable(); + + while let Some(token) = tokens_iter.next() { + match token.rule() { + TkRule::Then => { + node_toks.push(token.clone()); + tokens = &tokens[1..]; + break + } + TkRule::Sep | TkRule::Whitespace => { + node_toks.push(token.clone()); + tokens = &tokens[1..]; + } + _ => { + let span = get_span(&node_toks,shenv)?; + return Err(err("Expected `then` after if statement condition",span,shenv)) + } + } + } + + if tokens_iter.peek().is_none() { + let span = get_span(&node_toks,shenv)?; + return Err(err("Failed to parse this if statement",span,shenv)) + } + + let (used,lists) = get_lists(tokens, shenv); + for list in &lists { + node_toks.extend(list.tokens().clone()); + } + tokens = &tokens[used..]; + let body = lists; + tokens_iter = tokens.iter().peekable(); + cond_blocks.push((cond,body)); + + + let mut closed = false; + while let Some(token) = tokens_iter.next() { + match token.rule() { + TkRule::Elif => { + node_toks.push(token.clone()); + tokens = &tokens[1..]; + + let (used,lists) = get_lists(tokens, shenv); + for list in &lists { + node_toks.extend(list.tokens().clone()); + } + tokens = &tokens[used..]; + let cond = lists; + tokens_iter = tokens.iter().peekable(); + + while let Some(token) = tokens_iter.next() { + match token.rule() { + TkRule::Then => { + node_toks.push(token.clone()); + tokens = &tokens[1..]; + break + } + TkRule::Sep | TkRule::Whitespace => { + node_toks.push(token.clone()); + tokens = &tokens[1..]; + } + _ => { + let span = get_span(&node_toks,shenv)?; + return Err(err("Expected `then` after if statement condition",span,shenv)) + } + } + } + + if tokens_iter.peek().is_none() { + let span = get_span(&node_toks,shenv)?; + return Err(err("Failed to parse this if statement",span,shenv)) + } + + let (used,lists) = get_lists(tokens, shenv); + for list in &lists { + node_toks.extend(list.tokens().clone()); + } + tokens = &tokens[used..]; + let body = lists; + tokens_iter = tokens.iter().peekable(); + cond_blocks.push((cond,body)); + } + TkRule::Else => { + node_toks.push(token.clone()); + tokens = &tokens[1..]; + let (used,lists) = get_lists(tokens, shenv); + for list in &lists { + node_toks.extend(list.tokens().clone()); + } + tokens = &tokens[used..]; + else_block = Some(lists); + tokens_iter = tokens.iter().peekable(); + } + TkRule::Fi => { + closed = true; + node_toks.push(token.clone()); + tokens = &tokens[1..]; + } + TkRule::Sep | TkRule::Whitespace => { + node_toks.push(token.clone()); + tokens = &tokens[1..]; + if closed { break } + } + _ => { + let span = get_span(&node_toks, shenv)?; + return Err(err("Unexpected token in if statement",span,shenv)) + } + } + } + + if !closed { + let span = get_span(&node_toks, shenv)?; + return Err(err("Expected `fi` to close if statement",span,shenv)) + } + + let span = get_span(&node_toks, shenv)?; + let node = Node { + node_rule: NdRule::IfThen { cond_blocks, else_block }, + tokens: node_toks, + span, + flags: NdFlag::empty() + }; + + Ok(Some(node)) +}); + +ndrule_def!(Loop, shenv, |mut tokens: &[Token], shenv: &mut ShEnv| { + let err = |msg: &str, span: Rc>, shenv: &mut ShEnv | { + ShErr::full(ShErrKind::ParseErr, msg, shenv.get_input(), span) + }; + let mut tokens_iter = tokens.iter().peekable(); + let mut node_toks = vec![]; + let kind: LoopKind; + let cond: Vec; + let body: Vec; + + if let Some(token) = tokens_iter.next() { + node_toks.push(token.clone()); + match token.rule() { + TkRule::While => { + kind = LoopKind::While + } + TkRule::Until => { + kind = LoopKind::Until + } + _ => return Ok(None) + } + } else { return Ok(None) } + tokens = &tokens[1..]; + + let (used,lists) = get_lists(tokens, shenv); + for list in &lists { + node_toks.extend(list.tokens().clone()); + } + tokens = &tokens[used..]; + cond = lists; + tokens_iter = tokens.iter().peekable(); + + while let Some(token) = tokens_iter.next() { + match token.rule() { + TkRule::Sep | TkRule::Whitespace => { + node_toks.push(token.clone()); + tokens = &tokens[1..]; + } + TkRule::Do => { + node_toks.push(token.clone()); + tokens = &tokens[1..]; + break + } + _ => { + let span = get_span(&node_toks,shenv)?; + return Err(err("Expected `do` after loop condition",span,shenv)) + } + } + } + + if tokens_iter.peek().is_none() { + return Ok(None) + } + + let (used,lists) = get_lists(tokens, shenv); + for list in &lists { + node_toks.extend(list.tokens().clone()); + } + tokens = &tokens[used..]; + body = lists; + tokens_iter = tokens.iter().peekable(); + + let mut closed = false; + while let Some(token) = tokens_iter.next() { + match token.rule() { + TkRule::Sep | TkRule::Whitespace => { + node_toks.push(token.clone()); + tokens = &tokens[1..]; + if closed { break } + } + TkRule::Done => { + closed = true; + node_toks.push(token.clone()); + tokens = &tokens[1..]; + } + _ => { + let span = get_span(&node_toks,shenv)?; + return Err(err("Unexpected token in loop",span,shenv)) + } + } + } + + if !closed { + let span = get_span(&node_toks,shenv)?; + return Err(err("Expected `done` to close loop",span,shenv)) + } + + let span = get_span(&node_toks, shenv)?; + let node = Node { + node_rule: NdRule::Loop { kind, cond, body }, + tokens: node_toks, + span, + flags: NdFlag::empty() + }; + + log!(DEBUG, node); + Ok(Some(node)) +}); + +ndrule_def!(FuncDef, shenv, |tokens: &[Token], shenv: &mut ShEnv| { let mut tokens_iter = tokens.iter(); let mut node_toks = vec![]; let name: Token; @@ -307,7 +673,7 @@ ndrule_def!(FuncDef, |tokens: &[Token]| { return Ok(None) } - let span = get_span(&node_toks)?; + let span = get_span(&node_toks,shenv)?; let node = Node { node_rule: NdRule::FuncDef { name, body }, tokens: node_toks, @@ -317,7 +683,7 @@ ndrule_def!(FuncDef, |tokens: &[Token]| { Ok(Some(node)) }); -ndrule_def!(Subshell, |tokens: &[Token]| { +ndrule_def!(Subshell, shenv, |tokens: &[Token], shenv: &mut ShEnv| { let mut tokens_iter = tokens.iter(); let mut node_toks = vec![]; let mut argv = vec![]; @@ -350,27 +716,29 @@ ndrule_def!(Subshell, |tokens: &[Token]| { TkRule::RedirOp => { node_toks.push(token.clone()); // Get the raw redirection text, e.g. "1>&2" or "2>" or ">>" or something - let redir_raw = token.span().get_slice(); + let redir_raw = shenv.input_slice(token.span()); let mut redir_bldr = RedirBldr::from_str(&redir_raw).unwrap(); // If there isn't an FD target, get the next token and use it as the filename if redir_bldr.tgt().is_none() { if let Some(filename) = tokens_iter.next() { // Make sure it's a word and not an operator or something - if !matches!(filename.rule(), TkRule::SQuote | TkRule::DQuote | TkRule::Ident | TkRule::Keyword) { + if !matches!(filename.rule(), TkRule::SQuote | TkRule::DQuote | TkRule::Ident) || KEYWORDS.contains(&filename.rule()) { let mut err = ShErr::simple(ShErrKind::ParseErr, "Did not find a target for this redirection"); - err.blame(token.span().clone()); + let input = shenv.input_slice(token.span()).to_string(); + err.blame(input, token.span()); return Err(err) } node_toks.push(filename.clone()); // Construct the Path object - let filename_raw = filename.span().get_slice(); + let filename_raw = shenv.input_slice(filename.span()).to_string(); let filename_path = PathBuf::from(filename_raw); let tgt = RedirTarget::File(filename_path); // Update the builder redir_bldr = redir_bldr.with_tgt(tgt); } else { let mut err = ShErr::simple(ShErrKind::ParseErr, "Did not find a target for this redirection"); - err.blame(token.span().clone()); + let input = shenv.input_slice(token.span()).to_string(); + err.blame(input, token.span()); return Err(err) } } @@ -379,7 +747,7 @@ ndrule_def!(Subshell, |tokens: &[Token]| { _ => break } } - let span = get_span(&node_toks)?; + let span = get_span(&node_toks,shenv)?; let node = Node { node_rule: NdRule::Subshell { body, argv, redirs }, tokens: node_toks, @@ -394,7 +762,7 @@ ndrule_def!(Subshell, |tokens: &[Token]| { Ok(None) }); -ndrule_def!(Pipeline, |mut tokens: &[Token]| { +ndrule_def!(Pipeline, shenv, |mut tokens: &[Token], shenv: &mut ShEnv| { log!(TRACE, "Parsing pipeline"); let mut tokens_iter = tokens.iter().peekable(); let mut node_toks = vec![]; @@ -411,7 +779,7 @@ ndrule_def!(Pipeline, |mut tokens: &[Token]| { } _ => { /* Keep going */ } } - if let Some(mut cmd) = ExprNoPipeline::try_match(tokens)? { + if let Some(mut cmd) = ExprNoPipeline::try_match(tokens,shenv)? { // Add sub-node's tokens to our tokens node_toks.extend(cmd.tokens().clone()); @@ -422,8 +790,11 @@ ndrule_def!(Pipeline, |mut tokens: &[Token]| { } if let NdRule::Command { argv, redirs: _ } = cmd.rule() { - if argv.first().is_some_and(|arg| BUILTINS.contains(&arg.to_string().as_str())) { - *cmd.flags_mut() |= NdFlag::BUILTIN; + if let Some(arg) = argv.first() { + let slice = shenv.input_slice(arg.span().clone()); + if BUILTINS.contains(&slice) { + *cmd.flags_mut() |= NdFlag::BUILTIN; + } } } // Push sub-node @@ -458,7 +829,7 @@ ndrule_def!(Pipeline, |mut tokens: &[Token]| { if node_toks.is_empty() { return Ok(None) } - let span = get_span(&node_toks)?; + let span = get_span(&node_toks,shenv)?; let node = Node { node_rule: NdRule::Pipeline { cmds }, tokens: node_toks, @@ -468,7 +839,7 @@ ndrule_def!(Pipeline, |mut tokens: &[Token]| { Ok(Some(node)) }); -ndrule_def!(Command, |tokens: &[Token]| { +ndrule_def!(Command, shenv, |tokens: &[Token], shenv: &mut ShEnv| { log!(TRACE, "Parsing command"); let mut tokens = tokens.iter().peekable(); let mut node_toks = vec![]; @@ -495,40 +866,49 @@ ndrule_def!(Command, |tokens: &[Token]| { } TkRule::RedirOp => { // Get the raw redirection text, e.g. "1>&2" or "2>" or ">>" or something - let redir_raw = token.span().get_slice(); + let redir_raw = shenv.input_slice(token.span()).to_string(); let mut redir_bldr = RedirBldr::from_str(&redir_raw).unwrap(); // If there isn't an FD target, get the next token and use it as the filename if redir_bldr.tgt().is_none() { if let Some(filename) = tokens.next() { // Make sure it's a word and not an operator or something - if !matches!(filename.rule(), TkRule::SQuote | TkRule::DQuote | TkRule::Ident | TkRule::Keyword) { + if !matches!(filename.rule(), TkRule::SQuote | TkRule::DQuote | TkRule::Ident) || KEYWORDS.contains(&filename.rule()) { let mut err = ShErr::simple(ShErrKind::ParseErr, "Did not find a target for this redirection"); - err.blame(token.span().clone()); + let input = shenv.input_slice(token.span()).to_string(); + err.blame(input, token.span()); return Err(err) } node_toks.push(filename.clone()); // Construct the Path object - let filename_raw = filename.span().get_slice(); + let filename_raw = shenv.input_slice(filename.span()).to_string(); let filename_path = PathBuf::from(filename_raw); let tgt = RedirTarget::File(filename_path); // Update the builder redir_bldr = redir_bldr.with_tgt(tgt); } else { let mut err = ShErr::simple(ShErrKind::ParseErr, "Did not find a target for this redirection"); - err.blame(token.span().clone()); + let input = shenv.input_slice(token.span()).to_string(); + err.blame(input, token.span()); return Err(err) } } redirs.push(redir_bldr.build()); } TkRule::Sep => break, - _ => unreachable!("Found this rule: {:?}", token.rule()) + _ => return Err( + ShErr::full( + ShErrKind::ParseErr, + format!("Unexpected token in command rule: {:?}", token.rule()), + shenv.get_input(), + get_span(&node_toks,shenv)? + ) + ) } } if node_toks.is_empty() { return Ok(None) } - let span = get_span(&node_toks)?; + let span = get_span(&node_toks,shenv)?; if !argv.is_empty() { let node = Node { node_rule: NdRule::Command { argv, redirs }, @@ -542,7 +922,7 @@ ndrule_def!(Command, |tokens: &[Token]| { } }); -ndrule_def!(Assignment, |tokens: &[Token]| { +ndrule_def!(Assignment, shenv, |tokens: &[Token], shenv: &mut ShEnv| { log!(TRACE, "Parsing assignment"); let mut tokens = tokens.into_iter().peekable(); let mut node_toks = vec![]; @@ -559,11 +939,11 @@ ndrule_def!(Assignment, |tokens: &[Token]| { if tokens.peek().is_some() { let tokens_vec: Vec = tokens.into_iter().map(|token| token.clone()).collect(); let tokens_slice = &tokens_vec; - let cmd = Command::try_match(tokens_slice)?.map(|cmd| Box::new(cmd)); + let cmd = Command::try_match(tokens_slice,shenv)?.map(|cmd| Box::new(cmd)); if let Some(ref cmd) = cmd { node_toks.extend(cmd.tokens().clone()); } - let span = get_span(&node_toks)?; + let span = get_span(&node_toks,shenv)?; let node = Node { node_rule: NdRule::Assignment { assignments, cmd }, tokens: node_toks, @@ -572,7 +952,7 @@ ndrule_def!(Assignment, |tokens: &[Token]| { }; return Ok(Some(node)) } else { - let span = get_span(&node_toks)?; + let span = get_span(&node_toks,shenv)?; let node = Node { node_rule: NdRule::Assignment { assignments, cmd: None }, tokens: node_toks, diff --git a/src/prelude.rs b/src/prelude.rs index d40286b..c2d1bfb 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -97,12 +97,15 @@ pub use crate::{ }, sys::{ self, + sh_quit, + read_to_string, write_err, write_out, c_pipe, execvpe }, error::{ + ResultExt, ShErrKind, ShErr, ShResult @@ -114,6 +117,7 @@ pub use crate::{ pwd::pwd, read::read_builtin, alias::alias, + control_flow::sh_flow, jobctl::{ continue_job, jobs @@ -140,7 +144,10 @@ pub use crate::{ exec_ctx::ExecFlags, shenv::ShEnv }, - execute::Executor, + execute::{ + exec_input, + Executor, + }, parse::{ parse::{ Node, diff --git a/src/prompt/highlight.rs b/src/prompt/highlight.rs index 2582141..b270b6f 100644 --- a/src/prompt/highlight.rs +++ b/src/prompt/highlight.rs @@ -1,19 +1,22 @@ use rustyline::highlight::Highlighter; use sys::get_bin_path; -use crate::prelude::*; +use crate::{parse::lex::KEYWORDS, prelude::*}; use super::readline::SynHelper; impl<'a> Highlighter for SynHelper<'a> { fn highlight<'l>(&self, line: &'l str, pos: usize) -> std::borrow::Cow<'l, str> { + let mut shenv_clone = self.shenv.clone(); + shenv_clone.new_input(line); + let mut result = String::new(); - let mut tokens = Lexer::new(Rc::new(line.to_string())).lex().into_iter(); + let mut tokens = Lexer::new(line.to_string(),&mut shenv_clone).lex().into_iter(); let mut is_command = true; let mut in_array = false; while let Some(token) = tokens.next() { - let raw = token.to_string(); + let raw = token.as_raw(&mut shenv_clone); match token.rule() { TkRule::Comment => { let styled = &raw.styled(Style::BrightBlack); @@ -35,7 +38,7 @@ impl<'a> Highlighter for SynHelper<'a> { let rebuilt = format!("{styled}()"); result.push_str(&rebuilt); } - TkRule::Keyword => { + _ if KEYWORDS.contains(&token.rule()) => { if &raw == "for" { in_array = true; } @@ -76,7 +79,7 @@ impl<'a> Highlighter for SynHelper<'a> { result.push_str(&raw); } else if is_command { - if get_bin_path(&token.to_string(), self.shenv).is_some() || + if get_bin_path(&token.as_raw(&mut shenv_clone), 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()) { diff --git a/src/prompt/mod.rs b/src/prompt/mod.rs index dee0be3..037e67b 100644 --- a/src/prompt/mod.rs +++ b/src/prompt/mod.rs @@ -4,6 +4,7 @@ use rustyline::{config::Configurer, history::{DefaultHistory, History}, ColorMod pub mod readline; pub mod highlight; +pub mod validate; fn init_rl<'a>(shenv: &'a mut ShEnv) -> Editor, DefaultHistory> { let hist_path = std::env::var("FERN_HIST").unwrap_or_default(); @@ -25,7 +26,7 @@ fn init_rl<'a>(shenv: &'a mut ShEnv) -> Editor, DefaultHistory> { editor } -pub fn read_line<'a>(shenv: &'a mut ShEnv) -> ShResult { +pub fn read_line(shenv: &mut ShEnv) -> ShResult { log!(TRACE, "Entering prompt"); let prompt = "$ ".styled(Style::Green | Style::Bold); let mut editor = init_rl(shenv); diff --git a/src/prompt/readline.rs b/src/prompt/readline.rs index a30c0f2..94e146d 100644 --- a/src/prompt/readline.rs +++ b/src/prompt/readline.rs @@ -33,11 +33,6 @@ impl<'a> SynHelper<'a> { } } -impl<'a> Validator for SynHelper<'a> { - fn validate(&self, ctx: &mut rustyline::validate::ValidationContext) -> rustyline::Result { - Ok(ValidationResult::Valid(None)) - } -} impl<'a> Completer for SynHelper<'a> { diff --git a/src/prompt/validate.rs b/src/prompt/validate.rs new file mode 100644 index 0000000..ff3a8a1 --- /dev/null +++ b/src/prompt/validate.rs @@ -0,0 +1,115 @@ +use rustyline::validate::{ValidationResult, Validator}; + +use crate::prelude::*; + +use super::readline::SynHelper; + +pub fn check_delims(line: &str) -> bool { + let mut delim_stack = vec![]; + let mut chars = line.chars(); + let mut in_quote = None; // Tracks which quote type is open (`'` or `"`) + + while let Some(ch) = chars.next() { + match ch { + '{' | '(' | '[' if in_quote.is_none() => delim_stack.push(ch), + '}' if in_quote.is_none() && delim_stack.pop() != Some('{') => return false, + ')' if in_quote.is_none() && delim_stack.pop() != Some('(') => return false, + ']' if in_quote.is_none() && delim_stack.pop() != Some('[') => return false, + '"' | '\'' => { + if in_quote == Some(ch) { + in_quote = None; + } else if in_quote.is_none() { + in_quote = Some(ch); + } + } + '\\' => { chars.next(); } // Skip next character if escaped + _ => {} + } + } + + delim_stack.is_empty() && in_quote.is_none() +} + +pub fn check_keywords(line: &str, shenv: &mut ShEnv) -> bool { + use TkRule::*; + let mut expecting: Vec> = vec![]; + let mut tokens = Lexer::new(line.to_string(),shenv).lex().into_iter(); + + while let Some(token) = tokens.next() { + match token.rule() { + If => { + expecting.push(vec![Then]); + } + Then => { + if let Some(frame) = expecting.pop() { + if frame.contains(&Then) { + expecting.push(vec![Elif, Else, Fi]) + } else { return false } + } else { return false } + } + Elif => { + if let Some(frame) = expecting.pop() { + if frame.contains(&Elif) { + expecting.push(vec![Then]) + } else { return false } + } else { return false } + } + Else => { + if let Some(frame) = expecting.pop() { + if frame.contains(&Else) { + expecting.push(vec![Fi]) + } else { return false } + } else { return false } + } + Fi => { + if let Some(frame) = expecting.pop() { + if frame.contains(&Fi) { + /* Do nothing */ + } else { return false } + } else { return false } + } + While | Until | For | Select => { + expecting.push(vec![Do]) + } + Do => { + if let Some(frame) = expecting.pop() { + if frame.contains(&Do) { + expecting.push(vec![Done]) + } else { return false } + } else { return false } + } + Done => { + if let Some(frame) = expecting.pop() { + if frame.contains(&Done) { + /* Do nothing */ + } else { return false } + } else { return false } + } + Case => { + expecting.push(vec![Esac]) + } + Esac => { + if let Some(frame) = expecting.pop() { + if frame.contains(&Esac) { + /* Do nothing */ + } else { return false } + } else { return false } + } + _ => { /* Do nothing */ } + } + } + + expecting.is_empty() +} + +impl<'a> Validator for SynHelper<'a> { + fn validate(&self, ctx: &mut rustyline::validate::ValidationContext) -> rustyline::Result { + let input = ctx.input(); + let mut shenv_clone = self.shenv.clone(); + match check_delims(input) && check_keywords(input, &mut shenv_clone) { + true => Ok(ValidationResult::Valid(None)), + false => Ok(ValidationResult::Incomplete), + } + + } +} diff --git a/src/shellenv/input.rs b/src/shellenv/input.rs new file mode 100644 index 0000000..8cd99fd --- /dev/null +++ b/src/shellenv/input.rs @@ -0,0 +1,61 @@ +use std::cell::Ref; + +use crate::prelude::*; + +#[derive(Clone,Debug)] +pub struct InputMan { + input: Option, + spans: Vec>>, +} + +impl InputMan { + pub fn new() -> Self { + Self { input: None, spans: vec![] } + } + pub fn clear(&mut self) { + *self = Self::new(); + } + pub fn new_input(&mut self, input: &str) { + self.input = Some(input.to_string()) + } + pub fn get_input(&self) -> Option<&String> { + self.input.as_ref() + } + pub fn get_input_mut(&mut self) -> Option<&mut String> { + self.input.as_mut() + } + pub fn new_span(&mut self, start: usize, end: usize) -> Rc> { + if let Some(_input) = &self.input { + let span = Rc::new(RefCell::new(Span::new(start, end))); + self.spans.push(span.clone()); + span + } else { + Rc::new(RefCell::new(Span::new(0,0))) + } + } + pub fn spans_mut(&mut self) -> &mut Vec>> { + &mut self.spans + } + pub fn clamp(&self, span: Rc>) { + let mut span = span.borrow_mut(); + if let Some(input) = &self.input { + span.clamp_start(input.len()); + span.clamp_end(input.len()); + } + } + pub fn clamp_all(&self) { + for span in &self.spans { + self.clamp(span.clone()); + } + } + pub fn get_slice(&self, span: Rc>) -> Option<&str> { + let span = span.borrow(); + let mut start = span.start(); + let end = span.end(); + if start > end { + start = end; + } + + self.input.as_ref().map(|s| &s[start..end]) + } +} diff --git a/src/shellenv/mod.rs b/src/shellenv/mod.rs index cb992f3..b5b87e1 100644 --- a/src/shellenv/mod.rs +++ b/src/shellenv/mod.rs @@ -10,6 +10,7 @@ pub mod exec_ctx; pub mod meta; pub mod shenv; pub mod vars; +pub mod input; /// Calls attach_tty() on the shell's process group to retake control of the terminal pub fn take_term() -> ShResult<()> { diff --git a/src/shellenv/shenv.rs b/src/shellenv/shenv.rs index 29cdd0c..7108d74 100644 --- a/src/shellenv/shenv.rs +++ b/src/shellenv/shenv.rs @@ -5,6 +5,7 @@ pub struct ShEnv { vars: shellenv::vars::VarTab, logic: shellenv::logic::LogTab, meta: shellenv::meta::MetaTab, + input_man: shellenv::input::InputMan, ctx: shellenv::exec_ctx::ExecCtx } @@ -14,6 +15,7 @@ impl ShEnv { vars: shellenv::vars::VarTab::new(), logic: shellenv::logic::LogTab::new(), meta: shellenv::meta::MetaTab::new(), + input_man: shellenv::input::InputMan::new(), ctx: shellenv::exec_ctx::ExecCtx::new(), } } @@ -26,6 +28,58 @@ impl ShEnv { pub fn meta(&self) -> &shellenv::meta::MetaTab { &self.meta } + pub fn input_slice(&self, span: Rc>) -> &str { + &self.input_man.get_slice(span).unwrap_or_default() + } + pub fn expand_input(&mut self, new: &str, repl_span: Rc>) -> Vec { + let saved_spans = self.input_man.spans_mut().clone(); + let mut new_tokens = Lexer::new(new.to_string(), self).lex(); + *self.input_man.spans_mut() = saved_spans; + + let offset = repl_span.borrow().start(); + for token in new_tokens.iter_mut() { + token.span().borrow_mut().shift(offset as isize); + } + + let repl_start = repl_span.borrow().start(); + let repl_end = repl_span.borrow().end(); + let range = repl_start..repl_end; + + if let Some(ref mut input) = self.input_man.get_input_mut() { + let old = &input[range.clone()]; + let delta: isize = new.len() as isize - old.len() as isize; + input.replace_range(range, new); + let expanded = input.clone(); + log!(DEBUG, expanded); + + for span in self.input_man.spans_mut() { + let mut span_mut = span.borrow_mut(); + if span_mut.start() > repl_start { + span_mut.shift(delta); + } + } + for token in &new_tokens { + self.input_man.spans_mut().push(token.span()); + } + } + self.input_man.clamp_all(); + new_tokens + } + pub fn new_input(&mut self, input: &str) { + self.input_man.clear(); + self.input_man.new_input(input); + } + pub fn get_input(&self) -> String { + let input = self.input_man.get_input().map(|s| s.to_string()).unwrap_or_default(); + log!(DEBUG, input); + input + } + pub fn inputman(&self) -> &shellenv::input::InputMan { + &self.input_man + } + pub fn inputman_mut(&mut self) -> &mut shellenv::input::InputMan { + &mut self.input_man + } pub fn meta_mut(&mut self) -> &mut shellenv::meta::MetaTab { &mut self.meta }