Early implementation of scripting elements
This commit is contained in:
@@ -6,12 +6,16 @@ pub fn alias(node: Node, shenv: &mut ShEnv) -> ShResult<()> {
|
|||||||
let argv = argv.drop_first();
|
let argv = argv.drop_first();
|
||||||
let mut argv_iter = argv.into_iter();
|
let mut argv_iter = argv.into_iter();
|
||||||
while let Some(arg) = argv_iter.next() {
|
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('=') {
|
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);
|
let clean_body = trim_quotes(&body);
|
||||||
|
log!(DEBUG, clean_body);
|
||||||
shenv.logic_mut().set_alias(alias, &clean_body);
|
shenv.logic_mut().set_alias(alias, &clean_body);
|
||||||
} else {
|
} 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!() }
|
} else { unreachable!() }
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ pub fn cd(node: Node, shenv: &mut ShEnv) -> ShResult<()> {
|
|||||||
if let NdRule::Command { argv, redirs: _ } = rule {
|
if let NdRule::Command { argv, redirs: _ } = rule {
|
||||||
let mut argv_iter = argv.into_iter();
|
let mut argv_iter = argv.into_iter();
|
||||||
argv_iter.next(); // Ignore 'cd'
|
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);
|
let dir = PathBuf::from(&dir_raw);
|
||||||
std::env::set_current_dir(dir)?;
|
std::env::set_current_dir(dir)?;
|
||||||
shenv.vars_mut().export("PWD",&dir_raw);
|
shenv.vars_mut().export("PWD",&dir_raw);
|
||||||
|
|||||||
20
src/builtin/control_flow.rs
Normal file
20
src/builtin/control_flow.rs
Normal file
@@ -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, ""))
|
||||||
|
}
|
||||||
@@ -6,7 +6,7 @@ pub fn export(node: Node, shenv: &mut ShEnv) -> ShResult<()> {
|
|||||||
let mut argv_iter = argv.into_iter();
|
let mut argv_iter = argv.into_iter();
|
||||||
argv_iter.next(); // Ignore 'export'
|
argv_iter.next(); // Ignore 'export'
|
||||||
while let Some(arg) = argv_iter.next() {
|
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('=') {
|
if let Some((var,val)) = arg_raw.split_once('=') {
|
||||||
shenv.vars_mut().export(var, val);
|
shenv.vars_mut().export(var, val);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ pub fn continue_job(node: Node, shenv: &mut ShEnv, fg: bool) -> ShResult<()> {
|
|||||||
ShErr::full(
|
ShErr::full(
|
||||||
ShErrKind::InternalErr,
|
ShErrKind::InternalErr,
|
||||||
format!("Somehow called {} with an existing foreground job",cmd),
|
format!("Somehow called {} with an existing foreground job",cmd),
|
||||||
|
shenv.get_input(),
|
||||||
blame
|
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()) {
|
let curr_job_id = if let Some(id) = read_jobs(|j| j.curr_job()) {
|
||||||
id
|
id
|
||||||
} else {
|
} 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() {
|
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
|
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() {
|
if query_result.is_some() {
|
||||||
Ok(j.remove_job(id.clone()).unwrap())
|
Ok(j.remove_job(id.clone()).unwrap())
|
||||||
} else {
|
} 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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_job_id(arg: &str, blame: Span) -> ShResult<usize> {
|
fn parse_job_id(arg: &str, blame: Rc<RefCell<Span>>, shenv: &mut ShEnv) -> ShResult<usize> {
|
||||||
if arg.starts_with('%') {
|
if arg.starts_with('%') {
|
||||||
let arg = arg.strip_prefix('%').unwrap();
|
let arg = arg.strip_prefix('%').unwrap();
|
||||||
if arg.chars().all(|ch| ch.is_ascii_digit()) {
|
if arg.chars().all(|ch| ch.is_ascii_digit()) {
|
||||||
@@ -66,7 +74,14 @@ fn parse_job_id(arg: &str, blame: Span) -> ShResult<usize> {
|
|||||||
});
|
});
|
||||||
match result {
|
match result {
|
||||||
Some(id) => Ok(id),
|
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()) {
|
} else if arg.chars().all(|ch| ch.is_ascii_digit()) {
|
||||||
@@ -86,10 +101,24 @@ fn parse_job_id(arg: &str, blame: Span) -> ShResult<usize> {
|
|||||||
|
|
||||||
match result {
|
match result {
|
||||||
Some(id) => Ok(id),
|
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 {
|
} 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();
|
let mut flags = JobCmdFlags::empty();
|
||||||
while let Some(arg) = argv.next() {
|
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();
|
let mut chars = arg_s.chars().peekable();
|
||||||
if chars.peek().is_none_or(|ch| *ch != '-') {
|
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();
|
chars.next();
|
||||||
while let Some(ch) = 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,
|
'n' => JobCmdFlags::NEW_ONLY,
|
||||||
'r' => JobCmdFlags::RUNNING,
|
'r' => JobCmdFlags::RUNNING,
|
||||||
's' => JobCmdFlags::STOPPED,
|
'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
|
flags |= flag
|
||||||
|
|||||||
@@ -5,8 +5,9 @@ pub mod export;
|
|||||||
pub mod jobctl;
|
pub mod jobctl;
|
||||||
pub mod read;
|
pub mod read;
|
||||||
pub mod alias;
|
pub mod alias;
|
||||||
|
pub mod control_flow;
|
||||||
|
|
||||||
pub const BUILTINS: [&str;9] = [
|
pub const BUILTINS: [&str;13] = [
|
||||||
"echo",
|
"echo",
|
||||||
"cd",
|
"cd",
|
||||||
"pwd",
|
"pwd",
|
||||||
@@ -15,5 +16,9 @@ pub const BUILTINS: [&str;9] = [
|
|||||||
"bg",
|
"bg",
|
||||||
"jobs",
|
"jobs",
|
||||||
"read",
|
"read",
|
||||||
"alias"
|
"alias",
|
||||||
|
"exit",
|
||||||
|
"continue",
|
||||||
|
"return",
|
||||||
|
"break",
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -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(&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 {
|
} else {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
|
|||||||
28
src/execute/ifthen.rs
Normal file
28
src/execute/ifthen.rs
Normal file
@@ -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(())
|
||||||
|
}
|
||||||
@@ -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::*};
|
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<S: Into<String>>(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> {
|
pub struct Executor<'a> {
|
||||||
ast: SynTree,
|
ast: SynTree,
|
||||||
shenv: &'a mut ShEnv
|
shenv: &'a mut ShEnv
|
||||||
@@ -16,10 +43,9 @@ impl<'a> Executor<'a> {
|
|||||||
pub fn walk(&mut self) -> ShResult<()> {
|
pub fn walk(&mut self) -> ShResult<()> {
|
||||||
log!(DEBUG, "Starting walk");
|
log!(DEBUG, "Starting walk");
|
||||||
while let Some(node) = self.ast.next_node() {
|
while let Some(node) = self.ast.next_node() {
|
||||||
let span = node.span();
|
|
||||||
if let NdRule::CmdList { cmds } = node.clone().into_rule() {
|
if let NdRule::CmdList { cmds } = node.clone().into_rule() {
|
||||||
log!(TRACE, "{:?}", cmds);
|
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!() }
|
} else { unreachable!() }
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -33,6 +59,7 @@ fn exec_list(list: Vec<(Option<CmdGuard>, Node)>, shenv: &mut ShEnv) -> ShResult
|
|||||||
let guard = cmd_info.0;
|
let guard = cmd_info.0;
|
||||||
let cmd = cmd_info.1;
|
let cmd = cmd_info.1;
|
||||||
let span = cmd.span();
|
let span = cmd.span();
|
||||||
|
let cmd_raw = cmd.as_raw(shenv);
|
||||||
|
|
||||||
if let Some(guard) = guard {
|
if let Some(guard) = guard {
|
||||||
let code = shenv.get_code();
|
let code = shenv.get_code();
|
||||||
@@ -47,11 +74,12 @@ fn exec_list(list: Vec<(Option<CmdGuard>, Node)>, shenv: &mut ShEnv) -> ShResult
|
|||||||
}
|
}
|
||||||
log!(TRACE, "{:?}", *cmd.rule());
|
log!(TRACE, "{:?}", *cmd.rule());
|
||||||
match *cmd.rule() {
|
match *cmd.rule() {
|
||||||
NdRule::Command {..} => dispatch_command(cmd, shenv).try_blame(span)?,
|
NdRule::Command {..} => dispatch_command(cmd, shenv).try_blame(cmd_raw, span)?,
|
||||||
NdRule::Subshell {..} => exec_subshell(cmd,shenv).try_blame(span)?,
|
NdRule::Subshell {..} => exec_subshell(cmd,shenv).try_blame(cmd_raw, span)?,
|
||||||
NdRule::FuncDef {..} => exec_funcdef(cmd,shenv).try_blame(span)?,
|
NdRule::IfThen {..} => ifthen::exec_if(cmd, shenv).try_blame(cmd_raw, span)?,
|
||||||
NdRule::Assignment {..} => exec_assignment(cmd,shenv).try_blame(span)?,
|
NdRule::FuncDef {..} => exec_funcdef(cmd,shenv).try_blame(cmd_raw, span)?,
|
||||||
NdRule::Pipeline {..} => exec_pipeline(cmd, shenv).try_blame(span)?,
|
NdRule::Assignment {..} => exec_assignment(cmd,shenv).try_blame(cmd_raw, span)?,
|
||||||
|
NdRule::Pipeline {..} => exec_pipeline(cmd, shenv).try_blame(cmd_raw, span)?,
|
||||||
_ => unimplemented!()
|
_ => unimplemented!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -64,7 +92,7 @@ fn dispatch_command(mut node: Node, shenv: &mut ShEnv) -> ShResult<()> {
|
|||||||
let mut is_subsh = false;
|
let mut is_subsh = false;
|
||||||
if let NdRule::Command { ref mut argv, redirs: _ } = node.rule_mut() {
|
if let NdRule::Command { ref mut argv, redirs: _ } = node.rule_mut() {
|
||||||
*argv = expand_argv(argv.to_vec(), shenv);
|
*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() {
|
if shenv.logic().get_function(&cmd).is_some() {
|
||||||
is_func = true;
|
is_func = true;
|
||||||
} else if node.flags().contains(NdFlag::BUILTIN) {
|
} 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();
|
let rule = node.into_rule();
|
||||||
if let NdRule::Command { argv, redirs } = rule {
|
if let NdRule::Command { argv, redirs } = rule {
|
||||||
let mut argv_iter = argv.into_iter();
|
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 body = shenv.logic().get_function(&func_name).unwrap().to_string();
|
||||||
let snapshot = shenv.clone();
|
let snapshot = shenv.clone();
|
||||||
shenv.vars_mut().reset_params();
|
shenv.vars_mut().reset_params();
|
||||||
while let Some(arg) = argv_iter.next() {
|
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);
|
shenv.collect_redirs(redirs);
|
||||||
|
|
||||||
let lex_input = Rc::new(body);
|
match exec_input(body, shenv) {
|
||||||
let tokens = Lexer::new(lex_input).lex();
|
Ok(()) => {
|
||||||
match Parser::new(tokens).parse() {
|
|
||||||
Ok(syn_tree) => {
|
|
||||||
match Executor::new(syn_tree, shenv).walk() {
|
|
||||||
Ok(_) => { /* yippee */ }
|
|
||||||
Err(e) => {
|
|
||||||
*shenv = snapshot;
|
*shenv = snapshot;
|
||||||
return Err(e.into())
|
return Ok(())
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Err(e) if e.kind() == ShErrKind::FuncReturn => {
|
||||||
|
let code = shenv.get_code();
|
||||||
|
*shenv = snapshot;
|
||||||
|
shenv.set_code(code);
|
||||||
|
return Ok(())
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
*shenv = snapshot;
|
*shenv = snapshot;
|
||||||
return Err(e.into())
|
return Err(e.into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
*shenv = snapshot;
|
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -125,9 +152,9 @@ fn exec_func(node: Node, shenv: &mut ShEnv) -> ShResult<()> {
|
|||||||
fn exec_funcdef(node: Node, shenv: &mut ShEnv) -> ShResult<()> {
|
fn exec_funcdef(node: Node, shenv: &mut ShEnv) -> ShResult<()> {
|
||||||
let rule = node.into_rule();
|
let rule = node.into_rule();
|
||||||
if let NdRule::FuncDef { name, body } = 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 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();
|
let body = body_raw[1..body_raw.len() - 1].trim();
|
||||||
|
|
||||||
shenv.logic_mut().set_function(name, body);
|
shenv.logic_mut().set_function(name, body);
|
||||||
@@ -148,26 +175,18 @@ fn exec_subshell(node: Node, shenv: &mut ShEnv) -> ShResult<()> {
|
|||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
for arg in argv {
|
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) => {
|
Err(e) => {
|
||||||
write_err(e)?;
|
eprintln!("{}",e);
|
||||||
exit(1);
|
sh_quit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
exit(0);
|
|
||||||
} else {
|
} else {
|
||||||
match unsafe { fork()? } {
|
match unsafe { fork()? } {
|
||||||
Child => {
|
Child => {
|
||||||
@@ -177,26 +196,17 @@ fn exec_subshell(node: Node, shenv: &mut ShEnv) -> ShResult<()> {
|
|||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
for arg in argv {
|
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) => {
|
Err(e) => {
|
||||||
write_err(e)?;
|
eprintln!("{}",e);
|
||||||
exit(1);
|
sh_quit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
exit(0);
|
|
||||||
}
|
}
|
||||||
Parent { child } => {
|
Parent { child } => {
|
||||||
*shenv = snapshot;
|
*shenv = snapshot;
|
||||||
@@ -218,7 +228,7 @@ fn exec_subshell(node: Node, shenv: &mut ShEnv) -> ShResult<()> {
|
|||||||
fn exec_builtin(node: Node, shenv: &mut ShEnv) -> ShResult<()> {
|
fn exec_builtin(node: Node, shenv: &mut ShEnv) -> ShResult<()> {
|
||||||
log!(DEBUG, "Executing builtin");
|
log!(DEBUG, "Executing builtin");
|
||||||
let command = if let NdRule::Command { argv, redirs: _ } = node.rule() {
|
let command = if let NdRule::Command { argv, redirs: _ } = node.rule() {
|
||||||
argv.first().unwrap().to_string()
|
argv.first().unwrap().as_raw(shenv)
|
||||||
} else { unreachable!() };
|
} else { unreachable!() };
|
||||||
|
|
||||||
log!(TRACE, "{}", command.as_str());
|
log!(TRACE, "{}", command.as_str());
|
||||||
@@ -232,6 +242,8 @@ fn exec_builtin(node: Node, shenv: &mut ShEnv) -> ShResult<()> {
|
|||||||
"bg" => continue_job(node, shenv, false)?,
|
"bg" => continue_job(node, shenv, false)?,
|
||||||
"read" => read_builtin(node, shenv)?,
|
"read" => read_builtin(node, shenv)?,
|
||||||
"alias" => alias(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)
|
_ => unimplemented!("Have not yet implemented support for builtin `{}'",command)
|
||||||
}
|
}
|
||||||
log!(TRACE, "done");
|
log!(TRACE, "done");
|
||||||
@@ -247,7 +259,7 @@ fn exec_assignment(node: Node, shenv: &mut ShEnv) -> ShResult<()> {
|
|||||||
let mut assigns = assignments.into_iter();
|
let mut assigns = assignments.into_iter();
|
||||||
if let Some(cmd) = cmd {
|
if let Some(cmd) = cmd {
|
||||||
while let Some(assign) = assigns.next() {
|
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('=') {
|
if let Some((var,val)) = assign_raw.split_once('=') {
|
||||||
shenv.vars_mut().export(var, val);
|
shenv.vars_mut().export(var, val);
|
||||||
}
|
}
|
||||||
@@ -259,7 +271,7 @@ fn exec_assignment(node: Node, shenv: &mut ShEnv) -> ShResult<()> {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
while let Some(assign) = assigns.next() {
|
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('=') {
|
if let Some((var,val)) = assign_raw.split_once('=') {
|
||||||
shenv.vars_mut().set_var(var, val);
|
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))
|
(Some(r_pipe),Some(w_pipe))
|
||||||
};
|
};
|
||||||
if let NdRule::Command { argv, redirs: _ } = cmd.rule() {
|
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);
|
cmd_names.push(cmd_name);
|
||||||
} else if let NdRule::Subshell {..} = cmd.rule() {
|
} else if let NdRule::Subshell {..} = cmd.rule() {
|
||||||
cmd_names.push("subshell".to_string());
|
cmd_names.push("subshell".to_string());
|
||||||
@@ -305,12 +317,12 @@ fn exec_pipeline(node: Node, shenv: &mut ShEnv) -> ShResult<()> {
|
|||||||
|
|
||||||
// Create some redirections
|
// Create some redirections
|
||||||
if let Some(w_pipe) = w_pipe {
|
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);
|
shenv.ctx_mut().push_rdr(wpipe_redir);
|
||||||
}
|
}
|
||||||
// Use the r_pipe created in the last iteration
|
// Use the r_pipe created in the last iteration
|
||||||
if let Some(prev_rpipe) = prev_rpipe {
|
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);
|
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<()> {
|
fn exec_cmd(node: Node, shenv: &mut ShEnv) -> ShResult<()> {
|
||||||
log!(DEBUG, "Executing command");
|
log!(DEBUG, "Executing command");
|
||||||
let blame = node.span();
|
let blame = node.span();
|
||||||
|
let blame_raw = node.as_raw(shenv);
|
||||||
let rule = node.into_rule();
|
let rule = node.into_rule();
|
||||||
|
|
||||||
if let NdRule::Command { argv, redirs } = rule {
|
if let NdRule::Command { argv, redirs } = rule {
|
||||||
@@ -395,7 +408,7 @@ fn exec_cmd(node: Node, shenv: &mut ShEnv) -> ShResult<()> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} 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) }
|
} else { unreachable!("Found this rule in exec_cmd: {:?}", rule) }
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -1,75 +1,62 @@
|
|||||||
use crate::{parse::lex::SEPARATORS, prelude::*};
|
use crate::{parse::lex::SEPARATORS, prelude::*};
|
||||||
|
|
||||||
pub fn expand_aliases(input: &str, shenv: &mut ShEnv) -> Option<String> {
|
pub fn expand_alias(candidate: Token, shenv: &mut ShEnv) -> Vec<Token> {
|
||||||
let mut result = input.to_string();
|
let mut tokens = vec![];
|
||||||
let mut expanded_aliases = Vec::new();
|
let mut work_stack = VecDeque::new();
|
||||||
let mut found_in_iteration = true;
|
let mut expanded_aliases = vec![];
|
||||||
|
let logic = shenv.logic().clone();
|
||||||
|
|
||||||
// Loop until no new alias expansion happens.
|
// Start with the candidate token in the work queue
|
||||||
while found_in_iteration {
|
work_stack.bpush(candidate);
|
||||||
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() {
|
// Process until there are no more tokens in the queue
|
||||||
match ch {
|
while let Some(token) = work_stack.fpop() {
|
||||||
';' | '\n' => {
|
if token.rule() == TkRule::Ident {
|
||||||
new_result.push(ch);
|
let candidate_str = token.as_raw(shenv);
|
||||||
is_cmd = true;
|
if let Some(alias) = logic.get_alias(&candidate_str) {
|
||||||
// Consume any extra whitespace or delimiters.
|
// Expand the alias only if it hasn't been expanded yet
|
||||||
while let Some(&next_ch) = chars.peek() {
|
if !expanded_aliases.contains(&candidate_str) {
|
||||||
if matches!(next_ch, ' ' | '\t' | ';' | '\n') {
|
expanded_aliases.push(candidate_str);
|
||||||
new_result.push(next_ch);
|
let mut new_tokens = shenv.expand_input(alias, token.span());
|
||||||
chars.next();
|
for token in new_tokens.iter_mut() {
|
||||||
} else {
|
work_stack.bpush(token.clone());
|
||||||
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 {
|
} else {
|
||||||
new_result.push_str(&alias_cand);
|
// If already expanded, just add the token to the output
|
||||||
|
tokens.push(token);
|
||||||
}
|
}
|
||||||
alias_cand.clear();
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
new_result.push(ch);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result = new_result;
|
|
||||||
log!(DEBUG, result);
|
|
||||||
}
|
|
||||||
|
|
||||||
if expanded_aliases.is_empty() {
|
|
||||||
None
|
|
||||||
} else {
|
} else {
|
||||||
Some(result)
|
tokens.push(token);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
tokens.push(token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tokens
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn expand_aliases(tokens: Vec<Token>, shenv: &mut ShEnv) -> Vec<Token> {
|
||||||
|
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()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
processed
|
||||||
}
|
}
|
||||||
|
|||||||
30
src/expand/cmdsub.rs
Normal file
30
src/expand/cmdsub.rs
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
use crate::prelude::*;
|
||||||
|
|
||||||
|
pub fn expand_cmdsub(token: Token, shenv: &mut ShEnv) -> ShResult<Vec<Token>> {
|
||||||
|
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)
|
||||||
|
}
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
pub mod expand_vars;
|
pub mod vars;
|
||||||
pub mod tilde;
|
pub mod tilde;
|
||||||
pub mod alias;
|
pub mod alias;
|
||||||
|
pub mod cmdsub;
|
||||||
|
|
||||||
use alias::expand_aliases;
|
use alias::expand_aliases;
|
||||||
use expand_vars::{expand_dquote, expand_var};
|
use vars::{expand_dquote, expand_var};
|
||||||
use tilde::expand_tilde;
|
use tilde::expand_tilde;
|
||||||
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
@@ -11,7 +12,7 @@ use crate::prelude::*;
|
|||||||
pub fn expand_argv(argv: Vec<Token>, shenv: &mut ShEnv) -> Vec<Token> {
|
pub fn expand_argv(argv: Vec<Token>, shenv: &mut ShEnv) -> Vec<Token> {
|
||||||
let mut processed = vec![];
|
let mut processed = vec![];
|
||||||
for arg in argv {
|
for arg in argv {
|
||||||
log!(DEBUG, arg);
|
log!(DEBUG, "{}",arg.as_raw(shenv));
|
||||||
log!(DEBUG, processed);
|
log!(DEBUG, processed);
|
||||||
match arg.rule() {
|
match arg.rule() {
|
||||||
TkRule::DQuote => {
|
TkRule::DQuote => {
|
||||||
@@ -23,7 +24,7 @@ pub fn expand_argv(argv: Vec<Token>, shenv: &mut ShEnv) -> Vec<Token> {
|
|||||||
processed.append(&mut varsub_exp);
|
processed.append(&mut varsub_exp);
|
||||||
}
|
}
|
||||||
TkRule::TildeSub => {
|
TkRule::TildeSub => {
|
||||||
let tilde_exp = expand_tilde(arg.clone());
|
let tilde_exp = expand_tilde(arg.clone(), shenv);
|
||||||
processed.push(tilde_exp);
|
processed.push(tilde_exp);
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
pub fn expand_tilde(tilde_sub: Token) -> Token {
|
pub fn expand_tilde(tilde_sub: Token, shenv: &mut ShEnv) -> Token {
|
||||||
let tilde_sub_raw = tilde_sub.to_string();
|
let mut tilde_sub_raw = tilde_sub.as_raw(shenv);
|
||||||
if tilde_sub_raw.starts_with('~') {
|
if tilde_sub_raw.starts_with('~') {
|
||||||
let home = std::env::var("HOME").unwrap_or_default();
|
let home = std::env::var("HOME").unwrap_or_default();
|
||||||
tilde_sub_raw.replacen('~', &home, 1);
|
tilde_sub_raw = tilde_sub_raw.replacen('~', &home, 1);
|
||||||
let lex_input = Rc::new(tilde_sub_raw);
|
let mut tokens = Lexer::new(tilde_sub_raw,shenv).lex();
|
||||||
let mut tokens = Lexer::new(lex_input).lex();
|
|
||||||
tokens.pop().unwrap_or(tilde_sub)
|
tokens.pop().unwrap_or(tilde_sub)
|
||||||
} else {
|
} else {
|
||||||
tilde_sub
|
tilde_sub
|
||||||
|
|||||||
@@ -1,18 +1,15 @@
|
|||||||
use crate::{parse::lex::Token, prelude::*};
|
use crate::{parse::lex::Token, prelude::*};
|
||||||
|
|
||||||
pub fn expand_var(var_sub: Token, shenv: &mut ShEnv) -> Vec<Token> {
|
pub fn expand_var(var_sub: Token, shenv: &mut ShEnv) -> Vec<Token> {
|
||||||
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 var_name = var_name.trim_start_matches('$').trim_matches(['{','}']);
|
||||||
let value = Rc::new(
|
let value = shenv.vars().get_var(var_name).to_string();
|
||||||
shenv.vars()
|
|
||||||
.get_var(var_name)
|
shenv.expand_input(&value, var_sub.span())
|
||||||
.to_string()
|
|
||||||
);
|
|
||||||
Lexer::new(value).lex() // Automatically handles word splitting for us
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn expand_dquote(dquote: Token, shenv: &mut ShEnv) -> Token {
|
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 result = String::new();
|
||||||
let mut var_name = String::new();
|
let mut var_name = String::new();
|
||||||
let mut chars = dquote_raw.chars().peekable();
|
let mut chars = dquote_raw.chars().peekable();
|
||||||
@@ -63,5 +60,8 @@ pub fn expand_dquote(dquote: Token, shenv: &mut ShEnv) -> Token {
|
|||||||
|
|
||||||
log!(DEBUG, result);
|
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
|
||||||
}
|
}
|
||||||
@@ -5,16 +5,62 @@ use crate::prelude::*;
|
|||||||
|
|
||||||
pub type ShResult<T> = Result<T,ShErr>;
|
pub type ShResult<T> = Result<T,ShErr>;
|
||||||
|
|
||||||
|
pub trait ResultExt {
|
||||||
|
fn eprint(self) -> Self;
|
||||||
|
fn abort_if_err(&self);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone,Debug)]
|
||||||
|
pub struct BlamePair {
|
||||||
|
input: String,
|
||||||
|
span: Rc<RefCell<Span>>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BlamePair {
|
||||||
|
pub fn new(input: String, span: Rc<RefCell<Span>>) -> 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<String> for BlamePair {
|
||||||
|
fn into(self) -> String {
|
||||||
|
self.input
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, E: Display> ResultExt for Result<T, E> {
|
||||||
|
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 {
|
pub trait Blame {
|
||||||
/// Blame a span for a propagated error. This will convert a ShErr::Simple into a ShErr::Full
|
/// 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
|
/// This will also set the span on a ShErr::Builder
|
||||||
fn blame(self, span: Span) -> Self;
|
fn blame(self, input: String, span: Rc<RefCell<Span>>) -> Self;
|
||||||
|
|
||||||
/// If an error is propagated to this point, then attempt to blame a span.
|
/// 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.
|
/// 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
|
/// 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<RefCell<Span>>) -> Self;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<std::io::Error> for ShErr {
|
impl From<std::io::Error> for ShErr {
|
||||||
@@ -42,17 +88,17 @@ impl From<Errno> for ShErr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Blame for Result<T,ShErr> {
|
impl<T> Blame for Result<T,ShErr> {
|
||||||
fn blame(self, span: Span) -> Self {
|
fn blame(self, input: String, span: Rc<RefCell<Span>>) -> Self {
|
||||||
if let Err(mut e) = self {
|
if let Err(mut e) = self {
|
||||||
e.blame(span);
|
e.blame(input,span);
|
||||||
Err(e)
|
Err(e)
|
||||||
} else {
|
} else {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn try_blame(self, span: Span) -> Self {
|
fn try_blame(self, input: String, span: Rc<RefCell<Span>>) -> Self {
|
||||||
if let Err(mut e) = self {
|
if let Err(mut e) = self {
|
||||||
e.try_blame(span);
|
e.try_blame(input,span);
|
||||||
Err(e)
|
Err(e)
|
||||||
} else {
|
} else {
|
||||||
self
|
self
|
||||||
@@ -60,7 +106,7 @@ impl<T> Blame for Result<T,ShErr> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug,Clone)]
|
#[derive(Debug,Copy,Clone,PartialEq,Eq)]
|
||||||
pub enum ShErrKind {
|
pub enum ShErrKind {
|
||||||
IoErr,
|
IoErr,
|
||||||
SyntaxErr,
|
SyntaxErr,
|
||||||
@@ -85,7 +131,7 @@ impl Default for ShErrKind {
|
|||||||
#[derive(Clone,Debug)]
|
#[derive(Clone,Debug)]
|
||||||
pub enum ShErr {
|
pub enum ShErr {
|
||||||
Simple { kind: ShErrKind, message: String },
|
Simple { kind: ShErrKind, message: String },
|
||||||
Full { kind: ShErrKind, message: String, span: Span },
|
Full { kind: ShErrKind, message: String, blame: BlamePair },
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ShErr {
|
impl ShErr {
|
||||||
@@ -95,32 +141,35 @@ impl ShErr {
|
|||||||
pub fn io() -> Self {
|
pub fn io() -> Self {
|
||||||
io::Error::last_os_error().into()
|
io::Error::last_os_error().into()
|
||||||
}
|
}
|
||||||
pub fn full<S: Into<String>>(kind: ShErrKind, message: S, span: Span) -> Self {
|
pub fn full<S: Into<String>>(kind: ShErrKind, message: S, input: String, span: Rc<RefCell<Span>>) -> Self {
|
||||||
Self::Full { kind, message: message.into(), span }
|
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<RefCell<Span>>) {
|
||||||
|
let blame_pair = BlamePair::new(input, span);
|
||||||
match self {
|
match self {
|
||||||
Self::Full {..} => {
|
Self::Full {..} => {
|
||||||
/* Do not overwrite */
|
/* Do not overwrite */
|
||||||
}
|
}
|
||||||
Self::Simple { kind, message } => {
|
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<RefCell<Span>>) {
|
||||||
|
let blame_pair = BlamePair::new(input, span);
|
||||||
match self {
|
match self {
|
||||||
Self::Full { kind: _, message: _, span } => {
|
Self::Full { kind: _, message: _, blame } => {
|
||||||
*span = blame;
|
*blame = blame_pair;
|
||||||
}
|
}
|
||||||
Self::Simple { kind, message } => {
|
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) {
|
pub fn with_msg(&mut self, new_message: String) {
|
||||||
match self {
|
match self {
|
||||||
Self::Full { kind: _, message, span: _ } => {
|
Self::Full { kind: _, message, blame: _ } => {
|
||||||
*message = new_message
|
*message = new_message
|
||||||
}
|
}
|
||||||
Self::Simple { kind: _, 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) {
|
pub fn with_kind(&mut self, new_kind: ShErrKind) {
|
||||||
match self {
|
match self {
|
||||||
Self::Full { kind, message: _, span: _ } => {
|
Self::Full { kind, message: _, blame: _ } => {
|
||||||
*kind = new_kind
|
*kind = new_kind
|
||||||
}
|
}
|
||||||
Self::Simple { kind, message: _ } => {
|
Self::Simple { kind, message: _ } => {
|
||||||
@@ -141,7 +200,7 @@ impl ShErr {
|
|||||||
pub fn display_kind(&self) -> String {
|
pub fn display_kind(&self) -> String {
|
||||||
match self {
|
match self {
|
||||||
ShErr::Simple { kind, message: _ } |
|
ShErr::Simple { kind, message: _ } |
|
||||||
ShErr::Full { kind, message: _, span: _ } => {
|
ShErr::Full { kind, message: _, blame: _ } => {
|
||||||
match kind {
|
match kind {
|
||||||
ShErrKind::IoErr => "I/O Error: ".into(),
|
ShErrKind::IoErr => "I/O Error: ".into(),
|
||||||
ShErrKind::SyntaxErr => "Syntax 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 {
|
pub fn get_window(&self) -> String {
|
||||||
if let ShErr::Full { kind: _, message: _, span } = self {
|
if let ShErr::Full { kind: _, message: _, blame } = self.clone() {
|
||||||
let window = span.get_slice();
|
let window: String = blame.into();
|
||||||
window.split_once('\n').unwrap_or((&window,"")).0.to_string()
|
window.split_once('\n').unwrap_or((&window,"")).0.to_string()
|
||||||
} else {
|
} else {
|
||||||
String::new()
|
String::new()
|
||||||
@@ -173,9 +254,10 @@ impl Display for ShErr {
|
|||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
let error_display = match self {
|
let error_display = match self {
|
||||||
ShErr::Simple { kind: _, message } => format!("{}{}",self.display_kind(),message),
|
ShErr::Simple { kind: _, message } => format!("{}{}",self.display_kind(),message),
|
||||||
ShErr::Full { kind: _, message, span } => {
|
ShErr::Full { kind: _, message, blame } => {
|
||||||
let (offset,line_no,line_text) = span.get_line();
|
let (offset,line_no,line_text) = self.get_line();
|
||||||
let dist = span.end() - span.start();
|
log!(DEBUG, blame);
|
||||||
|
let dist = blame.len();
|
||||||
let padding = " ".repeat(offset);
|
let padding = " ".repeat(offset);
|
||||||
let line_inner = "~".repeat(dist.saturating_sub(2));
|
let line_inner = "~".repeat(dist.saturating_sub(2));
|
||||||
let err_kind = &self.display_kind().styled(Style::Red | Style::Bold);
|
let err_kind = &self.display_kind().styled(Style::Red | Style::Bold);
|
||||||
|
|||||||
@@ -40,6 +40,31 @@ pub fn c_pipe() -> Result<(RawFd,RawFd),Errno> {
|
|||||||
Ok((pipes[0],pipes[1]))
|
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<String> {
|
||||||
|
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<String>, envp: Vec<String>) -> Result<(),Errno> {
|
pub fn execvpe(cmd: String, argv: Vec<String>, envp: Vec<String>) -> Result<(),Errno> {
|
||||||
let cmd_raw = CString::new(cmd).unwrap();
|
let cmd_raw = CString::new(cmd).unwrap();
|
||||||
|
|
||||||
|
|||||||
@@ -3,10 +3,26 @@ use std::{os::fd::{AsFd, AsRawFd, BorrowedFd, FromRawFd, OwnedFd}, str::FromStr}
|
|||||||
|
|
||||||
use nix::libc::getpgrp;
|
use nix::libc::getpgrp;
|
||||||
|
|
||||||
use crate::{expand::{expand_vars::{expand_dquote, expand_var}, tilde::expand_tilde}, prelude::*};
|
use crate::prelude::*;
|
||||||
|
|
||||||
use super::term::StyleSet;
|
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 {
|
pub trait StrOps {
|
||||||
/// This function operates on anything that implements `AsRef<str>` and `Display`, which is mainly strings.
|
/// 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,
|
/// 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<Token> {
|
|||||||
let mut argv_iter = self.into_iter();
|
let mut argv_iter = self.into_iter();
|
||||||
let mut argv_processed = vec![];
|
let mut argv_processed = vec![];
|
||||||
while let Some(arg) = argv_iter.next() {
|
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.push(cleaned);
|
||||||
}
|
}
|
||||||
argv_processed
|
argv_processed
|
||||||
@@ -248,6 +264,12 @@ impl Redir {
|
|||||||
pub fn new(src: i32, op: RedirType, tgt: RedirTarget) -> Self {
|
pub fn new(src: i32, op: RedirType, tgt: RedirTarget) -> Self {
|
||||||
Self { src, op, tgt }
|
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)]
|
#[derive(Debug,Clone)]
|
||||||
|
|||||||
25
src/main.rs
25
src/main.rs
@@ -10,8 +10,6 @@ pub mod prompt;
|
|||||||
pub mod builtin;
|
pub mod builtin;
|
||||||
pub mod expand;
|
pub mod expand;
|
||||||
|
|
||||||
use libc::PIPE_BUF;
|
|
||||||
use nix::unistd::setpgid;
|
|
||||||
use signal::sig_setup;
|
use signal::sig_setup;
|
||||||
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
@@ -22,32 +20,13 @@ pub fn main() {
|
|||||||
|
|
||||||
loop {
|
loop {
|
||||||
log!(TRACE, "Entered 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,
|
Ok(line) => line,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
eprintln!("{}",e);
|
eprintln!("{}",e);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if let Some(line_exp) = expand_aliases(&line, &mut shenv) {
|
let _ = exec_input(line, &mut shenv).eprint();
|
||||||
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");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
317
src/parse/lex.rs
317
src/parse/lex.rs
@@ -1,66 +1,66 @@
|
|||||||
use std::fmt::{Debug, Display};
|
use std::{cell::Ref, fmt::{Debug, Display}};
|
||||||
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
pub const KEYWORDS: [&str;14] = [
|
pub const KEYWORDS: [TkRule;14] = [
|
||||||
"if",
|
TkRule::If,
|
||||||
"then",
|
TkRule::Then,
|
||||||
"elif",
|
TkRule::Elif,
|
||||||
"else",
|
TkRule::Else,
|
||||||
"fi",
|
TkRule::Fi,
|
||||||
"while",
|
TkRule::While,
|
||||||
"until",
|
TkRule::Until,
|
||||||
"for",
|
TkRule::For,
|
||||||
"in",
|
TkRule::In,
|
||||||
"select",
|
TkRule::Select,
|
||||||
"do",
|
TkRule::Do,
|
||||||
"done",
|
TkRule::Done,
|
||||||
"case",
|
TkRule::Case,
|
||||||
"esac"
|
TkRule::Esac
|
||||||
];
|
];
|
||||||
|
|
||||||
pub const SEPARATORS: [TkRule; 7] = [
|
pub const SEPARATORS: [TkRule; 6] = [
|
||||||
TkRule::Sep,
|
TkRule::Sep,
|
||||||
TkRule::AndOp,
|
TkRule::AndOp,
|
||||||
TkRule::OrOp,
|
TkRule::OrOp,
|
||||||
TkRule::PipeOp,
|
TkRule::PipeOp,
|
||||||
TkRule::ErrPipeOp,
|
TkRule::ErrPipeOp,
|
||||||
TkRule::BgOp,
|
TkRule::BgOp,
|
||||||
TkRule::Keyword // Keywords don't count as command names
|
|
||||||
];
|
];
|
||||||
|
|
||||||
pub trait LexRule {
|
pub trait LexRule {
|
||||||
fn try_match(input: &str) -> Option<usize>;
|
fn try_match(input: &str) -> Option<usize>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Lexer {
|
pub struct Lexer<'a> {
|
||||||
input: Rc<String>,
|
input: String,
|
||||||
tokens: Vec<Token>,
|
tokens: Vec<Token>,
|
||||||
is_command: bool,
|
is_command: bool,
|
||||||
|
shenv: &'a mut ShEnv,
|
||||||
consumed: usize
|
consumed: usize
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Lexer {
|
impl<'a> Lexer<'a> {
|
||||||
pub fn new(input: Rc<String>) -> Self {
|
pub fn new(input: String, shenv: &'a mut ShEnv) -> Self {
|
||||||
Self { input, tokens: vec![], is_command: true, consumed: 0 }
|
Self { input, tokens: vec![], is_command: true, shenv, consumed: 0 }
|
||||||
}
|
}
|
||||||
pub fn lex(mut self) -> Vec<Token> {
|
pub fn lex(mut self) -> Vec<Token> {
|
||||||
unsafe {
|
unsafe {
|
||||||
let mut input = self.input.as_str();
|
let mut input = self.input.as_str();
|
||||||
while let Some((mut rule,len)) = TkRule::try_match(input) {
|
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 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
|
rule = TkRule::Ident
|
||||||
|
|
||||||
// If we are in a command right now, after this we are in arguments
|
// 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;
|
self.is_command = false;
|
||||||
}
|
}
|
||||||
// If we see a separator like && or ;, we are now in a command again
|
// If we see a separator like && or ;, we are now in a command again
|
||||||
if SEPARATORS.contains(&rule) {
|
if SEPARATORS.contains(&rule) {
|
||||||
self.is_command = true;
|
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);
|
let token = Token::new(rule, span);
|
||||||
self.consumed += len;
|
self.consumed += len;
|
||||||
input = &input[len..];
|
input = &input[len..];
|
||||||
@@ -77,73 +77,43 @@ impl Lexer {
|
|||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Token {
|
pub struct Token {
|
||||||
rule: TkRule,
|
rule: TkRule,
|
||||||
span: Span
|
span: Rc<RefCell<Span>>
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Token {
|
impl Token {
|
||||||
pub fn new(rule: TkRule, span: Span) -> Self {
|
pub fn new(rule: TkRule, span: Rc<RefCell<Span>>) -> Self {
|
||||||
Self { rule, span }
|
Self { rule, span }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn span(&self) -> &Span {
|
pub fn span(&self) -> Rc<RefCell<Span>> {
|
||||||
&self.span
|
self.span.clone()
|
||||||
}
|
|
||||||
|
|
||||||
pub fn span_mut(&mut self) -> &mut Span {
|
|
||||||
&mut self.span
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn rule(&self) -> TkRule {
|
pub fn rule(&self) -> TkRule {
|
||||||
self.rule
|
self.rule
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn as_raw(&self, shenv: &mut ShEnv) -> String {
|
||||||
|
shenv.input_slice(self.span()).to_string()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Debug for Token {
|
impl Debug for Token {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
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)
|
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)]
|
#[derive(Debug,Clone)]
|
||||||
pub struct Span {
|
pub struct Span {
|
||||||
input: Rc<String>,
|
|
||||||
start: usize,
|
start: usize,
|
||||||
end: usize
|
end: usize
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Span {
|
impl Span {
|
||||||
pub fn new(input: Rc<String>, start: usize, end: usize) -> Self {
|
pub fn new(start: usize, end: usize) -> Self {
|
||||||
Self { input, start, end } }
|
Self { 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 start(&self) -> usize {
|
pub fn start(&self) -> usize {
|
||||||
self.start
|
self.start
|
||||||
@@ -151,8 +121,19 @@ impl Span {
|
|||||||
pub fn end(&self) -> usize {
|
pub fn end(&self) -> usize {
|
||||||
self.end
|
self.end
|
||||||
}
|
}
|
||||||
pub fn get_input(&self) -> Rc<String> {
|
pub fn clamp_start(&mut self, start: usize) {
|
||||||
self.input.clone()
|
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,
|
CmdSub,
|
||||||
DQuote,
|
DQuote,
|
||||||
SQuote,
|
SQuote,
|
||||||
Keyword,
|
If,
|
||||||
|
Then,
|
||||||
|
Elif,
|
||||||
|
Else,
|
||||||
|
Fi,
|
||||||
|
While,
|
||||||
|
Until,
|
||||||
|
For,
|
||||||
|
In,
|
||||||
|
Select,
|
||||||
|
Do,
|
||||||
|
Done,
|
||||||
|
Case,
|
||||||
|
Esac,
|
||||||
Assign,
|
Assign,
|
||||||
Ident,
|
Ident,
|
||||||
Sep,
|
Sep,
|
||||||
@@ -232,8 +226,21 @@ impl TkRule {
|
|||||||
try_match!(TildeSub,input);
|
try_match!(TildeSub,input);
|
||||||
try_match!(Subshell,input);
|
try_match!(Subshell,input);
|
||||||
try_match!(Sep,input);
|
try_match!(Sep,input);
|
||||||
try_match!(Keyword,input);
|
|
||||||
try_match!(Assign,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);
|
try_match!(Ident,input);
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
@@ -371,17 +378,159 @@ tkrule_def!(OrOp, |input: &str| {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
tkrule_def!(If, |input: &str| {
|
||||||
tkrule_def!(Keyword, |input: &str| {
|
if input.starts_with("if") {
|
||||||
for &kw in KEYWORDS.iter() {
|
match input.chars().nth(2) {
|
||||||
if input.starts_with(kw) {
|
Some(ch) if ch.is_whitespace() || ch == ';' => Some(2),
|
||||||
let len = kw.len();
|
Some(_) => None,
|
||||||
if input.chars().nth(len).map_or(true, |ch| ch.is_whitespace() || ch == ';') {
|
None => Some(2), // "if" is the entire input
|
||||||
return Some(len);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
None
|
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
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
tkrule_def!(Ident, |input: &str| {
|
tkrule_def!(Ident, |input: &str| {
|
||||||
@@ -717,6 +866,14 @@ tkrule_def!(BraceGrp, |input: &str| {
|
|||||||
});
|
});
|
||||||
|
|
||||||
tkrule_def!(RedirOp, |input: &str| {
|
tkrule_def!(RedirOp, |input: &str| {
|
||||||
|
if let Some(ch) = input.chars().next() {
|
||||||
|
match ch {
|
||||||
|
'>' |
|
||||||
|
'<' |
|
||||||
|
'&' => { /* Continue */ }
|
||||||
|
_ => return None
|
||||||
|
}
|
||||||
|
}
|
||||||
// Order matters here
|
// Order matters here
|
||||||
// For instance, if '>' is checked before '>>', '>' will always match first, and '>>' will never be checked
|
// For instance, if '>' is checked before '>>', '>' will always match first, and '>>' will never be checked
|
||||||
try_match_inner!(RedirCombineAppend,input); // Ex: &>>
|
try_match_inner!(RedirCombineAppend,input); // Ex: &>>
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
use core::fmt::Display;
|
use core::fmt::Display;
|
||||||
use std::str::FromStr;
|
use std::{cell::Ref, str::FromStr};
|
||||||
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
use super::lex::{TkRule, Span, Token};
|
use super::lex::{Span, TkRule, Token, KEYWORDS};
|
||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
#[derive(Debug,Clone,Copy,PartialEq,Eq)]
|
#[derive(Debug,Clone,Copy,PartialEq,Eq)]
|
||||||
@@ -17,12 +17,12 @@ bitflags! {
|
|||||||
|
|
||||||
pub trait ParseRule {
|
pub trait ParseRule {
|
||||||
/// Used for cases where a rule is optional
|
/// Used for cases where a rule is optional
|
||||||
fn try_match(input: &[Token]) -> ShResult<Option<Node>>;
|
fn try_match(input: &[Token], shenv: &mut ShEnv) -> ShResult<Option<Node>>;
|
||||||
/// Used for cases where a rule is assumed based on context
|
/// 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
|
/// For instance, if the "for" keyword is encountered, then it *must* be a for loop
|
||||||
/// And if it isn't, return a parse error
|
/// And if it isn't, return a parse error
|
||||||
fn assert_match(input: &[Token]) -> ShResult<Node> {
|
fn assert_match(input: &[Token], shenv: &mut ShEnv) -> ShResult<Node> {
|
||||||
Self::try_match(input)?.ok_or_else(||
|
Self::try_match(input,shenv)?.ok_or_else(||
|
||||||
ShErr::simple(ShErrKind::ParseErr, "Parse Error")
|
ShErr::simple(ShErrKind::ParseErr, "Parse Error")
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -40,7 +40,7 @@ pub enum CmdGuard {
|
|||||||
pub struct Node {
|
pub struct Node {
|
||||||
node_rule: NdRule,
|
node_rule: NdRule,
|
||||||
tokens: Vec<Token>,
|
tokens: Vec<Token>,
|
||||||
span: Span,
|
span: Rc<RefCell<Span>>,
|
||||||
flags: NdFlag,
|
flags: NdFlag,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,9 +60,12 @@ impl Node {
|
|||||||
pub fn into_rule(self) -> NdRule {
|
pub fn into_rule(self) -> NdRule {
|
||||||
self.node_rule
|
self.node_rule
|
||||||
}
|
}
|
||||||
pub fn span(&self) -> Span {
|
pub fn span(&self) -> Rc<RefCell<Span>> {
|
||||||
self.span.clone()
|
self.span.clone()
|
||||||
}
|
}
|
||||||
|
pub fn as_raw(&self, shenv: &mut ShEnv) -> String {
|
||||||
|
shenv.input_slice(self.span()).to_string()
|
||||||
|
}
|
||||||
pub fn flags(&self) -> NdFlag {
|
pub fn flags(&self) -> NdFlag {
|
||||||
self.flags
|
self.flags
|
||||||
}
|
}
|
||||||
@@ -71,11 +74,10 @@ impl Node {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for Node {
|
#[derive(Clone,Debug)]
|
||||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
pub enum LoopKind {
|
||||||
let raw = self.span().get_slice();
|
While,
|
||||||
write!(f, "{}", raw)
|
Until
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone,Debug)]
|
#[derive(Clone,Debug)]
|
||||||
@@ -84,6 +86,9 @@ pub enum NdRule {
|
|||||||
Command { argv: Vec<Token>, redirs: Vec<Redir> },
|
Command { argv: Vec<Token>, redirs: Vec<Redir> },
|
||||||
Assignment { assignments: Vec<Token>, cmd: Option<Box<Node>> },
|
Assignment { assignments: Vec<Token>, cmd: Option<Box<Node>> },
|
||||||
FuncDef { name: Token, body: Token },
|
FuncDef { name: Token, body: Token },
|
||||||
|
IfThen { cond_blocks: Vec<(Vec<Node>,Vec<Node>)>, else_block: Option<Vec<Node>> },
|
||||||
|
Loop { kind: LoopKind, cond: Vec<Node>, body: Vec<Node> },
|
||||||
|
ForLoop { vars: Vec<Token>, arr: Vec<Token>, body: Vec<Node> },
|
||||||
Subshell { body: Token, argv: Vec<Token>, redirs: Vec<Redir> },
|
Subshell { body: Token, argv: Vec<Token>, redirs: Vec<Redir> },
|
||||||
CmdList { cmds: Vec<(Option<CmdGuard>,Node)> },
|
CmdList { cmds: Vec<(Option<CmdGuard>,Node)> },
|
||||||
Pipeline { cmds: Vec<Node> }
|
Pipeline { cmds: Vec<Node> }
|
||||||
@@ -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.
|
/// Define a Node rule. The body of this macro becomes the implementation for the try_match() method for the rule.
|
||||||
macro_rules! ndrule_def {
|
macro_rules! ndrule_def {
|
||||||
($name:ident,$try:expr) => {
|
($name:ident,$shenv:ident,$try:expr) => {
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct $name;
|
pub struct $name;
|
||||||
impl ParseRule for $name {
|
impl ParseRule for $name {
|
||||||
fn try_match(input: &[Token]) -> ShResult<Option<Node>> {
|
fn try_match(input: &[Token],shenv: &mut ShEnv) -> ShResult<Option<Node>> {
|
||||||
$try(input)
|
$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
|
/// 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
|
/// Place the most specialized/specific rules first, and the most general rules last
|
||||||
macro_rules! try_rules {
|
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 {
|
if let Some(node) = result {
|
||||||
return Ok(Some(node))
|
return Ok(Some(node))
|
||||||
}
|
}
|
||||||
@@ -125,6 +130,9 @@ impl SynTree {
|
|||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self { tree: VecDeque::new() }
|
Self { tree: VecDeque::new() }
|
||||||
}
|
}
|
||||||
|
pub fn from_vec(nodes: Vec<Node>) -> Self {
|
||||||
|
Self { tree: VecDeque::from(nodes) }
|
||||||
|
}
|
||||||
pub fn push_node(&mut self, node: Node) {
|
pub fn push_node(&mut self, node: Node) {
|
||||||
self.tree.bpush(node)
|
self.tree.bpush(node)
|
||||||
}
|
}
|
||||||
@@ -133,16 +141,17 @@ impl SynTree {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Parser {
|
pub struct Parser<'a> {
|
||||||
token_stream: Vec<Token>,
|
token_stream: Vec<Token>,
|
||||||
|
shenv: &'a mut ShEnv,
|
||||||
ast: SynTree
|
ast: SynTree
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Parser {
|
impl<'a> Parser<'a> {
|
||||||
pub fn new(mut token_stream: Vec<Token>) -> Self {
|
pub fn new(mut token_stream: Vec<Token>, shenv: &'a mut ShEnv) -> Self {
|
||||||
log!(TRACE, "New parser");
|
log!(TRACE, "New parser");
|
||||||
token_stream.retain(|tk| !matches!(tk.rule(), TkRule::Whitespace | TkRule::Comment));
|
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<SynTree> {
|
pub fn parse(mut self) -> ShResult<SynTree> {
|
||||||
@@ -150,11 +159,10 @@ impl Parser {
|
|||||||
let mut lists = VecDeque::new();
|
let mut lists = VecDeque::new();
|
||||||
let token_slice = &*self.token_stream;
|
let token_slice = &*self.token_stream;
|
||||||
// Get the Main rule
|
// 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
|
// Extract the inner lists
|
||||||
if let NdRule::Main { ref mut cmd_lists } = node.rule_mut() {
|
if let NdRule::Main { ref mut cmd_lists } = node.rule_mut() {
|
||||||
while let Some(node) = cmd_lists.pop() {
|
while let Some(node) = cmd_lists.pop() {
|
||||||
log!(DEBUG, node);
|
|
||||||
lists.bpush(node)
|
lists.bpush(node)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -167,26 +175,42 @@ impl Parser {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_span(toks: &Vec<Token>) -> ShResult<Span> {
|
fn get_span(toks: &Vec<Token>, shenv: &mut ShEnv) -> ShResult<Rc<RefCell<Span>>> {
|
||||||
if toks.is_empty() {
|
if toks.is_empty() {
|
||||||
Err(ShErr::simple(ShErrKind::InternalErr, "Get_span was given an empty token list"))
|
Err(ShErr::simple(ShErrKind::InternalErr, "Get_span was given an empty token list"))
|
||||||
} else {
|
} else {
|
||||||
let start = toks.first().unwrap().span().start();
|
let start = toks.first().unwrap().span().borrow().start();
|
||||||
let end = toks.iter().last().unwrap().span().end();
|
let end = toks.iter().last().unwrap().span().borrow().end();
|
||||||
let input = toks.iter().last().unwrap().span().get_input();
|
let span = shenv.inputman_mut().new_span(start, end);
|
||||||
Ok(Span::new(input,start,end))
|
Ok(span)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_lists(mut tokens: &[Token], shenv: &mut ShEnv) -> (usize,Vec<Node>) {
|
||||||
|
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
|
// 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");
|
log!(TRACE, "Parsing main");
|
||||||
let mut cmd_lists = vec![];
|
let mut cmd_lists = vec![];
|
||||||
let mut node_toks = vec![];
|
let mut node_toks = vec![];
|
||||||
let mut token_slice = &*tokens;
|
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());
|
node_toks.extend(node.tokens().clone());
|
||||||
token_slice = &token_slice[node.len()..];
|
token_slice = &token_slice[node.len()..];
|
||||||
cmd_lists.push(node);
|
cmd_lists.push(node);
|
||||||
@@ -195,7 +219,7 @@ ndrule_def!(Main, |tokens: &[Token]| {
|
|||||||
if cmd_lists.is_empty() {
|
if cmd_lists.is_empty() {
|
||||||
return Ok(None)
|
return Ok(None)
|
||||||
}
|
}
|
||||||
let span = get_span(&node_toks)?;
|
let span = get_span(&node_toks,shenv)?;
|
||||||
let node = Node {
|
let node = Node {
|
||||||
node_rule: NdRule::Main { cmd_lists },
|
node_rule: NdRule::Main { cmd_lists },
|
||||||
tokens: node_toks,
|
tokens: node_toks,
|
||||||
@@ -205,14 +229,14 @@ ndrule_def!(Main, |tokens: &[Token]| {
|
|||||||
Ok(Some(node))
|
Ok(Some(node))
|
||||||
});
|
});
|
||||||
|
|
||||||
ndrule_def!(CmdList, |tokens: &[Token]| {
|
ndrule_def!(CmdList, shenv, |tokens: &[Token], shenv: &mut ShEnv| {
|
||||||
log!(TRACE, "Parsing cmdlist");
|
log!(TRACE, "Parsing cmdlist");
|
||||||
let mut commands: Vec<(Option<CmdGuard>,Node)> = vec![];
|
let mut commands: Vec<(Option<CmdGuard>,Node)> = vec![];
|
||||||
let mut node_toks = vec![];
|
let mut node_toks = vec![];
|
||||||
let mut token_slice = &*tokens;
|
let mut token_slice = &*tokens;
|
||||||
let mut cmd_guard = None; // Operators like '&&' and '||'
|
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
|
// Add sub-node tokens to our tokens
|
||||||
node_toks.extend(node.tokens().clone());
|
node_toks.extend(node.tokens().clone());
|
||||||
// Reflect changes in the token slice
|
// Reflect changes in the token slice
|
||||||
@@ -221,10 +245,13 @@ ndrule_def!(CmdList, |tokens: &[Token]| {
|
|||||||
log!(DEBUG, token_slice);
|
log!(DEBUG, token_slice);
|
||||||
// Push sub-node
|
// Push sub-node
|
||||||
if let NdRule::Command { argv, redirs: _ } = node.rule() {
|
if let NdRule::Command { argv, redirs: _ } = node.rule() {
|
||||||
if argv.first().is_some_and(|arg| BUILTINS.contains(&arg.to_string().as_str())) {
|
if let Some(arg) = argv.first() {
|
||||||
|
let slice = shenv.input_slice(arg.span().clone());
|
||||||
|
if BUILTINS.contains(&slice) {
|
||||||
*node.flags_mut() |= NdFlag::BUILTIN;
|
*node.flags_mut() |= NdFlag::BUILTIN;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
commands.push((cmd_guard.take(),node));
|
commands.push((cmd_guard.take(),node));
|
||||||
|
|
||||||
// If the next token is '&&' or '||' then we set cmd_guard and go again
|
// If the next token is '&&' or '||' then we set cmd_guard and go again
|
||||||
@@ -244,7 +271,7 @@ ndrule_def!(CmdList, |tokens: &[Token]| {
|
|||||||
if node_toks.is_empty() {
|
if node_toks.is_empty() {
|
||||||
return Ok(None)
|
return Ok(None)
|
||||||
}
|
}
|
||||||
let span = get_span(&node_toks)?;
|
let span = get_span(&node_toks,shenv)?;
|
||||||
let node = Node {
|
let node = Node {
|
||||||
node_rule: NdRule::CmdList { cmds: commands },
|
node_rule: NdRule::CmdList { cmds: commands },
|
||||||
tokens: node_toks,
|
tokens: node_toks,
|
||||||
@@ -254,8 +281,8 @@ ndrule_def!(CmdList, |tokens: &[Token]| {
|
|||||||
Ok(Some(node))
|
Ok(Some(node))
|
||||||
});
|
});
|
||||||
|
|
||||||
ndrule_def!(Expr, |tokens: &[Token]| {
|
ndrule_def!(Expr, shenv, |tokens: &[Token], shenv: &mut ShEnv| {
|
||||||
try_rules!(tokens,
|
try_rules!(tokens, shenv,
|
||||||
ShellCmd,
|
ShellCmd,
|
||||||
Pipeline,
|
Pipeline,
|
||||||
Subshell,
|
Subshell,
|
||||||
@@ -264,8 +291,8 @@ ndrule_def!(Expr, |tokens: &[Token]| {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
// Used in pipelines to avoid recursion
|
// Used in pipelines to avoid recursion
|
||||||
ndrule_def!(ExprNoPipeline, |tokens: &[Token]| {
|
ndrule_def!(ExprNoPipeline, shenv, |tokens: &[Token], shenv: &mut ShEnv| {
|
||||||
try_rules!(tokens,
|
try_rules!(tokens, shenv,
|
||||||
ShellCmd,
|
ShellCmd,
|
||||||
Subshell,
|
Subshell,
|
||||||
Assignment,
|
Assignment,
|
||||||
@@ -273,13 +300,352 @@ ndrule_def!(ExprNoPipeline, |tokens: &[Token]| {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
ndrule_def!(ShellCmd, |tokens: &[Token]| {
|
ndrule_def!(ShellCmd, shenv, |tokens: &[Token], shenv: &mut ShEnv| {
|
||||||
try_rules!(tokens,
|
try_rules!(tokens, shenv,
|
||||||
|
IfThen,
|
||||||
|
Loop,
|
||||||
FuncDef
|
FuncDef
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
ndrule_def!(FuncDef, |tokens: &[Token]| {
|
ndrule_def!(ForLoop, shenv, |mut tokens: &[Token], shenv: &mut ShEnv| {
|
||||||
|
let err = |msg: &str, span: Rc<RefCell<Span>>, 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<Node>;
|
||||||
|
|
||||||
|
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<RefCell<Span>>, 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<Vec<Node>> = 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<RefCell<Span>>, 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<Node>;
|
||||||
|
let body: Vec<Node>;
|
||||||
|
|
||||||
|
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 tokens_iter = tokens.iter();
|
||||||
let mut node_toks = vec![];
|
let mut node_toks = vec![];
|
||||||
let name: Token;
|
let name: Token;
|
||||||
@@ -307,7 +673,7 @@ ndrule_def!(FuncDef, |tokens: &[Token]| {
|
|||||||
return Ok(None)
|
return Ok(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
let span = get_span(&node_toks)?;
|
let span = get_span(&node_toks,shenv)?;
|
||||||
let node = Node {
|
let node = Node {
|
||||||
node_rule: NdRule::FuncDef { name, body },
|
node_rule: NdRule::FuncDef { name, body },
|
||||||
tokens: node_toks,
|
tokens: node_toks,
|
||||||
@@ -317,7 +683,7 @@ ndrule_def!(FuncDef, |tokens: &[Token]| {
|
|||||||
Ok(Some(node))
|
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 tokens_iter = tokens.iter();
|
||||||
let mut node_toks = vec![];
|
let mut node_toks = vec![];
|
||||||
let mut argv = vec![];
|
let mut argv = vec![];
|
||||||
@@ -350,27 +716,29 @@ ndrule_def!(Subshell, |tokens: &[Token]| {
|
|||||||
TkRule::RedirOp => {
|
TkRule::RedirOp => {
|
||||||
node_toks.push(token.clone());
|
node_toks.push(token.clone());
|
||||||
// Get the raw redirection text, e.g. "1>&2" or "2>" or ">>" or something
|
// 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();
|
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 there isn't an FD target, get the next token and use it as the filename
|
||||||
if redir_bldr.tgt().is_none() {
|
if redir_bldr.tgt().is_none() {
|
||||||
if let Some(filename) = tokens_iter.next() {
|
if let Some(filename) = tokens_iter.next() {
|
||||||
// Make sure it's a word and not an operator or something
|
// 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");
|
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)
|
return Err(err)
|
||||||
}
|
}
|
||||||
node_toks.push(filename.clone());
|
node_toks.push(filename.clone());
|
||||||
// Construct the Path object
|
// 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 filename_path = PathBuf::from(filename_raw);
|
||||||
let tgt = RedirTarget::File(filename_path);
|
let tgt = RedirTarget::File(filename_path);
|
||||||
// Update the builder
|
// Update the builder
|
||||||
redir_bldr = redir_bldr.with_tgt(tgt);
|
redir_bldr = redir_bldr.with_tgt(tgt);
|
||||||
} else {
|
} else {
|
||||||
let mut err = ShErr::simple(ShErrKind::ParseErr, "Did not find a target for this redirection");
|
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)
|
return Err(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -379,7 +747,7 @@ ndrule_def!(Subshell, |tokens: &[Token]| {
|
|||||||
_ => break
|
_ => break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let span = get_span(&node_toks)?;
|
let span = get_span(&node_toks,shenv)?;
|
||||||
let node = Node {
|
let node = Node {
|
||||||
node_rule: NdRule::Subshell { body, argv, redirs },
|
node_rule: NdRule::Subshell { body, argv, redirs },
|
||||||
tokens: node_toks,
|
tokens: node_toks,
|
||||||
@@ -394,7 +762,7 @@ ndrule_def!(Subshell, |tokens: &[Token]| {
|
|||||||
Ok(None)
|
Ok(None)
|
||||||
});
|
});
|
||||||
|
|
||||||
ndrule_def!(Pipeline, |mut tokens: &[Token]| {
|
ndrule_def!(Pipeline, shenv, |mut tokens: &[Token], shenv: &mut ShEnv| {
|
||||||
log!(TRACE, "Parsing pipeline");
|
log!(TRACE, "Parsing pipeline");
|
||||||
let mut tokens_iter = tokens.iter().peekable();
|
let mut tokens_iter = tokens.iter().peekable();
|
||||||
let mut node_toks = vec![];
|
let mut node_toks = vec![];
|
||||||
@@ -411,7 +779,7 @@ ndrule_def!(Pipeline, |mut tokens: &[Token]| {
|
|||||||
}
|
}
|
||||||
_ => { /* Keep going */ }
|
_ => { /* 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
|
// Add sub-node's tokens to our tokens
|
||||||
node_toks.extend(cmd.tokens().clone());
|
node_toks.extend(cmd.tokens().clone());
|
||||||
|
|
||||||
@@ -422,10 +790,13 @@ ndrule_def!(Pipeline, |mut tokens: &[Token]| {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let NdRule::Command { argv, redirs: _ } = cmd.rule() {
|
if let NdRule::Command { argv, redirs: _ } = cmd.rule() {
|
||||||
if argv.first().is_some_and(|arg| BUILTINS.contains(&arg.to_string().as_str())) {
|
if let Some(arg) = argv.first() {
|
||||||
|
let slice = shenv.input_slice(arg.span().clone());
|
||||||
|
if BUILTINS.contains(&slice) {
|
||||||
*cmd.flags_mut() |= NdFlag::BUILTIN;
|
*cmd.flags_mut() |= NdFlag::BUILTIN;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// Push sub-node
|
// Push sub-node
|
||||||
cmds.push(cmd);
|
cmds.push(cmd);
|
||||||
|
|
||||||
@@ -458,7 +829,7 @@ ndrule_def!(Pipeline, |mut tokens: &[Token]| {
|
|||||||
if node_toks.is_empty() {
|
if node_toks.is_empty() {
|
||||||
return Ok(None)
|
return Ok(None)
|
||||||
}
|
}
|
||||||
let span = get_span(&node_toks)?;
|
let span = get_span(&node_toks,shenv)?;
|
||||||
let node = Node {
|
let node = Node {
|
||||||
node_rule: NdRule::Pipeline { cmds },
|
node_rule: NdRule::Pipeline { cmds },
|
||||||
tokens: node_toks,
|
tokens: node_toks,
|
||||||
@@ -468,7 +839,7 @@ ndrule_def!(Pipeline, |mut tokens: &[Token]| {
|
|||||||
Ok(Some(node))
|
Ok(Some(node))
|
||||||
});
|
});
|
||||||
|
|
||||||
ndrule_def!(Command, |tokens: &[Token]| {
|
ndrule_def!(Command, shenv, |tokens: &[Token], shenv: &mut ShEnv| {
|
||||||
log!(TRACE, "Parsing command");
|
log!(TRACE, "Parsing command");
|
||||||
let mut tokens = tokens.iter().peekable();
|
let mut tokens = tokens.iter().peekable();
|
||||||
let mut node_toks = vec![];
|
let mut node_toks = vec![];
|
||||||
@@ -495,40 +866,49 @@ ndrule_def!(Command, |tokens: &[Token]| {
|
|||||||
}
|
}
|
||||||
TkRule::RedirOp => {
|
TkRule::RedirOp => {
|
||||||
// Get the raw redirection text, e.g. "1>&2" or "2>" or ">>" or something
|
// 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();
|
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 there isn't an FD target, get the next token and use it as the filename
|
||||||
if redir_bldr.tgt().is_none() {
|
if redir_bldr.tgt().is_none() {
|
||||||
if let Some(filename) = tokens.next() {
|
if let Some(filename) = tokens.next() {
|
||||||
// Make sure it's a word and not an operator or something
|
// 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");
|
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)
|
return Err(err)
|
||||||
}
|
}
|
||||||
node_toks.push(filename.clone());
|
node_toks.push(filename.clone());
|
||||||
// Construct the Path object
|
// 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 filename_path = PathBuf::from(filename_raw);
|
||||||
let tgt = RedirTarget::File(filename_path);
|
let tgt = RedirTarget::File(filename_path);
|
||||||
// Update the builder
|
// Update the builder
|
||||||
redir_bldr = redir_bldr.with_tgt(tgt);
|
redir_bldr = redir_bldr.with_tgt(tgt);
|
||||||
} else {
|
} else {
|
||||||
let mut err = ShErr::simple(ShErrKind::ParseErr, "Did not find a target for this redirection");
|
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)
|
return Err(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
redirs.push(redir_bldr.build());
|
redirs.push(redir_bldr.build());
|
||||||
}
|
}
|
||||||
TkRule::Sep => break,
|
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() {
|
if node_toks.is_empty() {
|
||||||
return Ok(None)
|
return Ok(None)
|
||||||
}
|
}
|
||||||
let span = get_span(&node_toks)?;
|
let span = get_span(&node_toks,shenv)?;
|
||||||
if !argv.is_empty() {
|
if !argv.is_empty() {
|
||||||
let node = Node {
|
let node = Node {
|
||||||
node_rule: NdRule::Command { argv, redirs },
|
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");
|
log!(TRACE, "Parsing assignment");
|
||||||
let mut tokens = tokens.into_iter().peekable();
|
let mut tokens = tokens.into_iter().peekable();
|
||||||
let mut node_toks = vec![];
|
let mut node_toks = vec![];
|
||||||
@@ -559,11 +939,11 @@ ndrule_def!(Assignment, |tokens: &[Token]| {
|
|||||||
if tokens.peek().is_some() {
|
if tokens.peek().is_some() {
|
||||||
let tokens_vec: Vec<Token> = tokens.into_iter().map(|token| token.clone()).collect();
|
let tokens_vec: Vec<Token> = tokens.into_iter().map(|token| token.clone()).collect();
|
||||||
let tokens_slice = &tokens_vec;
|
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 {
|
if let Some(ref cmd) = cmd {
|
||||||
node_toks.extend(cmd.tokens().clone());
|
node_toks.extend(cmd.tokens().clone());
|
||||||
}
|
}
|
||||||
let span = get_span(&node_toks)?;
|
let span = get_span(&node_toks,shenv)?;
|
||||||
let node = Node {
|
let node = Node {
|
||||||
node_rule: NdRule::Assignment { assignments, cmd },
|
node_rule: NdRule::Assignment { assignments, cmd },
|
||||||
tokens: node_toks,
|
tokens: node_toks,
|
||||||
@@ -572,7 +952,7 @@ ndrule_def!(Assignment, |tokens: &[Token]| {
|
|||||||
};
|
};
|
||||||
return Ok(Some(node))
|
return Ok(Some(node))
|
||||||
} else {
|
} else {
|
||||||
let span = get_span(&node_toks)?;
|
let span = get_span(&node_toks,shenv)?;
|
||||||
let node = Node {
|
let node = Node {
|
||||||
node_rule: NdRule::Assignment { assignments, cmd: None },
|
node_rule: NdRule::Assignment { assignments, cmd: None },
|
||||||
tokens: node_toks,
|
tokens: node_toks,
|
||||||
|
|||||||
@@ -97,12 +97,15 @@ pub use crate::{
|
|||||||
},
|
},
|
||||||
sys::{
|
sys::{
|
||||||
self,
|
self,
|
||||||
|
sh_quit,
|
||||||
|
read_to_string,
|
||||||
write_err,
|
write_err,
|
||||||
write_out,
|
write_out,
|
||||||
c_pipe,
|
c_pipe,
|
||||||
execvpe
|
execvpe
|
||||||
},
|
},
|
||||||
error::{
|
error::{
|
||||||
|
ResultExt,
|
||||||
ShErrKind,
|
ShErrKind,
|
||||||
ShErr,
|
ShErr,
|
||||||
ShResult
|
ShResult
|
||||||
@@ -114,6 +117,7 @@ pub use crate::{
|
|||||||
pwd::pwd,
|
pwd::pwd,
|
||||||
read::read_builtin,
|
read::read_builtin,
|
||||||
alias::alias,
|
alias::alias,
|
||||||
|
control_flow::sh_flow,
|
||||||
jobctl::{
|
jobctl::{
|
||||||
continue_job,
|
continue_job,
|
||||||
jobs
|
jobs
|
||||||
@@ -140,7 +144,10 @@ pub use crate::{
|
|||||||
exec_ctx::ExecFlags,
|
exec_ctx::ExecFlags,
|
||||||
shenv::ShEnv
|
shenv::ShEnv
|
||||||
},
|
},
|
||||||
execute::Executor,
|
execute::{
|
||||||
|
exec_input,
|
||||||
|
Executor,
|
||||||
|
},
|
||||||
parse::{
|
parse::{
|
||||||
parse::{
|
parse::{
|
||||||
Node,
|
Node,
|
||||||
|
|||||||
@@ -1,19 +1,22 @@
|
|||||||
use rustyline::highlight::Highlighter;
|
use rustyline::highlight::Highlighter;
|
||||||
use sys::get_bin_path;
|
use sys::get_bin_path;
|
||||||
|
|
||||||
use crate::prelude::*;
|
use crate::{parse::lex::KEYWORDS, prelude::*};
|
||||||
|
|
||||||
use super::readline::SynHelper;
|
use super::readline::SynHelper;
|
||||||
|
|
||||||
impl<'a> Highlighter for SynHelper<'a> {
|
impl<'a> Highlighter for SynHelper<'a> {
|
||||||
fn highlight<'l>(&self, line: &'l str, pos: usize) -> std::borrow::Cow<'l, str> {
|
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 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 is_command = true;
|
||||||
let mut in_array = false;
|
let mut in_array = false;
|
||||||
|
|
||||||
while let Some(token) = tokens.next() {
|
while let Some(token) = tokens.next() {
|
||||||
let raw = token.to_string();
|
let raw = token.as_raw(&mut shenv_clone);
|
||||||
match token.rule() {
|
match token.rule() {
|
||||||
TkRule::Comment => {
|
TkRule::Comment => {
|
||||||
let styled = &raw.styled(Style::BrightBlack);
|
let styled = &raw.styled(Style::BrightBlack);
|
||||||
@@ -35,7 +38,7 @@ impl<'a> Highlighter for SynHelper<'a> {
|
|||||||
let rebuilt = format!("{styled}()");
|
let rebuilt = format!("{styled}()");
|
||||||
result.push_str(&rebuilt);
|
result.push_str(&rebuilt);
|
||||||
}
|
}
|
||||||
TkRule::Keyword => {
|
_ if KEYWORDS.contains(&token.rule()) => {
|
||||||
if &raw == "for" {
|
if &raw == "for" {
|
||||||
in_array = true;
|
in_array = true;
|
||||||
}
|
}
|
||||||
@@ -76,7 +79,7 @@ impl<'a> Highlighter for SynHelper<'a> {
|
|||||||
result.push_str(&raw);
|
result.push_str(&raw);
|
||||||
|
|
||||||
} else if is_command {
|
} 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_alias(&raw).is_some() ||
|
||||||
self.shenv.logic().get_function(&raw).is_some() ||
|
self.shenv.logic().get_function(&raw).is_some() ||
|
||||||
BUILTINS.contains(&raw.as_str()) {
|
BUILTINS.contains(&raw.as_str()) {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ use rustyline::{config::Configurer, history::{DefaultHistory, History}, ColorMod
|
|||||||
|
|
||||||
pub mod readline;
|
pub mod readline;
|
||||||
pub mod highlight;
|
pub mod highlight;
|
||||||
|
pub mod validate;
|
||||||
|
|
||||||
fn init_rl<'a>(shenv: &'a mut ShEnv) -> Editor<SynHelper<'a>, DefaultHistory> {
|
fn init_rl<'a>(shenv: &'a mut ShEnv) -> Editor<SynHelper<'a>, DefaultHistory> {
|
||||||
let hist_path = std::env::var("FERN_HIST").unwrap_or_default();
|
let hist_path = std::env::var("FERN_HIST").unwrap_or_default();
|
||||||
@@ -25,7 +26,7 @@ fn init_rl<'a>(shenv: &'a mut ShEnv) -> Editor<SynHelper<'a>, DefaultHistory> {
|
|||||||
editor
|
editor
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read_line<'a>(shenv: &'a mut ShEnv) -> ShResult<String> {
|
pub fn read_line(shenv: &mut ShEnv) -> ShResult<String> {
|
||||||
log!(TRACE, "Entering prompt");
|
log!(TRACE, "Entering prompt");
|
||||||
let prompt = "$ ".styled(Style::Green | Style::Bold);
|
let prompt = "$ ".styled(Style::Green | Style::Bold);
|
||||||
let mut editor = init_rl(shenv);
|
let mut editor = init_rl(shenv);
|
||||||
|
|||||||
@@ -33,11 +33,6 @@ impl<'a> SynHelper<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Validator for SynHelper<'a> {
|
|
||||||
fn validate(&self, ctx: &mut rustyline::validate::ValidationContext) -> rustyline::Result<rustyline::validate::ValidationResult> {
|
|
||||||
Ok(ValidationResult::Valid(None))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
impl<'a> Completer for SynHelper<'a> {
|
impl<'a> Completer for SynHelper<'a> {
|
||||||
|
|||||||
115
src/prompt/validate.rs
Normal file
115
src/prompt/validate.rs
Normal file
@@ -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<TkRule>> = 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<rustyline::validate::ValidationResult> {
|
||||||
|
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),
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
61
src/shellenv/input.rs
Normal file
61
src/shellenv/input.rs
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
use std::cell::Ref;
|
||||||
|
|
||||||
|
use crate::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Clone,Debug)]
|
||||||
|
pub struct InputMan {
|
||||||
|
input: Option<String>,
|
||||||
|
spans: Vec<Rc<RefCell<Span>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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<RefCell<Span>> {
|
||||||
|
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<Rc<RefCell<Span>>> {
|
||||||
|
&mut self.spans
|
||||||
|
}
|
||||||
|
pub fn clamp(&self, span: Rc<RefCell<Span>>) {
|
||||||
|
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<RefCell<Span>>) -> 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])
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@ pub mod exec_ctx;
|
|||||||
pub mod meta;
|
pub mod meta;
|
||||||
pub mod shenv;
|
pub mod shenv;
|
||||||
pub mod vars;
|
pub mod vars;
|
||||||
|
pub mod input;
|
||||||
|
|
||||||
/// Calls attach_tty() on the shell's process group to retake control of the terminal
|
/// Calls attach_tty() on the shell's process group to retake control of the terminal
|
||||||
pub fn take_term() -> ShResult<()> {
|
pub fn take_term() -> ShResult<()> {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ pub struct ShEnv {
|
|||||||
vars: shellenv::vars::VarTab,
|
vars: shellenv::vars::VarTab,
|
||||||
logic: shellenv::logic::LogTab,
|
logic: shellenv::logic::LogTab,
|
||||||
meta: shellenv::meta::MetaTab,
|
meta: shellenv::meta::MetaTab,
|
||||||
|
input_man: shellenv::input::InputMan,
|
||||||
ctx: shellenv::exec_ctx::ExecCtx
|
ctx: shellenv::exec_ctx::ExecCtx
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -14,6 +15,7 @@ impl ShEnv {
|
|||||||
vars: shellenv::vars::VarTab::new(),
|
vars: shellenv::vars::VarTab::new(),
|
||||||
logic: shellenv::logic::LogTab::new(),
|
logic: shellenv::logic::LogTab::new(),
|
||||||
meta: shellenv::meta::MetaTab::new(),
|
meta: shellenv::meta::MetaTab::new(),
|
||||||
|
input_man: shellenv::input::InputMan::new(),
|
||||||
ctx: shellenv::exec_ctx::ExecCtx::new(),
|
ctx: shellenv::exec_ctx::ExecCtx::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -26,6 +28,58 @@ impl ShEnv {
|
|||||||
pub fn meta(&self) -> &shellenv::meta::MetaTab {
|
pub fn meta(&self) -> &shellenv::meta::MetaTab {
|
||||||
&self.meta
|
&self.meta
|
||||||
}
|
}
|
||||||
|
pub fn input_slice(&self, span: Rc<RefCell<Span>>) -> &str {
|
||||||
|
&self.input_man.get_slice(span).unwrap_or_default()
|
||||||
|
}
|
||||||
|
pub fn expand_input(&mut self, new: &str, repl_span: Rc<RefCell<Span>>) -> Vec<Token> {
|
||||||
|
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 {
|
pub fn meta_mut(&mut self) -> &mut shellenv::meta::MetaTab {
|
||||||
&mut self.meta
|
&mut self.meta
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user