Initial commit for fern

This commit is contained in:
2025-03-02 16:26:28 -05:00
parent 706767ce4c
commit e7a84f1edd
40 changed files with 5281 additions and 0 deletions

15
src/builtin/cd.rs Normal file
View File

@@ -0,0 +1,15 @@
use crate::{parse::parse::{Node, NdRule}, prelude::*};
pub fn cd(node: Node, shenv: &mut ShEnv) -> ShResult<()> {
let rule = node.into_rule();
if let NdRule::Command { argv, redirs: _ } = rule {
let mut argv_iter = argv.into_iter();
argv_iter.next(); // Ignore 'cd'
let dir_raw = argv_iter.next().map(|arg| arg.to_string()).unwrap_or(std::env::var("HOME")?);
let dir = PathBuf::from(&dir_raw);
std::env::set_current_dir(dir)?;
shenv.vars_mut().export("PWD",&dir_raw);
shenv.set_code(0);
}
Ok(())
}

53
src/builtin/echo.rs Normal file
View File

@@ -0,0 +1,53 @@
use shellenv::jobs::{ChildProc, JobBldr};
use crate::{libsh::utils::ArgVec, parse::parse::{Node, NdRule}, prelude::*};
pub fn echo(node: Node, shenv: &mut ShEnv) -> ShResult<()> {
let rule = node.into_rule();
if let NdRule::Command { argv, redirs } = rule {
let argv = argv.drop_first().as_strings(shenv);
let mut formatted = argv.join(" ");
formatted.push('\n');
if shenv.ctx().flags().contains(ExecFlags::NO_FORK) {
shenv.collect_redirs(redirs);
if let Err(e) = shenv.ctx_mut().activate_rdrs() {
eprintln!("{:?}",e);
exit(1);
}
if let Err(e) = write_out(formatted) {
eprintln!("{:?}",e);
exit(1);
}
exit(0);
} else {
match unsafe { fork()? } {
Child => {
shenv.collect_redirs(redirs);
if let Err(e) = shenv.ctx_mut().activate_rdrs() {
eprintln!("{:?}",e);
exit(1);
}
if let Err(e) = write_out(formatted) {
eprintln!("{:?}",e);
exit(1);
}
exit(0);
}
Parent { child } => {
shenv.reset_io()?;
let children = vec![
ChildProc::new(child, Some("echo"), Some(child))?
];
let job = JobBldr::new()
.with_children(children)
.with_pgid(child)
.build();
wait_fg(job, shenv)?;
}
}
}
} else { unreachable!() }
Ok(())
}

18
src/builtin/export.rs Normal file
View File

@@ -0,0 +1,18 @@
use crate::prelude::*;
pub fn export(node: Node, shenv: &mut ShEnv) -> ShResult<()> {
let rule = node.into_rule();
if let NdRule::Command { argv, redirs: _ } = rule {
let mut argv_iter = argv.into_iter();
argv_iter.next(); // Ignore 'export'
while let Some(arg) = argv_iter.next() {
let arg_raw = arg.to_string();
if let Some((var,val)) = arg_raw.split_once('=') {
shenv.vars_mut().export(var, val);
} else {
eprintln!("Expected an assignment in export args, found this: {}", arg_raw)
}
}
} else { unreachable!() }
Ok(())
}

127
src/builtin/jobctl.rs Normal file
View File

