implemented functions

This commit is contained in:
2025-03-16 16:57:58 -04:00
parent da51be27a7
commit d673bb692f
6 changed files with 346 additions and 33 deletions

View File

@@ -13,6 +13,7 @@ pub mod tests;
use libsh::error::ShResult;
use parse::{execute::Dispatcher, lex::{LexFlags, LexStream}, ParseStream};
use procio::IoFrame;
use signal::sig_setup;
use termios::{LocalFlags, Termios};
use crate::prelude::*;
@@ -46,7 +47,7 @@ fn set_termios() {
}
}
pub fn exec_input(input: &str) -> ShResult<()> {
pub fn exec_input(input: &str, io_frame: Option<IoFrame>) -> ShResult<()> {
let parse_start = Instant::now();
let mut tokens = vec![];
for token in LexStream::new(&input, LexFlags::empty()) {
@@ -61,6 +62,9 @@ pub fn exec_input(input: &str) -> ShResult<()> {
let exec_start = Instant::now();
let mut dispatcher = Dispatcher::new(nodes);
if let Some(frame) = io_frame {
dispatcher.io_stack.push(frame)
}
dispatcher.begin_dispatch()?;
flog!(INFO, "cmd duration: {:?}", exec_start.elapsed());
@@ -77,7 +81,7 @@ fn main() {
loop {
let input = prompt::read_line().unwrap();
if let Err(e) = exec_input(&input) {
if let Err(e) = exec_input(&input,None) {
eprintln!("{e}");
}
}

View File

@@ -613,6 +613,7 @@ pub fn enable_reaping() -> ShResult<()> {
pub fn wait_fg(job: Job) -> ShResult<()> {
flog!(TRACE, "Waiting on foreground job");
let mut code = 0;
flog!(DEBUG,job.pgid());
attach_tty(job.pgid())?;
disable_reaping()?;
let statuses = write_jobs(|j| j.new_fg(job))?;

View File

@@ -1,9 +1,9 @@
use std::collections::VecDeque;
use crate::{builtin::{cd::cd, echo::echo, export::export, jobctl::{continue_job, jobs, JobBehavior}, pwd::pwd, shift::shift, source::source}, jobs::{dispatch_job, ChildProc, Job, JobBldr, JobStack}, libsh::{error::{ShErrKind, ShResult}, utils::RedirVecUtils}, prelude::*, procio::{IoFrame, IoMode, IoStack}, state::{self, write_vars}};
use crate::{builtin::{cd::cd, echo::echo, export::export, jobctl::{continue_job, jobs, JobBehavior}, pwd::pwd, shift::shift, source::source}, exec_input, jobs::{dispatch_job, ChildProc, Job, JobBldr, JobStack}, libsh::{error::{ErrSpan, ShErr, ShErrKind, ShResult}, utils::RedirVecUtils}, prelude::*, procio::{IoFrame, IoMode, IoStack}, state::{self, read_logic, read_vars, write_logic, write_vars}};
use super::{lex::{Span, Tk, TkFlags}, AssignKind, CondNode, ConjunctNode, ConjunctOp, LoopKind, NdFlags, NdRule, Node, Redir, RedirType};
use super::{lex::{LexFlags, LexStream, Span, Tk, TkFlags}, AssignKind, CondNode, ConjunctNode, ConjunctOp, LoopKind, NdFlags, NdRule, Node, ParseStream, Redir, RedirType};
pub enum AssignBehavior {
Export,
@@ -61,6 +61,8 @@ impl<'t> Dispatcher<'t> {
NdRule::Pipeline {..} => self.exec_pipeline(node)?,
NdRule::IfNode {..} => self.exec_if(node)?,
NdRule::LoopNode {..} => self.exec_loop(node)?,
NdRule::BraceGrp {..} => self.exec_brc_grp(node)?,
NdRule::FuncDef {..} => self.exec_func_def(node)?,
NdRule::Command {..} => self.dispatch_cmd(node)?,
_ => unreachable!()
}
@@ -68,10 +70,12 @@ impl<'t> Dispatcher<'t> {
}
pub fn dispatch_cmd(&mut self, node: Node<'t>) -> ShResult<()> {
let Some(cmd) = node.get_command() else {
return self.exec_cmd(node)
return self.exec_cmd(node) // Argv is empty, probably an assignment
};
if cmd.flags.contains(TkFlags::BUILTIN) {
self.exec_builtin(node)
} else if is_func(node.get_command().cloned()) {
self.exec_func(node)
} else {
self.exec_cmd(node)
}
@@ -95,6 +99,66 @@ impl<'t> Dispatcher<'t> {
}
Ok(())
}
pub fn exec_func_def(&mut self, func_def: Node<'t>) -> ShResult<()> {
let NdRule::FuncDef { name, body } = func_def.class else {
unreachable!()
};
let body_span = body.get_span();
let body = body_span.as_str();
let name = name.span.as_str().strip_suffix("()").unwrap();
write_logic(|l| l.insert_func(name, body));
Ok(())
}
pub fn exec_func(&mut self, func: Node<'t>) -> ShResult<()> {
let blame: ErrSpan = func.get_span().into();
// TODO: Find a way to store functions as pre-parsed nodes so we don't have to re-parse them
let NdRule::Command { assignments, mut argv } = func.class else {
unreachable!()
};
self.set_assignments(assignments, AssignBehavior::Export);
let mut io_frame = self.io_stack.pop_frame();
io_frame.extend(func.redirs);
let func_name = argv.remove(0).span.as_str().to_string();
if let Some(func_body) = read_logic(|l| l.get_func(&func_name)) {
let saved_sh_args = read_vars(|v| v.sh_argv().clone());
write_vars(|v| {
v.clear_args();
for arg in argv {
v.bpush_arg(arg.to_string());
}
});
let result = exec_input(&func_body, Some(io_frame));
write_vars(|v| *v.sh_argv_mut() = saved_sh_args);
Ok(result?)
} else {
Err(
ShErr::full(
ShErrKind::InternalErr,
format!("Failed to find function '{}'",func_name),
blame
)
)
}
}
pub fn exec_brc_grp(&mut self, brc_grp: Node<'t>) -> ShResult<()> {
let NdRule::BraceGrp { body } = brc_grp.class else {
unreachable!()
};
let mut io_frame = self.io_stack.pop_frame();
io_frame.extend(brc_grp.redirs);
for node in body {
self.io_stack.push_frame(io_frame.clone());
self.dispatch_node(node)?;
}
Ok(())
}
pub fn exec_loop(&mut self, loop_stmt: Node<'t>) -> ShResult<()> {
let NdRule::LoopNode { kind, cond_node } = loop_stmt.class else {
unreachable!();
@@ -413,3 +477,10 @@ pub fn get_pipe_stack(num_cmds: usize) -> Vec<(Option<Redir>,Option<Redir>)> {
}
stack
}
pub fn is_func<'t>(tk: Option<Tk<'t>>) -> bool {
let Some(tk) = tk else {
return false
};
read_logic(|l| l.get_func(&tk.to_string())).is_some()
}

View File

@@ -76,6 +76,8 @@ pub enum TkRule {
Bg,
Sep,
Redir,
BraceGrpStart,
BraceGrpEnd,
Expanded { exp: Vec<String> },
Comment,
EOI, // End-of-Input
@@ -156,6 +158,7 @@ bitflags! {
const FRESH = 0b00010000;
/// The lexer has no more tokens to produce
const STALE = 0b00100000;
const IN_BRC_GRP = 0b01000000;
}
}
@@ -192,13 +195,26 @@ impl<'t> LexStream<'t> {
pub fn slice_from_cursor(&self) -> Option<&'t str> {
self.slice(self.cursor..)
}
/// The next string token is a command name
pub fn next_is_cmd(&mut self) {
self.flags |= LexFlags::NEXT_IS_CMD;
pub fn in_brc_grp(&self) -> bool {
self.flags.contains(LexFlags::IN_BRC_GRP)
}
/// The next string token is not a command name
pub fn next_is_not_cmd(&mut self) {
self.flags &= !LexFlags::NEXT_IS_CMD;
pub fn set_in_brc_grp(&mut self, is: bool) {
if is {
self.flags |= LexFlags::IN_BRC_GRP;
} else {
self.flags &= !LexFlags::IN_BRC_GRP;
}
}
pub fn next_is_cmd(&self) -> bool {
self.flags.contains(LexFlags::NEXT_IS_CMD)
}
/// Set whether the next string token is a command name
pub fn set_next_is_cmd(&mut self, is: bool) {
if is {
self.flags |= LexFlags::NEXT_IS_CMD;
} else {
self.flags &= !LexFlags::NEXT_IS_CMD;
}
}
pub fn read_redir(&mut self) -> Option<ShResult<Tk<'t>>> {
assert!(self.cursor <= self.source.len());
@@ -299,6 +315,24 @@ impl<'t> LexStream<'t> {
pos += 1;
}
}
'{' if pos == self.cursor && self.next_is_cmd() => {
pos += 1;
let mut tk = self.get_token(self.cursor..pos, TkRule::BraceGrpStart);
tk.flags |= TkFlags::IS_CMD;
self.set_in_brc_grp(true);
self.set_next_is_cmd(true);
self.cursor = pos;
return Ok(tk)
}
'}' if pos == self.cursor && self.in_brc_grp() => {
pos += 1;
let tk = self.get_token(self.cursor..pos, TkRule::BraceGrpEnd);
self.set_in_brc_grp(false);
self.set_next_is_cmd(true);
self.cursor = pos;
return Ok(tk)
}
'"' | '\'' => {
self.in_quote = true;
quote_pos = Some(pos);
@@ -340,7 +374,9 @@ impl<'t> LexStream<'t> {
);
}
if self.flags.contains(LexFlags::NEXT_IS_CMD) {
if KEYWORDS.contains(&new_tk.span.as_str()) {
flog!(DEBUG,&new_tk.span.as_str());
if is_keyword(&new_tk.span.as_str()) {
flog!(DEBUG,"is keyword");
new_tk.flags |= TkFlags::KEYWORD;
} else if is_assignment(&new_tk.span.as_str()) {
new_tk.flags |= TkFlags::ASSIGN;
@@ -351,7 +387,7 @@ impl<'t> LexStream<'t> {
new_tk.flags |= TkFlags::BUILTIN;
}
flog!(TRACE, new_tk.flags);
self.next_is_not_cmd();
self.set_next_is_cmd(false);
}
}
self.cursor = pos;
@@ -411,7 +447,7 @@ impl<'t> Iterator for LexStream<'t> {
'\r' | '\n' | ';' => {
let ch_idx = self.cursor;
self.cursor += 1;
self.next_is_cmd();
self.set_next_is_cmd(true);
while let Some(ch) = get_char(self.source, self.cursor) {
if is_hard_sep(ch) { // Combine consecutive separators into one, including whitespace
@@ -438,7 +474,7 @@ impl<'t> Iterator for LexStream<'t> {
'|' => {
let ch_idx = self.cursor;
self.cursor += 1;
self.next_is_cmd();
self.set_next_is_cmd(true);
let tk_type = if let Some('|') = get_char(self.source, self.cursor) {
self.cursor += 1;
@@ -455,7 +491,7 @@ impl<'t> Iterator for LexStream<'t> {
'&' => {
let ch_idx = self.cursor;
self.cursor += 1;
self.next_is_cmd();
self.set_next_is_cmd(true);
let tk_type = if let Some('&') = get_char(self.source, self.cursor) {
self.cursor += 1;
@@ -467,7 +503,7 @@ impl<'t> Iterator for LexStream<'t> {
}
_ => {
if let Some(tk) = self.read_redir() {
self.next_is_not_cmd();
self.set_next_is_cmd(false);
match tk {
Ok(tk) => tk,
Err(e) => return Some(Err(e))
@@ -516,3 +552,8 @@ pub fn is_hard_sep(ch: char) -> bool {
pub fn is_field_sep(ch: char) -> bool {
matches!(ch, ' ' | '\t')
}
pub fn is_keyword(slice: &str) -> bool {
KEYWORDS.contains(&slice) ||
(slice.ends_with("()") && !slice.ends_with("\\()"))
}

View File

@@ -9,6 +9,18 @@ use crate::{libsh::{error::{ShErr, ShErrKind, ShResult}, utils::TkVecUtils}, pre
pub mod lex;
pub mod execute;
/// Try to match a specific parsing rule
///
/// # Notes
/// * If the match fails, execution continues.
/// * If the match succeeds, the matched node is returned.
macro_rules! try_match {
($expr:expr) => {
if let Some(node) = $expr {
return Ok(Some(node))
}
};
}
#[derive(Clone,Debug)]
pub struct Node<'t> {
@@ -244,6 +256,8 @@ pub enum NdRule<'t> {
Pipeline { cmds: Vec<Node<'t>>, pipe_err: bool },
Conjunction { elements: Vec<ConjunctNode<'t>> },
Assignment { kind: AssignKind, var: Tk<'t>, val: Tk<'t> },
BraceGrp { body: Vec<Node<'t>> },
FuncDef { name: Tk<'t>, body: Box<Node<'t>> }
}
#[derive(Debug)]
@@ -270,6 +284,9 @@ impl<'t> ParseStream<'t> {
&TkRule::Null
}
}
fn peek_tk(&self) -> Option<&Tk<'t>> {
self.tokens.first()
}
fn next_tk(&mut self) -> Option<Tk<'t>> {
if !self.tokens.is_empty() {
if *self.next_tk_class() == TkRule::EOI {
@@ -301,6 +318,7 @@ impl<'t> ParseStream<'t> {
TkRule::Or |
TkRule::Bg |
TkRule::And |
TkRule::BraceGrpEnd |
TkRule::Pipe => Ok(()),
TkRule::Sep => {
@@ -371,25 +389,127 @@ impl<'t> ParseStream<'t> {
/// This tries to match on different stuff that can appear in a command position
/// Matches shell commands like if-then-fi, pipelines, etc.
/// 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 left-recursion issues in self.parse_pipeline()
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))
}
try_match!(self.parse_func_def()?);
try_match!(self.parse_brc_grp(false /* from_func_def */)?);
try_match!(self.parse_loop()?);
try_match!(self.parse_if()?);
if check_pipelines {
if let Some(node) = self.parse_pipeline()? {
return Ok(Some(node))
}
try_match!(self.parse_pipeline()?);
} else {
if let Some(node) = self.parse_cmd()? {
return Ok(Some(node))
}
try_match!(self.parse_cmd()?);
}
Ok(None)
}
fn parse_func_def(&mut self) -> ShResult<Option<Node<'t>>> {
let mut node_tks: Vec<Tk> = vec![];
let name;
let body;
if !is_func_name(self.peek_tk()) {
return Ok(None)
}
let name_tk = self.next_tk().unwrap();
node_tks.push(name_tk.clone());
name = name_tk;
let Some(brc_grp) = self.parse_brc_grp(true /* from_func_def */)? else {
return Err(parse_err_full(
"Expected a brace group after function name",
&node_tks.get_span().unwrap()
)
)
};
body = Box::new(brc_grp);
let node = Node {
class: NdRule::FuncDef { name, body },
flags: NdFlags::empty(),
redirs: vec![],
tokens: node_tks
};
flog!(DEBUG,node);
Ok(Some(node))
}
fn parse_brc_grp(&mut self, from_func_def: bool) -> ShResult<Option<Node<'t>>> {
let mut node_tks: Vec<Tk> = vec![];
let mut body: Vec<Node> = vec![];
let mut redirs: Vec<Redir> = vec![];
if *self.next_tk_class() != TkRule::BraceGrpStart {
return Ok(None)
}
node_tks.push(self.next_tk().unwrap());
loop {
if *self.next_tk_class() == TkRule::BraceGrpEnd {
node_tks.push(self.next_tk().unwrap());
break
}
if let Some(node) = self.parse_block(true)? {
node_tks.extend(node.tokens.clone());
body.push(node);
}
if !self.next_tk_is_some() {
return Err(parse_err_full(
"Expected a closing brace for this brace group",
&node_tks.get_span().unwrap()
)
)
}
}
if !from_func_def {
while self.check_redir() {
let tk = self.next_tk().unwrap();
node_tks.push(tk.clone());
let redir_bldr = tk.span.as_str().parse::<RedirBldr>().unwrap();
if redir_bldr.io_mode.is_none() {
let path_tk = self.next_tk();
if path_tk.clone().is_none_or(|tk| tk.class == TkRule::EOI) {
self.flags |= ParseFlags::ERROR;
return Err(
ShErr::full(
ShErrKind::ParseErr,
"Expected a filename after this redirection",
tk.span.clone().into()
)
)
};
let path_tk = path_tk.unwrap();
node_tks.push(path_tk.clone());
let redir_class = redir_bldr.class.unwrap();
let pathbuf = PathBuf::from(path_tk.span.as_str());
let Ok(file) = get_redir_file(redir_class, pathbuf) else {
self.flags |= ParseFlags::ERROR;
return Err(parse_err_full(
"Error opening file for redirection",
&path_tk.span
));
};
let io_mode = IoMode::file(redir_bldr.tgt_fd.unwrap(), file);
let redir_bldr = redir_bldr.with_io_mode(io_mode);
let redir = redir_bldr.build();
redirs.push(redir);
}
}
}
let node = Node {
class: NdRule::BraceGrp { body },
flags: NdFlags::empty(),
redirs,
tokens: node_tks
};
flog!(DEBUG, node);
Ok(Some(node))
}
fn parse_if(&mut self) -> ShResult<Option<Node<'t>>> {
// Needs at last one 'if-then',
// Any number of 'elif-then',
@@ -647,6 +767,7 @@ impl<'t> ParseStream<'t> {
TkRule::EOI |
TkRule::Pipe |
TkRule::And |
TkRule::BraceGrpEnd |
TkRule::Or => {
break
}
@@ -878,3 +999,10 @@ fn parse_err_full<'t>(reason: &str, blame: &Span<'t>) -> ShErr {
blame.clone().into()
)
}
fn is_func_name<'t>(tk: Option<&Tk<'t>>) -> bool {
tk.is_some_and(|tk| {
tk.flags.contains(TkFlags::KEYWORD) &&
(tk.span.as_str().ends_with("()") && !tk.span.as_str().ends_with("\\()"))
})
}

View File

@@ -1,11 +1,43 @@
use std::{collections::{HashMap, VecDeque}, sync::{LazyLock, RwLock, RwLockReadGuard, RwLockWriteGuard}};
use std::{cell::RefCell, collections::{HashMap, VecDeque}, ops::Range, sync::{LazyLock, RwLock, RwLockReadGuard, RwLockWriteGuard}};
use crate::{exec_input, jobs::JobTab, libsh::{error::ShResult, utils::VecDequeExt}, parse::lex::get_char, prelude::*};
use crate::{exec_input, jobs::JobTab, libsh::{error::ShResult, utils::VecDequeExt}, parse::{lex::{get_char, Tk}, Node}, prelude::*};
pub static JOB_TABLE: LazyLock<RwLock<JobTab>> = LazyLock::new(|| RwLock::new(JobTab::new()));
pub static VAR_TABLE: LazyLock<RwLock<VarTab>> = LazyLock::new(|| RwLock::new(VarTab::new()));
pub static LOGIC_TABLE: LazyLock<RwLock<LogTab>> = LazyLock::new(|| RwLock::new(LogTab::new()));
thread_local! {
pub static LAST_INPUT: RefCell<String> = RefCell::new(String::new());
}
/// The logic table for the shell
///
/// Contains aliases and functions
pub struct LogTab {
// TODO: Find a way to store actual owned nodes instead of strings that must be re-parsed
functions: HashMap<String,String>,
aliases: HashMap<String,String>
}
impl LogTab {
pub fn new() -> Self {
Self { functions: HashMap::new(), aliases: HashMap::new() }
}
pub fn insert_func(&mut self, name: &str, body: &str) {
self.functions.insert(name.into(), body.into());
}
pub fn get_func(&self, name: &str) -> Option<String> {
self.functions.get(name).cloned()
}
pub fn insert_alias(&mut self, name: &str, body: &str) {
self.aliases.insert(name.into(), body.into());
}
pub fn get_alias(&self, name: &str) -> Option<String> {
self.aliases.get(name).cloned()
}
}
pub struct VarTab {
vars: HashMap<String,String>,
@@ -35,6 +67,15 @@ impl VarTab {
self.bpush_arg(arg);
}
}
pub fn sh_argv(&self) -> &VecDeque<String> {
&self.sh_argv
}
pub fn sh_argv_mut(&mut self) -> &mut VecDeque<String> {
&mut self.sh_argv
}
pub fn clear_args(&mut self) {
self.sh_argv.clear()
}
fn update_arg_params(&mut self) {
self.set_param('@', &self.sh_argv.clone().to_vec().join(" "));
self.set_param('#', &self.sh_argv.len().to_string());
@@ -131,6 +172,33 @@ pub fn write_vars<T, F: FnOnce(&mut RwLockWriteGuard<VarTab>) -> T>(f: F) -> T {
f(lock)
}
/// Read from the logic table
pub fn read_logic<T, F: FnOnce(RwLockReadGuard<LogTab>) -> T>(f: F) -> T {
let lock = LOGIC_TABLE.read().unwrap();
f(lock)
}
/// Write to the logic table
pub fn write_logic<T, F: FnOnce(&mut RwLockWriteGuard<LogTab>) -> T>(f: F) -> T {
let lock = &mut LOGIC_TABLE.write().unwrap();
f(lock)
}
pub fn set_last_input(input: &str) {
LAST_INPUT.with(|input_ref| {
let mut last_input = input_ref.borrow_mut();
last_input.clear();
last_input.push_str(input);
})
}
pub fn slice_last_input(range: Range<usize>) -> String {
LAST_INPUT.with(|input_ref| {
let input = input_ref.borrow();
input[range].to_string()
})
}
pub fn get_status() -> i32 {
read_vars(|v| v.get_param('?')).parse::<i32>().unwrap()
}
@@ -145,6 +213,6 @@ pub fn source_file(path: PathBuf) -> ShResult<()> {
let mut buf = String::new();
file.read_to_string(&mut buf)?;
exec_input(&buf)?;
exec_input(&buf, None)?;
Ok(())
}