Implemented assignments, working on job control
This commit is contained in:
@@ -2,9 +2,14 @@ use std::collections::VecDeque;
|
||||
|
||||
use nix::sys::wait::WaitPidFlag;
|
||||
|
||||
use crate::{libsh::error::ShResult, prelude::*, procio::{IoFrame, IoPipe, IoStack}, state};
|
||||
use crate::{builtin::echo::echo, libsh::error::ShResult, prelude::*, procio::{IoFrame, IoPipe, IoStack}, state::{self, write_vars}};
|
||||
|
||||
use super::{lex::Tk, ConjunctNode, ConjunctOp, NdRule, Node, Redir, RedirType};
|
||||
use super::{lex::{Tk, TkFlags}, AssignKind, ConjunctNode, ConjunctOp, NdRule, Node, Redir, RedirType};
|
||||
|
||||
pub enum AssignBehavior {
|
||||
Export,
|
||||
Set
|
||||
}
|
||||
|
||||
/// Arguments to the execvpe function
|
||||
pub struct ExecArgs {
|
||||
@@ -14,8 +19,9 @@ pub struct ExecArgs {
|
||||
}
|
||||
|
||||
impl ExecArgs {
|
||||
pub fn new(argv: Vec<String>) -> Self {
|
||||
pub fn new(argv: Vec<Tk>) -> Self {
|
||||
assert!(!argv.is_empty());
|
||||
let argv = prepare_argv(argv);
|
||||
let cmd = Self::get_cmd(&argv);
|
||||
let argv = Self::get_argv(argv);
|
||||
let envp = Self::get_envp();
|
||||
@@ -42,22 +48,33 @@ impl<'t> Dispatcher<'t> {
|
||||
let nodes = VecDeque::from(nodes);
|
||||
Self { nodes, io_stack: IoStack::new() }
|
||||
}
|
||||
pub fn begin_dispatch(&mut self) -> ShResult<'t,()> {
|
||||
pub fn begin_dispatch(&mut self) -> ShResult<()> {
|
||||
flog!(TRACE, "beginning dispatch");
|
||||
while let Some(list) = self.nodes.pop_front() {
|
||||
self.dispatch_node(list)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
pub fn dispatch_node(&mut self, node: Node<'t>) -> ShResult<'t,()> {
|
||||
pub fn dispatch_node(&mut self, node: Node<'t>) -> ShResult<()> {
|
||||
match node.class {
|
||||
NdRule::CmdList {..} => self.exec_conjunction(node)?,
|
||||
NdRule::Pipeline {..} => self.exec_pipeline(node)?,
|
||||
NdRule::Command {..} => self.exec_cmd(node)?,
|
||||
NdRule::Command {..} => self.dispatch_cmd(node)?,
|
||||
_ => unreachable!()
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
pub fn exec_conjunction(&mut self, conjunction: Node<'t>) -> ShResult<'t,()> {
|
||||
pub fn dispatch_cmd(&mut self, node: Node<'t>) -> ShResult<()> {
|
||||
let Some(cmd) = node.get_command() else {
|
||||
return self.exec_cmd(node)
|
||||
};
|
||||
if cmd.flags.contains(TkFlags::BUILTIN) {
|
||||
self.exec_builtin(node)
|
||||
} else {
|
||||
self.exec_cmd(node)
|
||||
}
|
||||
}
|
||||
pub fn exec_conjunction(&mut self, conjunction: Node<'t>) -> ShResult<()> {
|
||||
let NdRule::CmdList { elements } = conjunction.class else {
|
||||
unreachable!()
|
||||
};
|
||||
@@ -76,7 +93,7 @@ impl<'t> Dispatcher<'t> {
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
pub fn exec_pipeline(&mut self, pipeline: Node<'t>) -> ShResult<'t,()> {
|
||||
pub fn exec_pipeline(&mut self, pipeline: Node<'t>) -> ShResult<()> {
|
||||
let NdRule::Pipeline { cmds, pipe_err } = pipeline.class else {
|
||||
unreachable!()
|
||||
};
|
||||
@@ -96,15 +113,45 @@ impl<'t> Dispatcher<'t> {
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
pub fn exec_cmd(&mut self, cmd: Node<'t>) -> ShResult<'t,()> {
|
||||
pub fn exec_builtin(&mut self, mut cmd: Node<'t>) -> ShResult<()> {
|
||||
let NdRule::Command { ref mut assignments, argv } = &mut cmd.class else {
|
||||
unreachable!()
|
||||
};
|
||||
let env_vars_to_unset = self.set_assignments(mem::take(assignments), AssignBehavior::Export);
|
||||
let cmd_raw = cmd.get_command().unwrap();
|
||||
flog!(TRACE, "doing builtin");
|
||||
let result = match cmd_raw.span.as_str() {
|
||||
"echo" => echo(cmd, &mut self.io_stack),
|
||||
_ => unimplemented!("Have not yet added support for builtin '{}'", cmd_raw.span.as_str())
|
||||
};
|
||||
|
||||
for var in env_vars_to_unset {
|
||||
env::set_var(&var, "");
|
||||
}
|
||||
|
||||
Ok(result?)
|
||||
}
|
||||
pub fn exec_cmd(&mut self, cmd: Node<'t>) -> ShResult<()> {
|
||||
let NdRule::Command { assignments, argv } = cmd.class else {
|
||||
unreachable!()
|
||||
};
|
||||
let mut env_vars_to_unset = vec![];
|
||||
if !assignments.is_empty() {
|
||||
let assign_behavior = if argv.is_empty() {
|
||||
AssignBehavior::Set
|
||||
} else {
|
||||
AssignBehavior::Export
|
||||
};
|
||||
env_vars_to_unset = self.set_assignments(assignments, assign_behavior);
|
||||
}
|
||||
for redir in cmd.redirs {
|
||||
self.io_stack.push_to_frame(redir);
|
||||
}
|
||||
if argv.is_empty() {
|
||||
return Ok(())
|
||||
}
|
||||
|
||||
let exec_args = ExecArgs::new(prepare_argv(argv));
|
||||
let exec_args = ExecArgs::new(argv);
|
||||
let io_frame = self.io_stack.pop_frame();
|
||||
run_fork(
|
||||
io_frame,
|
||||
@@ -113,8 +160,51 @@ impl<'t> Dispatcher<'t> {
|
||||
def_parent_action
|
||||
)?;
|
||||
|
||||
for var in env_vars_to_unset {
|
||||
std::env::set_var(&var, "");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
pub fn set_assignments(&self, assigns: Vec<Node<'t>>, behavior: AssignBehavior) -> Vec<String> {
|
||||
let mut new_env_vars = vec![];
|
||||
match behavior {
|
||||
AssignBehavior::Export => {
|
||||
for assign in assigns {
|
||||
let NdRule::Assignment { kind, var, val } = assign.class else {
|
||||
unreachable!()
|
||||
};
|
||||
let var = var.span.as_str();
|
||||
let val = val.span.as_str();
|
||||
match kind {
|
||||
AssignKind::Eq => std::env::set_var(var, val),
|
||||
AssignKind::PlusEq => todo!(),
|
||||
AssignKind::MinusEq => todo!(),
|
||||
AssignKind::MultEq => todo!(),
|
||||
AssignKind::DivEq => todo!(),
|
||||
}
|
||||
new_env_vars.push(var.to_string());
|
||||
}
|
||||
}
|
||||
AssignBehavior::Set => {
|
||||
for assign in assigns {
|
||||
let NdRule::Assignment { kind, var, val } = assign.class else {
|
||||
unreachable!()
|
||||
};
|
||||
let var = var.span.as_str();
|
||||
let val = val.span.as_str();
|
||||
match kind {
|
||||
AssignKind::Eq => write_vars(|v| v.new_var(var, val)),
|
||||
AssignKind::PlusEq => todo!(),
|
||||
AssignKind::MinusEq => todo!(),
|
||||
AssignKind::MultEq => todo!(),
|
||||
AssignKind::DivEq => todo!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
new_env_vars
|
||||
}
|
||||
}
|
||||
|
||||
pub fn prepare_argv(argv: Vec<Tk>) -> Vec<String> {
|
||||
@@ -134,15 +224,20 @@ pub fn run_fork<'t,C,P>(
|
||||
exec_args: ExecArgs,
|
||||
child_action: C,
|
||||
parent_action: P,
|
||||
) -> ShResult<'t,()>
|
||||
) -> ShResult<()>
|
||||
where
|
||||
C: Fn(IoFrame,ExecArgs),
|
||||
P: Fn(IoFrame,Pid) -> ShResult<'t,()>
|
||||
C: Fn(IoFrame,ExecArgs) -> Errno,
|
||||
P: Fn(IoFrame,Pid) -> ShResult<()>
|
||||
{
|
||||
match unsafe { fork()? } {
|
||||
ForkResult::Child => {
|
||||
child_action(io_frame,exec_args);
|
||||
exit(1);
|
||||
let cmd = &exec_args.cmd.to_str().unwrap().to_string();
|
||||
let errno = child_action(io_frame,exec_args);
|
||||
match errno {
|
||||
Errno::ENOENT => eprintln!("Command not found: {}", cmd),
|
||||
_ => eprintln!("{errno}")
|
||||
}
|
||||
exit(errno as i32);
|
||||
}
|
||||
ForkResult::Parent { child } => {
|
||||
parent_action(io_frame,child)
|
||||
@@ -151,18 +246,21 @@ where
|
||||
}
|
||||
|
||||
/// The default behavior for the child process after forking
|
||||
pub fn def_child_action<'t>(mut io_frame: IoFrame, exec_args: ExecArgs) {
|
||||
io_frame.redirect().unwrap();
|
||||
execvpe(&exec_args.cmd, &exec_args.argv, &exec_args.envp).unwrap();
|
||||
pub fn def_child_action<'t>(mut io_frame: IoFrame, exec_args: ExecArgs) -> Errno {
|
||||
if let Err(e) = io_frame.redirect() {
|
||||
eprintln!("{e}");
|
||||
}
|
||||
let Err(e) = execvpe(&exec_args.cmd, &exec_args.argv, &exec_args.envp);
|
||||
e
|
||||
}
|
||||
|
||||
/// The default behavior for the parent process after forking
|
||||
pub fn def_parent_action<'t>(io_frame: IoFrame, child: Pid) -> ShResult<'t,()> {
|
||||
let status = waitpid(child, Some(WaitPidFlag::WSTOPPED))?;
|
||||
pub fn def_parent_action<'t>(io_frame: IoFrame, child: Pid) -> ShResult<()> {
|
||||
let status = waitpid(child, Some(WtFlag::WSTOPPED))?;
|
||||
match status {
|
||||
WaitStatus::Exited(_, status) => state::set_status(status),
|
||||
WtStat::Exited(_, status) => state::set_status(status),
|
||||
_ => unimplemented!()
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::{fmt::Display, ops::{Bound, Deref, Range, RangeBounds}};
|
||||
|
||||
use bitflags::bitflags;
|
||||
|
||||
use crate::{libsh::error::{ShErr, ShErrKind}, prelude::*};
|
||||
use crate::{builtin::BUILTINS, libsh::error::{ShErr, ShErrKind}, prelude::*};
|
||||
|
||||
pub const KEYWORDS: [&'static str;14] = [
|
||||
"if",
|
||||
@@ -48,6 +48,12 @@ impl<'s> Span<'s> {
|
||||
pub fn as_str(&self) -> &str {
|
||||
&self.source[self.start..self.end]
|
||||
}
|
||||
pub fn get_source(&'s self) -> &'s str {
|
||||
self.source
|
||||
}
|
||||
pub fn range(&self) -> Range<usize> {
|
||||
self.range.clone()
|
||||
}
|
||||
}
|
||||
|
||||
/// Allows simple access to the underlying range wrapped by the span
|
||||
@@ -130,6 +136,9 @@ impl<'s> Tk<'s> {
|
||||
pub fn is_err(&self) -> bool {
|
||||
self.err_span.is_some()
|
||||
}
|
||||
pub fn source(&self) -> &'s str {
|
||||
self.span.source
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> Display for Tk<'s> {
|
||||
@@ -150,6 +159,8 @@ bitflags! {
|
||||
const OPENER = 0b0000000000000010;
|
||||
const IS_CMD = 0b0000000000000100;
|
||||
const IS_OP = 0b0000000000001000;
|
||||
const ASSIGN = 0b0000000000010000;
|
||||
const BUILTIN = 0b0000000000100000;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,6 +192,7 @@ bitflags! {
|
||||
|
||||
impl<'t> LexStream<'t> {
|
||||
pub fn new(source: &'t str, flags: LexFlags) -> Self {
|
||||
flog!(TRACE, "new lex stream");
|
||||
let flags = flags | LexFlags::FRESH | LexFlags::NEXT_IS_CMD;
|
||||
Self { source, cursor: 0, in_quote: false, flags }
|
||||
}
|
||||
@@ -346,8 +358,15 @@ impl<'t> LexStream<'t> {
|
||||
if self.flags.contains(LexFlags::NEXT_IS_CMD) {
|
||||
if KEYWORDS.contains(&new_tk.span.as_str()) {
|
||||
new_tk.flags |= TkFlags::KEYWORD;
|
||||
} else if is_assignment(&new_tk.span.as_str()) {
|
||||
new_tk.flags |= TkFlags::ASSIGN;
|
||||
} else {
|
||||
new_tk.flags |= TkFlags::IS_CMD;
|
||||
flog!(TRACE, new_tk.span.as_str());
|
||||
if BUILTINS.contains(&new_tk.span.as_str()) {
|
||||
new_tk.flags |= TkFlags::BUILTIN;
|
||||
}
|
||||
flog!(TRACE, new_tk.flags);
|
||||
self.next_is_not_cmd();
|
||||
}
|
||||
}
|
||||
@@ -480,6 +499,19 @@ pub fn get_char(src: &str, idx: usize) -> Option<char> {
|
||||
src.get(idx..)?.chars().next()
|
||||
}
|
||||
|
||||
pub fn is_assignment(text: &str) -> bool {
|
||||
let mut chars = text.chars();
|
||||
|
||||
while let Some(ch) = chars.next() {
|
||||
match ch {
|
||||
'\\' => { chars.next(); }
|
||||
'=' => return true,
|
||||
_ => continue
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// Is '|', '&', '>', or '<'
|
||||
pub fn is_op(ch: char) -> bool {
|
||||
matches!(ch, '|' | '&' | '>' | '<')
|
||||
|
||||
234
src/parse/mod.rs
234
src/parse/mod.rs
@@ -1,7 +1,7 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use bitflags::bitflags;
|
||||
use lex::{Span, Tk, TkFlags, TkRule};
|
||||
use lex::{is_hard_sep, Span, Tk, TkFlags, TkRule};
|
||||
|
||||
use crate::{prelude::*, libsh::error::{ShErr, ShErrKind, ShResult}, procio::{IoFd, IoFile, IoInfo}};
|
||||
|
||||
@@ -17,6 +17,16 @@ pub struct Node<'t> {
|
||||
pub tokens: Vec<Tk<'t>>,
|
||||
}
|
||||
|
||||
impl<'t> Node<'t> {
|
||||
pub fn get_command(&'t self) -> Option<&'t Tk<'t>> {
|
||||
let NdRule::Command { assignments: _, argv } = &self.class else {
|
||||
return None
|
||||
};
|
||||
let command = argv.iter().find(|tk| tk.flags.contains(TkFlags::IS_CMD))?;
|
||||
Some(command)
|
||||
}
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
#[derive(Debug)]
|
||||
pub struct NdFlags: u32 {
|
||||
@@ -48,8 +58,8 @@ impl RedirBldr {
|
||||
Default::default()
|
||||
}
|
||||
pub fn with_io_info(self, io_info: Box<dyn IoInfo>) -> Self {
|
||||
let Self { io_info: _, class, tgt_fd } = self;
|
||||
Self { io_info: Some(io_info), class, tgt_fd }
|
||||
let Self { io_info: _, class, tgt_fd } = self;
|
||||
Self { io_info: Some(io_info), class, tgt_fd }
|
||||
}
|
||||
pub fn with_class(self, class: RedirType) -> Self {
|
||||
let Self { io_info, class: _, tgt_fd } = self;
|
||||
@@ -127,8 +137,8 @@ impl FromStr for RedirBldr {
|
||||
let tgt_fd = tgt_fd.parse::<i32>().unwrap_or_else(|_| {
|
||||
match redir.class.unwrap() {
|
||||
RedirType::Input |
|
||||
RedirType::HereDoc |
|
||||
RedirType::HereString => 0,
|
||||
RedirType::HereDoc |
|
||||
RedirType::HereString => 0,
|
||||
_ => 1
|
||||
}
|
||||
});
|
||||
@@ -199,22 +209,12 @@ pub enum NdRule<'t> {
|
||||
LoopNode { kind: LoopKind, cond_block: CondNode<'t> },
|
||||
ForNode { vars: Vec<Tk<'t>>, arr: Vec<Tk<'t>>, body: Vec<Node<'t>> },
|
||||
CaseNode { pattern: Tk<'t>, case_blocks: Vec<CaseNode<'t>> },
|
||||
Command { assignments: Vec<Tk<'t>>, argv: Vec<Tk<'t>> },
|
||||
Command { assignments: Vec<Node<'t>>, argv: Vec<Tk<'t>> },
|
||||
Pipeline { cmds: Vec<Node<'t>>, pipe_err: bool },
|
||||
CmdList { elements: Vec<ConjunctNode<'t>> },
|
||||
Assignment { kind: AssignKind, var: Tk<'t>, val: Tk<'t> },
|
||||
}
|
||||
|
||||
/// If the given expression returns Some(), then return it
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ParseResult<'t> {
|
||||
NoMatch,
|
||||
Error(ShErr<'t>),
|
||||
Match(Node<'t>)
|
||||
}
|
||||
use ParseResult::*; // muh ergonomics
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ParseStream<'t> {
|
||||
pub tokens: Vec<Tk<'t>>,
|
||||
@@ -254,11 +254,11 @@ impl<'t> ParseStream<'t> {
|
||||
assert!(num_consumed <= self.tokens.len());
|
||||
self.tokens = self.tokens[num_consumed..].to_vec();
|
||||
}
|
||||
fn parse_cmd_list(&mut self) -> ParseResult<'t> {
|
||||
fn parse_cmd_list(&mut self) -> ShResult<Option<Node<'t>>> {
|
||||
let mut elements = vec![];
|
||||
let mut node_tks = vec![];
|
||||
|
||||
while let Match(block) = self.parse_block(true) {
|
||||
while let Some(block) = self.parse_block(true)? {
|
||||
node_tks.append(&mut block.tokens.clone());
|
||||
let conjunct_op = match self.next_tk_class() {
|
||||
TkRule::And => ConjunctOp::And,
|
||||
@@ -275,35 +275,37 @@ impl<'t> ParseStream<'t> {
|
||||
break
|
||||
}
|
||||
}
|
||||
Match(Node {
|
||||
class: NdRule::CmdList { elements },
|
||||
flags: NdFlags::empty(),
|
||||
redirs: vec![],
|
||||
tokens: node_tks
|
||||
})
|
||||
if elements.is_empty() {
|
||||
Ok(None)
|
||||
} else {
|
||||
Ok(Some(Node {
|
||||
class: NdRule::CmdList { elements },
|
||||
flags: NdFlags::empty(),
|
||||
redirs: vec![],
|
||||
tokens: node_tks
|
||||
}))
|
||||
}
|
||||
}
|
||||
/// 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
|
||||
fn parse_block(&mut self, check_pipelines: bool) -> ParseResult<'t> {
|
||||
fn parse_block(&mut self, check_pipelines: bool) -> ShResult<Option<Node<'t>>> {
|
||||
if check_pipelines {
|
||||
match self.parse_pipeline() {
|
||||
NoMatch => { /* Continue */ }
|
||||
result => return result
|
||||
if let Some(node) = self.parse_pipeline()? {
|
||||
return Ok(Some(node))
|
||||
}
|
||||
} else {
|
||||
match self.parse_cmd() {
|
||||
NoMatch => { /* Continue */ }
|
||||
result => return result
|
||||
if let Some(node) = self.parse_cmd()? {
|
||||
return Ok(Some(node))
|
||||
}
|
||||
}
|
||||
NoMatch
|
||||
Ok(None)
|
||||
}
|
||||
fn parse_pipeline(&mut self) -> ParseResult<'t> {
|
||||
fn parse_pipeline(&mut self) -> ShResult<Option<Node<'t>>> {
|
||||
let mut cmds = vec![];
|
||||
let mut node_tks = vec![];
|
||||
while let Match(cmd) = self.parse_block(false) {
|
||||
while let Some(cmd) = self.parse_block(false)? {
|
||||
let is_punctuated = node_is_punctuated(&cmd.tokens);
|
||||
node_tks.append(&mut cmd.tokens.clone());
|
||||
cmds.push(cmd);
|
||||
@@ -318,18 +320,18 @@ impl<'t> ParseStream<'t> {
|
||||
}
|
||||
}
|
||||
if cmds.is_empty() {
|
||||
NoMatch
|
||||
Ok(None)
|
||||
} else {
|
||||
Match(Node {
|
||||
Ok(Some(Node {
|
||||
// TODO: implement pipe_err support
|
||||
class: NdRule::Pipeline { cmds, pipe_err: false },
|
||||
flags: NdFlags::empty(),
|
||||
redirs: vec![],
|
||||
tokens: node_tks
|
||||
})
|
||||
}))
|
||||
}
|
||||
}
|
||||
fn parse_cmd(&mut self) -> ParseResult<'t> {
|
||||
fn parse_cmd(&mut self) -> ShResult<Option<Node<'t>>> {
|
||||
let tk_slice = self.tokens.as_slice();
|
||||
let mut tk_iter = tk_slice.iter();
|
||||
let mut node_tks = vec![];
|
||||
@@ -337,25 +339,32 @@ impl<'t> ParseStream<'t> {
|
||||
let mut argv = vec![];
|
||||
let mut assignments = vec![];
|
||||
|
||||
let Some(cmd_tk) = tk_iter.next() else {
|
||||
return NoMatch
|
||||
};
|
||||
while let Some(prefix_tk) = tk_iter.next() {
|
||||
if prefix_tk.flags.contains(TkFlags::IS_CMD) {
|
||||
node_tks.push(prefix_tk.clone());
|
||||
argv.push(prefix_tk.clone());
|
||||
break
|
||||
} else if prefix_tk.flags.contains(TkFlags::ASSIGN) {
|
||||
let Some(assign) = self.parse_assignment(&prefix_tk) else {
|
||||
break
|
||||
};
|
||||
node_tks.push(prefix_tk.clone());
|
||||
assignments.push(assign)
|
||||
}
|
||||
}
|
||||
|
||||
if !cmd_tk.flags.contains(TkFlags::IS_CMD) {
|
||||
return NoMatch
|
||||
} else {
|
||||
node_tks.push(cmd_tk.clone());
|
||||
argv.push(cmd_tk.clone());
|
||||
if argv.is_empty() && assignments.is_empty() {
|
||||
return Ok(None)
|
||||
}
|
||||
|
||||
while let Some(tk) = tk_iter.next() {
|
||||
match tk.class {
|
||||
TkRule::EOI |
|
||||
TkRule::Pipe |
|
||||
TkRule::And |
|
||||
TkRule::Or => {
|
||||
break
|
||||
}
|
||||
TkRule::Pipe |
|
||||
TkRule::And |
|
||||
TkRule::Or => {
|
||||
break
|
||||
}
|
||||
TkRule::Sep => {
|
||||
node_tks.push(tk.clone());
|
||||
break
|
||||
@@ -368,9 +377,11 @@ impl<'t> ParseStream<'t> {
|
||||
node_tks.push(tk.clone());
|
||||
let redir_bldr = tk.span.as_str().parse::<RedirBldr>().unwrap();
|
||||
if redir_bldr.io_info.is_none() {
|
||||
let Some(path_tk) = tk_iter.next() else {
|
||||
let path_tk = tk_iter.next();
|
||||
|
||||
if path_tk.is_none_or(|tk| tk.class == TkRule::EOI) {
|
||||
self.flags |= ParseFlags::ERROR;
|
||||
return Error(
|
||||
return Err(
|
||||
ShErr::full(
|
||||
ShErrKind::ParseErr,
|
||||
"Expected a filename after this redirection",
|
||||
@@ -378,6 +389,8 @@ impl<'t> ParseStream<'t> {
|
||||
)
|
||||
)
|
||||
};
|
||||
|
||||
let path_tk = path_tk.unwrap();
|
||||
node_tks.push(path_tk.clone());
|
||||
|
||||
let Ok(file) = (match redir_bldr.class.unwrap() {
|
||||
@@ -403,7 +416,7 @@ impl<'t> ParseStream<'t> {
|
||||
_ => unreachable!()
|
||||
}) else {
|
||||
self.flags |= ParseFlags::ERROR;
|
||||
return Error(
|
||||
return Err(
|
||||
ShErr::full(
|
||||
ShErrKind::InternalErr,
|
||||
"Error opening file for redirection",
|
||||
@@ -423,17 +436,118 @@ impl<'t> ParseStream<'t> {
|
||||
}
|
||||
self.commit(node_tks.len());
|
||||
|
||||
Match(Node {
|
||||
Ok(Some(Node {
|
||||
class: NdRule::Command { assignments, argv },
|
||||
tokens: node_tks,
|
||||
flags: NdFlags::empty(),
|
||||
redirs,
|
||||
})
|
||||
}))
|
||||
}
|
||||
fn parse_assignment(&self, token: &Tk<'t>) -> Option<Node<'t>> {
|
||||
let mut chars = token.span.as_str().chars();
|
||||
let mut var_name = String::new();
|
||||
let mut name_range = token.span.start..token.span.start;
|
||||
let mut var_val = String::new();
|
||||
let mut val_range = token.span.end..token.span.end;
|
||||
let mut assign_kind = None;
|
||||
let mut pos = token.span.start;
|
||||
|
||||
while let Some(ch) = chars.next() {
|
||||
if !assign_kind.is_none() {
|
||||
match ch {
|
||||
'\\' => {
|
||||
pos += ch.len_utf8();
|
||||
var_val.push(ch);
|
||||
if let Some(esc_ch) = chars.next() {
|
||||
pos += esc_ch.len_utf8();
|
||||
var_val.push(esc_ch);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
pos += ch.len_utf8();
|
||||
var_val.push(ch);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
match ch {
|
||||
'=' => {
|
||||
name_range.end = pos;
|
||||
pos += ch.len_utf8();
|
||||
val_range.start = pos;
|
||||
assign_kind = Some(AssignKind::Eq);
|
||||
}
|
||||
'-' => {
|
||||
name_range.end = pos;
|
||||
pos += ch.len_utf8();
|
||||
let Some('=') = chars.next() else {
|
||||
return None
|
||||
};
|
||||
pos += '='.len_utf8();
|
||||
val_range.start = pos;
|
||||
assign_kind = Some(AssignKind::MinusEq);
|
||||
}
|
||||
'+' => {
|
||||
name_range.end = pos;
|
||||
pos += ch.len_utf8();
|
||||
let Some('=') = chars.next() else {
|
||||
return None
|
||||
};
|
||||
pos += '='.len_utf8();
|
||||
val_range.start = pos;
|
||||
assign_kind = Some(AssignKind::PlusEq);
|
||||
}
|
||||
'/' => {
|
||||
name_range.end = pos;
|
||||
pos += ch.len_utf8();
|
||||
let Some('=') = chars.next() else {
|
||||
return None
|
||||
};
|
||||
pos += '='.len_utf8();
|
||||
val_range.start = pos;
|
||||
assign_kind = Some(AssignKind::DivEq);
|
||||
}
|
||||
'*' => {
|
||||
name_range.end = pos;
|
||||
pos += ch.len_utf8();
|
||||
let Some('=') = chars.next() else {
|
||||
return None
|
||||
};
|
||||
pos += '='.len_utf8();
|
||||
val_range.start = pos;
|
||||
assign_kind = Some(AssignKind::MultEq);
|
||||
}
|
||||
'\\' => {
|
||||
pos += ch.len_utf8();
|
||||
var_name.push(ch);
|
||||
if let Some(esc_ch) = chars.next() {
|
||||
pos += esc_ch.len_utf8();
|
||||
var_name.push(esc_ch);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
pos += ch.len_utf8();
|
||||
var_name.push(ch)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if assign_kind.is_none() || var_name.is_empty() {
|
||||
None
|
||||
} else {
|
||||
let var = Tk::new(TkRule::Str, Span::new(name_range, token.source()));
|
||||
let val = Tk::new(TkRule::Str, Span::new(val_range, token.source()));
|
||||
Some(Node {
|
||||
class: NdRule::Assignment { kind: assign_kind.unwrap(), var, val },
|
||||
tokens: vec![token.clone()],
|
||||
flags: NdFlags::empty(),
|
||||
redirs: vec![]
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'t> Iterator for ParseStream<'t> {
|
||||
type Item = ParseResult<'t>;
|
||||
type Item = ShResult<Node<'t>>;
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
// Empty token vector or only SOI/EOI tokens, nothing to do
|
||||
if self.tokens.is_empty() || self.tokens.len() == 2 {
|
||||
@@ -451,9 +565,9 @@ impl<'t> Iterator for ParseStream<'t> {
|
||||
}
|
||||
}
|
||||
match self.parse_cmd_list() {
|
||||
NoMatch => None,
|
||||
Error(err) => Some(Error(err)),
|
||||
Match(cmdlist) => Some(Match(cmdlist))
|
||||
Ok(Some(node)) => return Some(Ok(node)),
|
||||
Ok(None) => return None,
|
||||
Err(e) => return Some(Err(e))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user