Early work on scripting features
This commit is contained in:
@@ -1,20 +1,14 @@
|
|||||||
use crate::{jobs::{ChildProc, JobBldr}, libsh::error::{ShErr, ShErrKind, ShResult}, parse::{execute::prepare_argv, NdRule, Node}, prelude::*, state::write_vars};
|
use crate::{jobs::{ChildProc, JobBldr}, libsh::error::{ShErr, ShErrKind, ShResult}, parse::{execute::prepare_argv, NdRule, Node}, prelude::*, state::{self, write_vars}};
|
||||||
|
|
||||||
|
use super::setup_builtin;
|
||||||
|
|
||||||
pub fn cd(node: Node, job: &mut JobBldr) -> ShResult<()> {
|
pub fn cd(node: Node, job: &mut JobBldr) -> ShResult<()> {
|
||||||
let NdRule::Command { assignments: _, argv } = node.class else {
|
let NdRule::Command { assignments: _, argv } = node.class else {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
let child_pgid = if let Some(pgid) = job.pgid() {
|
let (argv,_) = setup_builtin(argv,job,None)?;
|
||||||
pgid
|
|
||||||
} else {
|
|
||||||
job.set_pgid(Pid::this());
|
|
||||||
Pid::this()
|
|
||||||
};
|
|
||||||
let child = ChildProc::new(Pid::this(), Some("cd"), Some(child_pgid))?;
|
|
||||||
job.push_child(child);
|
|
||||||
|
|
||||||
let argv = prepare_argv(argv);
|
|
||||||
let new_dir = if let Some((arg,_)) = argv.into_iter().skip(1).next() {
|
let new_dir = if let Some((arg,_)) = argv.into_iter().skip(1).next() {
|
||||||
PathBuf::from(arg)
|
PathBuf::from(arg)
|
||||||
} else {
|
} else {
|
||||||
@@ -25,5 +19,6 @@ pub fn cd(node: Node, job: &mut JobBldr) -> ShResult<()> {
|
|||||||
let new_dir = env::current_dir().unwrap();
|
let new_dir = env::current_dir().unwrap();
|
||||||
env::set_var("PWD", new_dir);
|
env::set_var("PWD", new_dir);
|
||||||
|
|
||||||
|
state::set_status(0);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use crate::{jobs::{ChildProc, JobBldr}, libsh::error::ShResult, parse::{execute::prepare_argv, NdRule, Node}, prelude::*, procio::{borrow_fd, IoStack}};
|
use crate::{builtin::setup_builtin, jobs::{ChildProc, JobBldr}, libsh::error::ShResult, parse::{execute::prepare_argv, NdRule, Node}, prelude::*, procio::{borrow_fd, IoStack}, state};
|
||||||
|
|
||||||
pub fn echo(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
|
pub fn echo(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
|
||||||
let NdRule::Command { assignments: _, argv } = node.class else {
|
let NdRule::Command { assignments: _, argv } = node.class else {
|
||||||
@@ -6,33 +6,22 @@ pub fn echo(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<(
|
|||||||
};
|
};
|
||||||
assert!(!argv.is_empty());
|
assert!(!argv.is_empty());
|
||||||
|
|
||||||
let child_pgid = if let Some(pgid) = job.pgid() {
|
let (argv,io_frame) = setup_builtin(argv, job, Some((io_stack,node.redirs)))?;
|
||||||
pgid
|
|
||||||
} else {
|
|
||||||
job.set_pgid(Pid::this());
|
|
||||||
Pid::this()
|
|
||||||
};
|
|
||||||
let child = ChildProc::new(Pid::this(), Some("echo"), Some(child_pgid))?;
|
|
||||||
job.push_child(child);
|
|
||||||
|
|
||||||
io_stack.append_to_frame(node.redirs);
|
|
||||||
let mut io_frame = io_stack.pop_frame();
|
|
||||||
|
|
||||||
io_frame.redirect()?;
|
|
||||||
|
|
||||||
let stdout = borrow_fd(STDOUT_FILENO);
|
let stdout = borrow_fd(STDOUT_FILENO);
|
||||||
|
flog!(DEBUG, argv);
|
||||||
|
|
||||||
let mut echo_output = prepare_argv(argv)
|
let mut echo_output = argv.into_iter()
|
||||||
.into_iter()
|
|
||||||
.map(|a| a.0) // Extract the String from the tuple of (String,Span)
|
.map(|a| a.0) // Extract the String from the tuple of (String,Span)
|
||||||
.skip(1) // Skip 'echo'
|
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.join(" ");
|
.join(" ");
|
||||||
|
|
||||||
echo_output.push('\n');
|
echo_output.push('\n');
|
||||||
|
flog!(DEBUG, echo_output);
|
||||||
|
|
||||||
write(stdout, echo_output.as_bytes())?;
|
write(stdout, echo_output.as_bytes())?;
|
||||||
|
|
||||||
io_frame.restore()?;
|
io_frame.unwrap().restore()?;
|
||||||
|
state::set_status(0);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,32 +1,40 @@
|
|||||||
use crate::{jobs::{ChildProc, JobBldr}, libsh::error::{ShErr, ShErrKind, ShResult}, parse::{execute::prepare_argv, NdRule, Node}, prelude::*};
|
use crate::{jobs::{ChildProc, JobBldr}, libsh::error::{ShErr, ShErrKind, ShResult}, parse::{execute::prepare_argv, NdRule, Node}, prelude::*, procio::{borrow_fd, IoStack}, state};
|
||||||
|
|
||||||
pub fn export(node: Node, job: &mut JobBldr) -> ShResult<()> {
|
use super::setup_builtin;
|
||||||
|
|
||||||
|
pub fn export(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
|
||||||
let NdRule::Command { assignments: _, argv } = node.class else {
|
let NdRule::Command { assignments: _, argv } = node.class else {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
let child_pgid = if let Some(pgid) = job.pgid() {
|
let (argv,io_frame) = setup_builtin(argv, job, Some((io_stack,node.redirs)))?;
|
||||||
pgid
|
|
||||||
|
if argv.is_empty() {
|
||||||
|
// Display the environment variables
|
||||||
|
let mut env_output = env::vars()
|
||||||
|
.map(|var| format!("{}={}",var.0,var.1)) // Get all of them, zip them into one string
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
env_output.sort(); // Sort them alphabetically
|
||||||
|
let mut env_output = env_output.join("\n"); // Join them with newlines
|
||||||
|
env_output.push('\n'); // Push a final newline
|
||||||
|
|
||||||
|
let stdout = borrow_fd(STDOUT_FILENO);
|
||||||
|
write(stdout, env_output.as_bytes())?; // Write it
|
||||||
} else {
|
} else {
|
||||||
job.set_pgid(Pid::this());
|
for (arg,span) in argv {
|
||||||
Pid::this()
|
let Some((var,val)) = arg.split_once('=') else {
|
||||||
};
|
return Err(
|
||||||
let child = ChildProc::new(Pid::this(), Some("export"), Some(child_pgid))?;
|
ShErr::full(
|
||||||
job.push_child(child);
|
ShErrKind::SyntaxErr,
|
||||||
|
"export: Expected an assignment in export args",
|
||||||
let argv = prepare_argv(argv);
|
span.into()
|
||||||
|
)
|
||||||
for (arg,span) in argv {
|
|
||||||
let Some((var,val)) = arg.split_once('=') else {
|
|
||||||
return Err(
|
|
||||||
ShErr::full(
|
|
||||||
ShErrKind::ExecFail,
|
|
||||||
"Expected an assignment in export args",
|
|
||||||
span.into()
|
|
||||||
)
|
)
|
||||||
)
|
};
|
||||||
};
|
env::set_var(var, val);
|
||||||
env::set_var(var, val);
|
}
|
||||||
}
|
}
|
||||||
|
io_frame.unwrap().restore()?;
|
||||||
|
state::set_status(0);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
use crate::{jobs::{ChildProc, JobBldr, JobCmdFlags, JobID}, libsh::error::{ErrSpan, ShErr, ShErrKind, ShResult}, parse::{execute::prepare_argv, lex::Span, NdRule, Node}, prelude::*, procio::{borrow_fd, IoStack}, state::{self, read_jobs, write_jobs}};
|
use crate::{jobs::{ChildProc, JobBldr, JobCmdFlags, JobID}, libsh::error::{ErrSpan, ShErr, ShErrKind, ShResult}, parse::{execute::prepare_argv, lex::Span, NdRule, Node}, prelude::*, procio::{borrow_fd, IoStack}, state::{self, read_jobs, write_jobs}};
|
||||||
|
|
||||||
|
use super::setup_builtin;
|
||||||
|
|
||||||
pub enum JobBehavior {
|
pub enum JobBehavior {
|
||||||
Foregound,
|
Foregound,
|
||||||
Background
|
Background
|
||||||
@@ -11,22 +13,12 @@ pub fn continue_job(node: Node, job: &mut JobBldr, behavior: JobBehavior) -> ShR
|
|||||||
JobBehavior::Foregound => "fg",
|
JobBehavior::Foregound => "fg",
|
||||||
JobBehavior::Background => "bg"
|
JobBehavior::Background => "bg"
|
||||||
};
|
};
|
||||||
let NdRule::Command { assignments, argv } = node.class else {
|
let NdRule::Command { assignments: _, argv } = node.class else {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
let child_pgid = if let Some(pgid) = job.pgid() {
|
let (argv,_) = setup_builtin(argv, job, None)?;
|
||||||
pgid
|
let mut argv = argv.into_iter();
|
||||||
} else {
|
|
||||||
job.set_pgid(Pid::this());
|
|
||||||
Pid::this()
|
|
||||||
};
|
|
||||||
let child = ChildProc::new(Pid::this(), Some(cmd), Some(child_pgid))?;
|
|
||||||
job.push_child(child);
|
|
||||||
|
|
||||||
let mut argv = prepare_argv(argv)
|
|
||||||
.into_iter()
|
|
||||||
.skip(1);
|
|
||||||
|
|
||||||
if read_jobs(|j| j.get_fg().is_some()) {
|
if read_jobs(|j| j.get_fg().is_some()) {
|
||||||
return Err(
|
return Err(
|
||||||
@@ -145,28 +137,14 @@ fn parse_job_id(arg: &str, blame: Span) -> ShResult<usize> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn jobs(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
|
pub fn jobs(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
|
||||||
let NdRule::Command { assignments, argv } = node.class else {
|
let NdRule::Command { assignments: _, argv } = node.class else {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
let child_pgid = if let Some(pgid) = job.pgid() {
|
let (argv,io_frame) = setup_builtin(argv, job, Some((io_stack,node.redirs)))?;
|
||||||
pgid
|
|
||||||
} else {
|
|
||||||
job.set_pgid(Pid::this());
|
|
||||||
Pid::this()
|
|
||||||
};
|
|
||||||
let child = ChildProc::new(Pid::this(), Some("jobs"), Some(child_pgid))?;
|
|
||||||
job.push_child(child);
|
|
||||||
|
|
||||||
let mut argv = prepare_argv(argv)
|
|
||||||
.into_iter()
|
|
||||||
.skip(1);
|
|
||||||
|
|
||||||
let mut io_frame = io_stack.pop_frame();
|
|
||||||
io_frame.redirect()?;
|
|
||||||
|
|
||||||
let mut flags = JobCmdFlags::empty();
|
let mut flags = JobCmdFlags::empty();
|
||||||
while let Some((arg,span)) = argv.next() {
|
for (arg,span) in argv {
|
||||||
let mut chars = arg.chars().peekable();
|
let mut chars = arg.chars().peekable();
|
||||||
if chars.peek().is_none_or(|ch| *ch != '-') {
|
if chars.peek().is_none_or(|ch| *ch != '-') {
|
||||||
return Err(
|
return Err(
|
||||||
@@ -198,7 +176,7 @@ pub fn jobs(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
write_jobs(|j| j.print_jobs(flags))?;
|
write_jobs(|j| j.print_jobs(flags))?;
|
||||||
io_frame.restore()?;
|
io_frame.unwrap().restore()?;
|
||||||
state::set_status(0);
|
state::set_status(0);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
use nix::unistd::Pid;
|
||||||
|
|
||||||
|
use crate::{jobs::{ChildProc, JobBldr}, libsh::error::ShResult, parse::{execute::prepare_argv, lex::{Span, Tk}, Redir}, procio::{IoFrame, IoStack}};
|
||||||
|
|
||||||
pub mod echo;
|
pub mod echo;
|
||||||
pub mod cd;
|
pub mod cd;
|
||||||
pub mod export;
|
pub mod export;
|
||||||
@@ -17,3 +21,55 @@ pub const BUILTINS: [&str;9] = [
|
|||||||
"fg",
|
"fg",
|
||||||
"bg"
|
"bg"
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/// Sets up a builtin command
|
||||||
|
///
|
||||||
|
/// Prepares a builtin for execution by processing arguments, setting up redirections, and registering the command as a child process in the given `JobBldr`
|
||||||
|
///
|
||||||
|
/// # Parameters
|
||||||
|
/// * argv - The vector of raw argument tokens
|
||||||
|
/// * job - A mutable reference to a `JobBldr`
|
||||||
|
/// * io_info - An optional 2-tuple consisting of a mutable reference to an `IoStack` and a vector of `Redirs`
|
||||||
|
///
|
||||||
|
/// # Behavior
|
||||||
|
/// * Cleans, expands, and word splits the arg vector
|
||||||
|
/// * Adds a new `ChildProc` to the job builder
|
||||||
|
/// * Performs redirections, if any.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// * The processed arg vector
|
||||||
|
/// * The popped `IoFrame`, if any
|
||||||
|
///
|
||||||
|
/// # Notes
|
||||||
|
/// * If redirections are given to this function, the caller must call `IoFrame.restore()` on the returned `IoFrame`
|
||||||
|
/// * If redirections are given, the second field of the resulting tuple will *always* be `Some()`
|
||||||
|
/// * If no redirections are given, the second field will *always* be `None`
|
||||||
|
pub fn setup_builtin<'t>(
|
||||||
|
argv: Vec<Tk<'t>>,
|
||||||
|
job: &'t mut JobBldr,
|
||||||
|
io_info: Option<(&mut IoStack,Vec<Redir>)>,
|
||||||
|
) -> ShResult<(Vec<(String,Span<'t>)>, Option<IoFrame>)> {
|
||||||
|
let mut argv: Vec<(String,Span)> = prepare_argv(argv);
|
||||||
|
|
||||||
|
let child_pgid = if let Some(pgid) = job.pgid() {
|
||||||
|
pgid
|
||||||
|
} else {
|
||||||
|
job.set_pgid(Pid::this());
|
||||||
|
Pid::this()
|
||||||
|
};
|
||||||
|
let cmd_name = argv.remove(0).0;
|
||||||
|
let child = ChildProc::new(Pid::this(), Some(&cmd_name), Some(child_pgid))?;
|
||||||
|
job.push_child(child);
|
||||||
|
|
||||||
|
let io_frame = if let Some((io_stack,redirs)) = io_info {
|
||||||
|
io_stack.append_to_frame(redirs);
|
||||||
|
let mut io_frame = io_stack.pop_frame();
|
||||||
|
io_frame.redirect()?;
|
||||||
|
Some(io_frame)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
// We return the io_frame because the caller needs to also call io_frame.restore()
|
||||||
|
Ok((argv,io_frame))
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,30 +1,21 @@
|
|||||||
use crate::{jobs::{ChildProc, JobBldr}, libsh::error::ShResult, parse::{NdRule, Node}, prelude::*, procio::{borrow_fd, IoStack}};
|
use crate::{jobs::{ChildProc, JobBldr}, libsh::error::ShResult, parse::{NdRule, Node}, prelude::*, procio::{borrow_fd, IoStack}, state};
|
||||||
|
|
||||||
|
use super::setup_builtin;
|
||||||
|
|
||||||
pub fn pwd(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
|
pub fn pwd(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
|
||||||
let NdRule::Command { assignments, argv } = node.class else {
|
let NdRule::Command { assignments, argv } = node.class else {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
let child_pgid = if let Some(pgid) = job.pgid() {
|
let (_,io_frame) = setup_builtin(argv, job, Some((io_stack,node.redirs)))?;
|
||||||
pgid
|
|
||||||
} else {
|
|
||||||
job.set_pgid(Pid::this());
|
|
||||||
Pid::this()
|
|
||||||
};
|
|
||||||
let child = ChildProc::new(Pid::this(), Some("pwd"), Some(child_pgid))?;
|
|
||||||
job.push_child(child);
|
|
||||||
|
|
||||||
io_stack.append_to_frame(node.redirs);
|
|
||||||
let mut io_frame = io_stack.pop_frame();
|
|
||||||
|
|
||||||
io_frame.redirect()?;
|
|
||||||
|
|
||||||
let stdout = borrow_fd(STDOUT_FILENO);
|
let stdout = borrow_fd(STDOUT_FILENO);
|
||||||
|
|
||||||
let mut curr_dir = env::current_dir().unwrap().to_str().unwrap().to_string();
|
let mut curr_dir = env::current_dir().unwrap().to_str().unwrap().to_string();
|
||||||
curr_dir.push('\n');
|
curr_dir.push('\n');
|
||||||
write(stdout, curr_dir.as_bytes())?;
|
write(stdout, curr_dir.as_bytes())?;
|
||||||
io_frame.restore().unwrap();
|
|
||||||
|
|
||||||
|
io_frame.unwrap().restore().unwrap();
|
||||||
|
state::set_status(0);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,14 @@
|
|||||||
use crate::{jobs::{ChildProc, JobBldr}, libsh::error::{ErrSpan, ShErr, ShErrKind, ShResult}, parse::{execute::prepare_argv, NdRule, Node}, prelude::*, state::write_vars};
|
use crate::{jobs::{ChildProc, JobBldr}, libsh::error::{ErrSpan, ShErr, ShErrKind, ShResult}, parse::{execute::prepare_argv, NdRule, Node}, prelude::*, state::{self, write_vars}};
|
||||||
|
|
||||||
|
use super::setup_builtin;
|
||||||
|
|
||||||
pub fn shift(node: Node, job: &mut JobBldr) -> ShResult<()> {
|
pub fn shift(node: Node, job: &mut JobBldr) -> ShResult<()> {
|
||||||
let NdRule::Command { assignments: _, argv } = node.class else {
|
let NdRule::Command { assignments: _, argv } = node.class else {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
let child_pgid = if let Some(pgid) = job.pgid() {
|
let (argv,_) = setup_builtin(argv, job, None)?;
|
||||||
pgid
|
let mut argv = argv.into_iter();
|
||||||
} else {
|
|
||||||
job.set_pgid(Pid::this());
|
|
||||||
Pid::this()
|
|
||||||
};
|
|
||||||
let child = ChildProc::new(Pid::this(), Some("shift"), Some(child_pgid))?;
|
|
||||||
job.push_child(child);
|
|
||||||
|
|
||||||
let mut argv = prepare_argv(argv).into_iter().skip(1);
|
|
||||||
|
|
||||||
if let Some((arg,span)) = argv.next() {
|
if let Some((arg,span)) = argv.next() {
|
||||||
let Ok(count) = arg.parse::<usize>() else {
|
let Ok(count) = arg.parse::<usize>() else {
|
||||||
@@ -31,5 +25,6 @@ pub fn shift(node: Node, job: &mut JobBldr) -> ShResult<()> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
state::set_status(0);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,13 @@
|
|||||||
use crate::{jobs::{ChildProc, JobBldr}, libsh::error::{ShErr, ShErrKind, ShResult}, parse::{execute::prepare_argv, NdRule, Node}, prelude::*, state::source_file};
|
use crate::{jobs::{ChildProc, JobBldr}, libsh::error::{ShErr, ShErrKind, ShResult}, parse::{execute::prepare_argv, NdRule, Node}, prelude::*, state::{self, source_file}};
|
||||||
|
|
||||||
|
use super::setup_builtin;
|
||||||
|
|
||||||
pub fn source(node: Node, job: &mut JobBldr) -> ShResult<()> {
|
pub fn source(node: Node, job: &mut JobBldr) -> ShResult<()> {
|
||||||
let NdRule::Command { assignments: _, argv } = node.class else {
|
let NdRule::Command { assignments: _, argv } = node.class else {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
let child_pgid = if let Some(pgid) = job.pgid() {
|
let (argv,_) = setup_builtin(argv, job, None)?;
|
||||||
pgid
|
|
||||||
} else {
|
|
||||||
job.set_pgid(Pid::this());
|
|
||||||
Pid::this()
|
|
||||||
};
|
|
||||||
let child = ChildProc::new(Pid::this(), Some("source"), Some(child_pgid))?;
|
|
||||||
job.push_child(child);
|
|
||||||
|
|
||||||
let argv = prepare_argv(argv).into_iter().skip(1);
|
|
||||||
|
|
||||||
for (arg,span) in argv {
|
for (arg,span) in argv {
|
||||||
let path = PathBuf::from(arg);
|
let path = PathBuf::from(arg);
|
||||||
@@ -39,5 +32,6 @@ pub fn source(node: Node, job: &mut JobBldr) -> ShResult<()> {
|
|||||||
source_file(path)?;
|
source_file(path)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
state::set_status(0);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ pub mod tests;
|
|||||||
|
|
||||||
use libsh::error::ShResult;
|
use libsh::error::ShResult;
|
||||||
use parse::{execute::Dispatcher, lex::{LexFlags, LexStream}, ParseStream};
|
use parse::{execute::Dispatcher, lex::{LexFlags, LexStream}, ParseStream};
|
||||||
|
use signal::sig_setup;
|
||||||
use termios::{LocalFlags, Termios};
|
use termios::{LocalFlags, Termios};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
@@ -64,6 +65,7 @@ pub fn exec_input(input: &str) -> ShResult<()> {
|
|||||||
fn main() {
|
fn main() {
|
||||||
save_termios();
|
save_termios();
|
||||||
set_termios();
|
set_termios();
|
||||||
|
sig_setup();
|
||||||
loop {
|
loop {
|
||||||
let input = prompt::read_line().unwrap();
|
let input = prompt::read_line().unwrap();
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
|
|||||||
@@ -1,11 +1,43 @@
|
|||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
|
|
||||||
|
use crate::parse::lex::{Span, Tk};
|
||||||
|
use crate::prelude::*;
|
||||||
|
|
||||||
pub trait VecDequeExt<T> {
|
pub trait VecDequeExt<T> {
|
||||||
fn to_vec(self) -> Vec<T>;
|
fn to_vec(self) -> Vec<T>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait TkVecUtils<Tk> {
|
||||||
|
fn get_span(&self) -> Option<Span>;
|
||||||
|
fn debug_tokens(&self);
|
||||||
|
}
|
||||||
|
|
||||||
impl<T> VecDequeExt<T> for VecDeque<T> {
|
impl<T> VecDequeExt<T> for VecDeque<T> {
|
||||||
fn to_vec(self) -> Vec<T> {
|
fn to_vec(self) -> Vec<T> {
|
||||||
self.into_iter().collect::<Vec<T>>()
|
self.into_iter().collect::<Vec<T>>()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'t> TkVecUtils<Tk<'t>> for Vec<Tk<'t>> {
|
||||||
|
fn get_span(&self) -> Option<Span<'t>> {
|
||||||
|
if let Some(first_tk) = self.first() {
|
||||||
|
if let Some(last_tk) = self.last() {
|
||||||
|
Some(
|
||||||
|
Span::new(
|
||||||
|
first_tk.span.start..last_tk.span.end,
|
||||||
|
first_tk.source()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn debug_tokens(&self) {
|
||||||
|
for token in self {
|
||||||
|
flog!(DEBUG, "token: {}",token)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ impl<'t> Dispatcher<'t> {
|
|||||||
let result = match cmd_raw.span.as_str() {
|
let result = match cmd_raw.span.as_str() {
|
||||||
"echo" => echo(cmd, io_stack_mut, curr_job_mut),
|
"echo" => echo(cmd, io_stack_mut, curr_job_mut),
|
||||||
"cd" => cd(cmd, curr_job_mut),
|
"cd" => cd(cmd, curr_job_mut),
|
||||||
"export" => export(cmd, curr_job_mut),
|
"export" => export(cmd, io_stack_mut, curr_job_mut),
|
||||||
"pwd" => pwd(cmd, io_stack_mut, curr_job_mut),
|
"pwd" => pwd(cmd, io_stack_mut, curr_job_mut),
|
||||||
"source" => source(cmd, curr_job_mut),
|
"source" => source(cmd, curr_job_mut),
|
||||||
"shift" => shift(cmd, curr_job_mut),
|
"shift" => shift(cmd, curr_job_mut),
|
||||||
@@ -147,7 +147,11 @@ impl<'t> Dispatcher<'t> {
|
|||||||
env::set_var(&var, "");
|
env::set_var(&var, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(result?)
|
if let Err(e) = result {
|
||||||
|
state::set_status(1);
|
||||||
|
return Err(e.into())
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
pub fn exec_cmd(&mut self, cmd: Node<'t>) -> ShResult<()> {
|
pub fn exec_cmd(&mut self, cmd: Node<'t>) -> ShResult<()> {
|
||||||
let NdRule::Command { assignments, argv } = cmd.class else {
|
let NdRule::Command { assignments, argv } = cmd.class else {
|
||||||
|
|||||||
218
src/parse/mod.rs
218
src/parse/mod.rs
@@ -1,9 +1,10 @@
|
|||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use bitflags::bitflags;
|
use bitflags::bitflags;
|
||||||
|
use fmt::Display;
|
||||||
use lex::{Span, Tk, TkFlags, TkRule};
|
use lex::{Span, Tk, TkFlags, TkRule};
|
||||||
|
|
||||||
use crate::{prelude::*, libsh::error::{ShErr, ShErrKind, ShResult}, procio::{IoFd, IoFile, IoInfo}};
|
use crate::{libsh::{error::{ShErr, ShErrKind, ShResult}, utils::TkVecUtils}, prelude::*, procio::{IoFd, IoFile, IoInfo}};
|
||||||
|
|
||||||
pub mod lex;
|
pub mod lex;
|
||||||
pub mod execute;
|
pub mod execute;
|
||||||
@@ -175,7 +176,7 @@ pub enum RedirType {
|
|||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct CondNode<'t> {
|
pub struct CondNode<'t> {
|
||||||
pub cond: Vec<Node<'t>>,
|
pub cond: Box<Node<'t>>,
|
||||||
pub body: Vec<Node<'t>>
|
pub body: Vec<Node<'t>>
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -204,6 +205,26 @@ pub enum LoopKind {
|
|||||||
Until
|
Until
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl FromStr for LoopKind {
|
||||||
|
type Err = ShErr;
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
match s {
|
||||||
|
"while" => Ok(Self::While),
|
||||||
|
"until" => Ok(Self::Until),
|
||||||
|
_ => Err(ShErr::simple(ShErrKind::ParseErr, format!("Invalid loop kind: {s}")))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for LoopKind {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
LoopKind::While => write!(f,"while"),
|
||||||
|
LoopKind::Until => write!(f,"until")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum AssignKind {
|
pub enum AssignKind {
|
||||||
Eq,
|
Eq,
|
||||||
@@ -215,8 +236,8 @@ pub enum AssignKind {
|
|||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum NdRule<'t> {
|
pub enum NdRule<'t> {
|
||||||
IfNode { cond_blocks: Vec<CondNode<'t>>, else_block: Vec<Node<'t>> },
|
IfNode { cond_nodes: Vec<CondNode<'t>>, else_block: Vec<Node<'t>> },
|
||||||
LoopNode { kind: LoopKind, cond_block: CondNode<'t> },
|
LoopNode { kind: LoopKind, cond_node: CondNode<'t> },
|
||||||
ForNode { vars: Vec<Tk<'t>>, arr: Vec<Tk<'t>>, body: Vec<Node<'t>> },
|
ForNode { vars: Vec<Tk<'t>>, arr: Vec<Tk<'t>>, body: Vec<Node<'t>> },
|
||||||
CaseNode { pattern: Tk<'t>, case_blocks: Vec<CaseNode<'t>> },
|
CaseNode { pattern: Tk<'t>, case_blocks: Vec<CaseNode<'t>> },
|
||||||
Command { assignments: Vec<Node<'t>>, argv: Vec<Tk<'t>> },
|
Command { assignments: Vec<Node<'t>>, argv: Vec<Tk<'t>> },
|
||||||
@@ -259,6 +280,14 @@ impl<'t> ParseStream<'t> {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
fn next_tk_is_some(&self) -> bool {
|
||||||
|
self.tokens.first().is_some_and(|tk| tk.class != TkRule::EOI)
|
||||||
|
}
|
||||||
|
fn check_keyword(&self, kw: &str) -> bool {
|
||||||
|
self.tokens.first().is_some_and(|tk| {
|
||||||
|
tk.flags.contains(TkFlags::KEYWORD) && tk.span.as_str() == kw
|
||||||
|
})
|
||||||
|
}
|
||||||
/// Slice off consumed tokens
|
/// Slice off consumed tokens
|
||||||
fn commit(&mut self, num_consumed: usize) {
|
fn commit(&mut self, num_consumed: usize) {
|
||||||
assert!(num_consumed <= self.tokens.len());
|
assert!(num_consumed <= self.tokens.len());
|
||||||
@@ -303,6 +332,12 @@ impl<'t> ParseStream<'t> {
|
|||||||
/// Ordered from specialized to general, with more generally matchable stuff appearing at the bottom
|
/// Ordered from specialized to general, with more generally matchable stuff appearing at the bottom
|
||||||
/// The check_pipelines parameter is used to prevent infinite recursion in parse_pipeline
|
/// The check_pipelines parameter is used to prevent infinite recursion in parse_pipeline
|
||||||
fn parse_block(&mut self, check_pipelines: bool) -> ShResult<Option<Node<'t>>> {
|
fn parse_block(&mut self, check_pipelines: bool) -> ShResult<Option<Node<'t>>> {
|
||||||
|
if let Some(node) = self.parse_loop()? {
|
||||||
|
return Ok(Some(node))
|
||||||
|
}
|
||||||
|
if let Some(node) = self.parse_if()? {
|
||||||
|
return Ok(Some(node))
|
||||||
|
}
|
||||||
if check_pipelines {
|
if check_pipelines {
|
||||||
if let Some(node) = self.parse_pipeline()? {
|
if let Some(node) = self.parse_pipeline()? {
|
||||||
return Ok(Some(node))
|
return Ok(Some(node))
|
||||||
@@ -314,6 +349,158 @@ impl<'t> ParseStream<'t> {
|
|||||||
}
|
}
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
fn parse_if(&mut self) -> ShResult<Option<Node<'t>>> {
|
||||||
|
// Needs at last one 'if-then',
|
||||||
|
// Any number of 'elif-then',
|
||||||
|
// Zero or one 'else'
|
||||||
|
let mut node_tks: Vec<Tk> = vec![];
|
||||||
|
let mut cond_nodes: Vec<CondNode> = vec![];
|
||||||
|
let mut else_block: Vec<Node> = vec![];
|
||||||
|
|
||||||
|
if !self.check_keyword("if") || !self.next_tk_is_some() {
|
||||||
|
return Ok(None)
|
||||||
|
}
|
||||||
|
node_tks.push(self.next_tk().unwrap());
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let prefix_keywrd = if cond_nodes.is_empty() {
|
||||||
|
"if"
|
||||||
|
} else {
|
||||||
|
"elif"
|
||||||
|
};
|
||||||
|
let Some(cond) = self.parse_block(true)? else {
|
||||||
|
return Err(parse_err_full(
|
||||||
|
&format!("Expected an expression after '{prefix_keywrd}'"),
|
||||||
|
&node_tks.get_span().unwrap()
|
||||||
|
));
|
||||||
|
};
|
||||||
|
cond.tokens.debug_tokens();
|
||||||
|
node_tks.extend(cond.tokens.clone());
|
||||||
|
|
||||||
|
if !self.check_keyword("then") || !self.next_tk_is_some() {
|
||||||
|
return Err(parse_err_full(
|
||||||
|
&format!("Expected 'then' after '{prefix_keywrd}' condition"),
|
||||||
|
&node_tks.get_span().unwrap()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
node_tks.push(self.next_tk().unwrap());
|
||||||
|
|
||||||
|
let mut body_blocks = vec![];
|
||||||
|
while let Some(body_block) = self.parse_block(true)? {
|
||||||
|
body_block.tokens.debug_tokens();
|
||||||
|
node_tks.extend(body_block.tokens.clone());
|
||||||
|
body_blocks.push(body_block);
|
||||||
|
}
|
||||||
|
if body_blocks.is_empty() {
|
||||||
|
return Err(parse_err_full(
|
||||||
|
"Expected an expression after 'then'",
|
||||||
|
&node_tks.get_span().unwrap()
|
||||||
|
));
|
||||||
|
};
|
||||||
|
let cond_node = CondNode { cond: Box::new(cond), body: body_blocks };
|
||||||
|
cond_nodes.push(cond_node);
|
||||||
|
flog!(DEBUG, cond_nodes.len());
|
||||||
|
flog!(DEBUG, !self.check_keyword("elif") || !self.next_tk_is_some());
|
||||||
|
|
||||||
|
if !self.check_keyword("elif") || !self.next_tk_is_some() {
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
node_tks.push(self.next_tk().unwrap());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.check_keyword("else") {
|
||||||
|
node_tks.push(self.next_tk().unwrap());
|
||||||
|
while let Some(block) = self.parse_block(true)? {
|
||||||
|
else_block.push(block)
|
||||||
|
}
|
||||||
|
if else_block.is_empty() {
|
||||||
|
return Err(parse_err_full(
|
||||||
|
"Expected an expression after 'else'",
|
||||||
|
&node_tks.get_span().unwrap()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !self.check_keyword("fi") || !self.next_tk_is_some() {
|
||||||
|
return Err(parse_err_full(
|
||||||
|
"Expected 'fi' after if statement",
|
||||||
|
&node_tks.get_span().unwrap()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
node_tks.push(self.next_tk().unwrap());
|
||||||
|
|
||||||
|
let node = Node {
|
||||||
|
class: NdRule::IfNode { cond_nodes, else_block },
|
||||||
|
flags: NdFlags::empty(),
|
||||||
|
redirs: vec![],
|
||||||
|
tokens: node_tks
|
||||||
|
};
|
||||||
|
flog!(DEBUG, node);
|
||||||
|
Ok(Some(node))
|
||||||
|
}
|
||||||
|
fn parse_loop(&mut self) -> ShResult<Option<Node<'t>>> {
|
||||||
|
// Requires a single CondNode and a LoopKind
|
||||||
|
let loop_kind: LoopKind;
|
||||||
|
let cond_node: CondNode<'t>;
|
||||||
|
let mut node_tks = vec![];
|
||||||
|
|
||||||
|
if (!self.check_keyword("while") && !self.check_keyword("until")) || !self.next_tk_is_some() {
|
||||||
|
return Ok(None)
|
||||||
|
}
|
||||||
|
let loop_tk = self.next_tk().unwrap();
|
||||||
|
loop_kind = loop_tk.span
|
||||||
|
.as_str()
|
||||||
|
.parse() // LoopKind implements FromStr
|
||||||
|
.unwrap();
|
||||||
|
node_tks.push(loop_tk);
|
||||||
|
|
||||||
|
let Some(cond) = self.parse_block(true)? else {
|
||||||
|
return Err(parse_err_full(
|
||||||
|
&format!("Expected an expression after '{loop_kind}'"), // It also implements Display
|
||||||
|
&node_tks.get_span().unwrap()
|
||||||
|
))
|
||||||
|
};
|
||||||
|
node_tks.extend(cond.tokens.clone());
|
||||||
|
|
||||||
|
if !self.check_keyword("do") || !self.next_tk_is_some() {
|
||||||
|
return Err(parse_err_full(
|
||||||
|
"Expected 'do' after loop condition",
|
||||||
|
&node_tks.get_span().unwrap()
|
||||||
|
))
|
||||||
|
}
|
||||||
|
node_tks.push(self.next_tk().unwrap());
|
||||||
|
|
||||||
|
let mut body = vec![];
|
||||||
|
while let Some(block) = self.parse_block(true)? {
|
||||||
|
node_tks.extend(block.tokens.clone());
|
||||||
|
body.push(block);
|
||||||
|
}
|
||||||
|
if body.is_empty() {
|
||||||
|
return Err(parse_err_full(
|
||||||
|
"Expected an expression after 'do'",
|
||||||
|
&node_tks.get_span().unwrap()
|
||||||
|
))
|
||||||
|
};
|
||||||
|
|
||||||
|
if !self.check_keyword("done") || !self.next_tk_is_some() {
|
||||||
|
return Err(parse_err_full(
|
||||||
|
"Expected 'done' after loop body",
|
||||||
|
&node_tks.get_span().unwrap()
|
||||||
|
))
|
||||||
|
}
|
||||||
|
node_tks.push(self.next_tk().unwrap());
|
||||||
|
|
||||||
|
cond_node = CondNode { cond: Box::new(cond), body };
|
||||||
|
let loop_node = Node {
|
||||||
|
class: NdRule::LoopNode { kind: loop_kind, cond_node },
|
||||||
|
flags: NdFlags::empty(),
|
||||||
|
redirs: vec![],
|
||||||
|
tokens: node_tks
|
||||||
|
};
|
||||||
|
flog!(DEBUG, loop_node);
|
||||||
|
Ok(Some(loop_node))
|
||||||
|
}
|
||||||
fn parse_pipeline(&mut self) -> ShResult<Option<Node<'t>>> {
|
fn parse_pipeline(&mut self) -> ShResult<Option<Node<'t>>> {
|
||||||
let mut cmds = vec![];
|
let mut cmds = vec![];
|
||||||
let mut node_tks = vec![];
|
let mut node_tks = vec![];
|
||||||
@@ -356,12 +543,16 @@ impl<'t> ParseStream<'t> {
|
|||||||
node_tks.push(prefix_tk.clone());
|
node_tks.push(prefix_tk.clone());
|
||||||
argv.push(prefix_tk.clone());
|
argv.push(prefix_tk.clone());
|
||||||
break
|
break
|
||||||
|
|
||||||
} else if prefix_tk.flags.contains(TkFlags::ASSIGN) {
|
} else if prefix_tk.flags.contains(TkFlags::ASSIGN) {
|
||||||
let Some(assign) = self.parse_assignment(&prefix_tk) else {
|
let Some(assign) = self.parse_assignment(&prefix_tk) else {
|
||||||
break
|
break
|
||||||
};
|
};
|
||||||
node_tks.push(prefix_tk.clone());
|
node_tks.push(prefix_tk.clone());
|
||||||
assignments.push(assign)
|
assignments.push(assign)
|
||||||
|
|
||||||
|
} else if prefix_tk.flags.contains(TkFlags::KEYWORD) {
|
||||||
|
return Ok(None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -428,13 +619,10 @@ impl<'t> ParseStream<'t> {
|
|||||||
_ => unreachable!()
|
_ => unreachable!()
|
||||||
}) else {
|
}) else {
|
||||||
self.flags |= ParseFlags::ERROR;
|
self.flags |= ParseFlags::ERROR;
|
||||||
return Err(
|
return Err(parse_err_full(
|
||||||
ShErr::full(
|
"Error opening file for redirection",
|
||||||
ShErrKind::InternalErr,
|
&path_tk.span
|
||||||
"Error opening file for redirection",
|
));
|
||||||
path_tk.span.clone().into()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let io_info = IoFile::new(redir_bldr.tgt_fd.unwrap(), file);
|
let io_info = IoFile::new(redir_bldr.tgt_fd.unwrap(), file);
|
||||||
@@ -591,3 +779,11 @@ fn node_is_punctuated<'t>(tokens: &Vec<Tk>) -> bool {
|
|||||||
matches!(tk.class, TkRule::Sep)
|
matches!(tk.class, TkRule::Sep)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn parse_err_full<'t>(reason: &str, blame: &Span<'t>) -> ShErr {
|
||||||
|
ShErr::full(
|
||||||
|
ShErrKind::ParseErr,
|
||||||
|
reason,
|
||||||
|
blame.clone().into()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|||||||
@@ -28,7 +28,10 @@ pub fn read_line<'s>() -> ShResult<String> {
|
|||||||
}
|
}
|
||||||
Ok(line)
|
Ok(line)
|
||||||
}
|
}
|
||||||
Err(ReadlineError::Eof) => std::process::exit(0),
|
Err(ReadlineError::Eof) => {
|
||||||
|
kill(Pid::this(), Signal::SIGQUIT)?;
|
||||||
|
Ok(String::new())
|
||||||
|
}
|
||||||
Err(ReadlineError::Interrupted) => Ok(String::new()),
|
Err(ReadlineError::Interrupted) => Ok(String::new()),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
return Err(e.into())
|
return Err(e.into())
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ impl Highlighter for FernReadline {
|
|||||||
|
|
||||||
impl Validator for FernReadline {
|
impl Validator for FernReadline {
|
||||||
fn validate(&self, ctx: &mut rustyline::validate::ValidationContext) -> rustyline::Result<rustyline::validate::ValidationResult> {
|
fn validate(&self, ctx: &mut rustyline::validate::ValidationContext) -> rustyline::Result<rustyline::validate::ValidationResult> {
|
||||||
|
return Ok(ValidationResult::Valid(None));
|
||||||
let mut tokens = vec![];
|
let mut tokens = vec![];
|
||||||
let tk_stream = LexStream::new(ctx.input(), LexFlags::empty());
|
let tk_stream = LexStream::new(ctx.input(), LexFlags::empty());
|
||||||
for tk in tk_stream {
|
for tk in tk_stream {
|
||||||
|
|||||||
Reference in New Issue
Block a user