@@ -0,0 +1,127 @@
use shellenv::jobs::JobCmdFlags;
use crate::prelude::*;
pub fn continue_job(node: Node, shenv: &mut ShEnv, fg: bool) -> ShResult<()> {
let blame = node.span();
let cmd = if fg { "fg" } else { "bg" };
let rule = node.into_rule();
if let NdRule::Command { argv, redirs } = rule {
let mut argv_s = argv.drop_first().as_strings(shenv).into_iter();
if read_jobs(|j| j.get_fg().is_some()) {
return Err(
ShErr::full(
ShErrKind::InternalErr,
format!("Somehow called {} with an existing foreground job",cmd),
blame
)
)
}
let curr_job_id = if let Some(id) = read_jobs(|j| j.curr_job()) {
id
} else {
return Err(ShErr::full(ShErrKind::ExecFail, "No jobs found".into(), blame))
};
let tabid = match argv_s.next() {
Some(arg) => parse_job_id(&arg, blame.clone())?,
None => curr_job_id
};
let mut job = write_jobs(|j| {
let id = JobID::TableID(tabid);
let query_result = j.query(id.clone());
if query_result.is_some() {
Ok(j.remove_job(id.clone()).unwrap())
} else {
Err(ShErr::full(ShErrKind::ExecFail, format!("Job id `{}' not found", tabid), blame))
}
})?;
job.killpg(Signal::SIGCONT)?;
if fg {
write_jobs(|j| j.new_fg(job))?;
} else {
let job_order = read_jobs(|j| j.order().to_vec());
write(borrow_fd(1), job.display(&job_order, JobCmdFlags::PIDS).as_bytes())?;
write_jobs(|j| j.insert_job(job, true))?;
}
shenv.set_code(0);
} else { unreachable!() }
Ok(())
}
fn parse_job_id(arg: &str, blame: Span) -> ShResult<usize> {
if arg.starts_with('%') {
let arg = arg.strip_prefix('%').unwrap();
if arg.chars().all(|ch| ch.is_ascii_digit()) {
Ok(arg.parse::<usize>().unwrap())
} else {
let result = write_jobs(|j| {
let query_result = j.query(JobID::Command(arg.into()));
query_result.map(|job| job.tabid().unwrap())
});
match result {
Some(id) => Ok(id),
None => Err(ShErr::full(ShErrKind::InternalErr,"Found a job but no table id in parse_job_id()".into(),blame))
}
}
} else if arg.chars().all(|ch| ch.is_ascii_digit()) {
let result = write_jobs(|j| {
let pgid_query_result = j.query(JobID::Pgid(Pid::from_raw(arg.parse::<i32>().unwrap())));
if let Some(job) = pgid_query_result {
return Some(job.tabid().unwrap())
}
if arg.parse::<i32>().unwrap() > 0 {
let table_id_query_result = j.query(JobID::TableID(arg.parse::<usize>().unwrap()));
return table_id_query_result.map(|job| job.tabid().unwrap());
}
None
});
match result {
Some(id) => Ok(id),
None => Err(ShErr::full(ShErrKind::InternalErr,"Found a job but no table id in parse_job_id()".into(),blame))
}
} else {
Err(ShErr::full(ShErrKind::SyntaxErr,format!("Invalid fd arg: {}", arg),blame))
}
}
pub fn jobs(node: Node, shenv: &mut ShEnv) -> ShResult<()> {
let rule = node.into_rule();
if let NdRule::Command { argv, redirs } = rule {
let mut argv = argv.drop_first().into_iter();
let mut flags = JobCmdFlags::empty();
while let Some(arg) = argv.next() {
let arg_s = arg.to_string();
let mut chars = arg_s.chars().peekable();
if chars.peek().is_none_or(|ch| *ch != '-') {
return Err(ShErr::full(ShErrKind::SyntaxErr, "Invalid flag in jobs call".into(), arg.span().clone()))
}
chars.next();
while let Some(ch) = chars.next() {
let flag = match ch {
'l' => JobCmdFlags::LONG,
'p' => JobCmdFlags::PIDS,
'n' => JobCmdFlags::NEW_ONLY,
'r' => JobCmdFlags::RUNNING,
's' => JobCmdFlags::STOPPED,
_ => return Err(ShErr::full(ShErrKind::SyntaxErr, "Invalid flag in jobs call".into(), arg.span().clone()))
};
flags |= flag
}
}
read_jobs(|j| j.print_jobs(flags))?;
shenv.set_code(0);
} else { unreachable!() }
Ok(())
}

17
src/builtin/mod.rs Normal file
View File

@@ -0,0 +1,17 @@
pub mod echo;
pub mod cd;
pub mod pwd;
pub mod export;
pub mod jobctl;
pub mod read;
pub const BUILTINS: [&str;8] = [
"echo",
"cd",
"pwd",
"export",
"fg",
"bg",
"jobs",
"read"
];

50
src/builtin/pwd.rs Normal file
View File

@@ -0,0 +1,50 @@
use shellenv::jobs::{ChildProc, JobBldr};
use crate::{parse::parse::{NdFlag, Node, NdRule}, prelude::*};
pub fn pwd(node: Node, shenv: &mut ShEnv) -> ShResult<()> {
let rule = node.into_rule();
if let NdRule::Command { argv: _, redirs } = rule {
let mut pwd = shenv.vars().get_var("PWD").to_string();
pwd.push('\n');
if shenv.ctx().flags().contains(ExecFlags::NO_FORK) {
shenv.collect_redirs(redirs);
if let Err(e) = shenv.ctx_mut().activate_rdrs() {
eprintln!("{:?}",e);
exit(1);
}
if let Err(e) = write_out(pwd) {
eprintln!("{:?}",e);
exit(1);
}
exit(0);
} else {
match unsafe { fork()? } {
Child => {
if let Err(e) = shenv.ctx_mut().activate_rdrs() {
eprintln!("{:?}",e);
exit(1);
}
if let Err(e) = write_out(pwd) {
eprintln!("{:?}",e);
exit(1);
}
exit(0);
}
Parent { child } => {
shenv.reset_io()?;
let children = vec![
ChildProc::new(child, Some("echo"), Some(child))?
];
let job = JobBldr::new()
.with_children(children)
.with_pgid(child)
.build();
wait_fg(job, shenv)?;
}
}
}
} else { unreachable!() }
Ok(())
}

39
src/builtin/read.rs Normal file
View File

@@ -0,0 +1,39 @@
use crate::prelude::*;
pub fn read_builtin(node: Node, shenv: &mut ShEnv) -> ShResult<()> {
let rule = node.into_rule();
if let NdRule::Command { argv, redirs: _ } = rule {
let argv = argv.drop_first();
let mut argv_iter = argv.iter();
// TODO: properly implement redirections
// using activate_redirs() was causing issues, may require manual handling
let mut buf = vec![0u8; 1024];
let bytes_read = read(0, &mut buf)?;
buf.truncate(bytes_read);
let read_input = String::from_utf8_lossy(&buf).trim_end().to_string();
if let Some(var) = argv_iter.next() {
/*
let words: Vec<&str> = read_input.split_whitespace().collect();
for (var, value) in argv_iter.zip(words.iter().chain(std::iter::repeat(&""))) {
shenv.vars_mut().set_var(&var.to_string(), value);
}
// Assign the rest of the string to the first variable if there's only one
if argv.len() == 1 {
shenv.vars_mut().set_var(&first_var.to_string(), &read_input);
}
*/
shenv.vars_mut().set_var(&var.to_string(), &read_input);
}
} else {
unreachable!()
}
log!(TRACE, "leaving read");
shenv.set_code(0);
Ok(())
}