Implemented logic for loops and if statements
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -28,4 +28,4 @@ template/flake.lock
|
|||||||
ideas.md
|
ideas.md
|
||||||
roadmap.md
|
roadmap.md
|
||||||
README.md
|
README.md
|
||||||
file.*
|
file*
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ pub fn echo(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<(
|
|||||||
let (argv,io_frame) = setup_builtin(argv, job, Some((io_stack,node.redirs)))?;
|
let (argv,io_frame) = setup_builtin(argv, job, Some((io_stack,node.redirs)))?;
|
||||||
|
|
||||||
let stdout = borrow_fd(STDOUT_FILENO);
|
let stdout = borrow_fd(STDOUT_FILENO);
|
||||||
flog!(DEBUG, argv);
|
|
||||||
|
|
||||||
let mut echo_output = argv.into_iter()
|
let mut echo_output = argv.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)
|
||||||
@@ -17,7 +16,6 @@ pub fn echo(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<(
|
|||||||
.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())?;
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ pub const BUILTINS: [&str;9] = [
|
|||||||
/// # Parameters
|
/// # Parameters
|
||||||
/// * argv - The vector of raw argument tokens
|
/// * argv - The vector of raw argument tokens
|
||||||
/// * job - A mutable reference to a `JobBldr`
|
/// * 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`
|
/// * io_mode - An optional 2-tuple consisting of a mutable reference to an `IoStack` and a vector of `Redirs`
|
||||||
///
|
///
|
||||||
/// # Behavior
|
/// # Behavior
|
||||||
/// * Cleans, expands, and word splits the arg vector
|
/// * Cleans, expands, and word splits the arg vector
|
||||||
@@ -47,7 +47,7 @@ pub const BUILTINS: [&str;9] = [
|
|||||||
pub fn setup_builtin<'t>(
|
pub fn setup_builtin<'t>(
|
||||||
argv: Vec<Tk<'t>>,
|
argv: Vec<Tk<'t>>,
|
||||||
job: &'t mut JobBldr,
|
job: &'t mut JobBldr,
|
||||||
io_info: Option<(&mut IoStack,Vec<Redir>)>,
|
io_mode: Option<(&mut IoStack,Vec<Redir>)>,
|
||||||
) -> ShResult<(Vec<(String,Span<'t>)>, Option<IoFrame>)> {
|
) -> ShResult<(Vec<(String,Span<'t>)>, Option<IoFrame>)> {
|
||||||
let mut argv: Vec<(String,Span)> = prepare_argv(argv);
|
let mut argv: Vec<(String,Span)> = prepare_argv(argv);
|
||||||
|
|
||||||
@@ -61,7 +61,7 @@ pub fn setup_builtin<'t>(
|
|||||||
let child = ChildProc::new(Pid::this(), Some(&cmd_name), Some(child_pgid))?;
|
let child = ChildProc::new(Pid::this(), Some(&cmd_name), Some(child_pgid))?;
|
||||||
job.push_child(child);
|
job.push_child(child);
|
||||||
|
|
||||||
let io_frame = if let Some((io_stack,redirs)) = io_info {
|
let io_frame = if let Some((io_stack,redirs)) = io_mode {
|
||||||
io_stack.append_to_frame(redirs);
|
io_stack.append_to_frame(redirs);
|
||||||
let mut io_frame = io_stack.pop_frame();
|
let mut io_frame = io_stack.pop_frame();
|
||||||
io_frame.redirect()?;
|
io_frame.redirect()?;
|
||||||
|
|||||||
10
src/fern.rs
10
src/fern.rs
@@ -47,6 +47,7 @@ fn set_termios() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn exec_input(input: &str) -> ShResult<()> {
|
pub fn exec_input(input: &str) -> ShResult<()> {
|
||||||
|
let parse_start = Instant::now();
|
||||||
let mut tokens = vec![];
|
let mut tokens = vec![];
|
||||||
for token in LexStream::new(&input, LexFlags::empty()) {
|
for token in LexStream::new(&input, LexFlags::empty()) {
|
||||||
tokens.push(token?);
|
tokens.push(token?);
|
||||||
@@ -56,9 +57,14 @@ pub fn exec_input(input: &str) -> ShResult<()> {
|
|||||||
for result in ParseStream::new(tokens) {
|
for result in ParseStream::new(tokens) {
|
||||||
nodes.push(result?);
|
nodes.push(result?);
|
||||||
}
|
}
|
||||||
|
flog!(INFO, "parse duration: {:?}", parse_start.elapsed());
|
||||||
|
|
||||||
|
let exec_start = Instant::now();
|
||||||
let mut dispatcher = Dispatcher::new(nodes);
|
let mut dispatcher = Dispatcher::new(nodes);
|
||||||
dispatcher.begin_dispatch()?;
|
dispatcher.begin_dispatch()?;
|
||||||
|
flog!(INFO, "cmd duration: {:?}", exec_start.elapsed());
|
||||||
|
|
||||||
|
flog!(INFO, "total duration: {:?}", parse_start.elapsed());
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,13 +72,13 @@ fn main() {
|
|||||||
save_termios();
|
save_termios();
|
||||||
set_termios();
|
set_termios();
|
||||||
sig_setup();
|
sig_setup();
|
||||||
|
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let input = prompt::read_line().unwrap();
|
let input = prompt::read_line().unwrap();
|
||||||
let start = Instant::now();
|
|
||||||
|
|
||||||
if let Err(e) = exec_input(&input) {
|
if let Err(e) = exec_input(&input) {
|
||||||
eprintln!("{e}");
|
eprintln!("{e}");
|
||||||
}
|
}
|
||||||
flog!(INFO, "cmd duration: {:?}", start.elapsed());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
20
src/jobs.rs
20
src/jobs.rs
@@ -318,6 +318,7 @@ impl JobTab {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct JobBldr {
|
pub struct JobBldr {
|
||||||
table_id: Option<usize>,
|
table_id: Option<usize>,
|
||||||
pgid: Option<Pid>,
|
pgid: Option<Pid>,
|
||||||
@@ -373,6 +374,25 @@ impl JobBldr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A wrapper around Vec<JobBldr> with some job-specific methods
|
||||||
|
pub struct JobStack(Vec<JobBldr>);
|
||||||
|
|
||||||
|
impl JobStack {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self(vec![])
|
||||||
|
}
|
||||||
|
pub fn new_job(&mut self) {
|
||||||
|
self.0.push(JobBldr::new())
|
||||||
|
}
|
||||||
|
pub fn curr_job_mut(&mut self) -> Option<&mut JobBldr> {
|
||||||
|
self.0.last_mut()
|
||||||
|
}
|
||||||
|
pub fn finalize_job(&mut self) -> Option<Job> {
|
||||||
|
let job = self.0.pop().map(|bldr| bldr.build());
|
||||||
|
job
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug,Clone)]
|
#[derive(Debug,Clone)]
|
||||||
pub struct Job {
|
pub struct Job {
|
||||||
table_id: Option<usize>,
|
table_id: Option<usize>,
|
||||||
|
|||||||
@@ -44,6 +44,12 @@ impl<'s> ShErr {
|
|||||||
let span = span.into();
|
let span = span.into();
|
||||||
Self::Full { kind, msg, span }
|
Self::Full { kind, msg, span }
|
||||||
}
|
}
|
||||||
|
pub fn kind(&self) -> &ShErrKind {
|
||||||
|
match self {
|
||||||
|
ShErr::Simple { kind, msg: _ } |
|
||||||
|
ShErr::Full { kind, msg: _, span: _ } => kind
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for ShErr {
|
impl Display for ShErr {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
|
|
||||||
use crate::parse::lex::{Span, Tk};
|
use crate::parse::lex::{Span, Tk};
|
||||||
|
use crate::parse::{Redir, RedirType};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
pub trait VecDequeExt<T> {
|
pub trait VecDequeExt<T> {
|
||||||
@@ -12,6 +13,13 @@ pub trait TkVecUtils<Tk> {
|
|||||||
fn debug_tokens(&self);
|
fn debug_tokens(&self);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait RedirVecUtils<Redir> {
|
||||||
|
/// Splits the vector of redirections into two vectors
|
||||||
|
///
|
||||||
|
/// One vector contains input redirs, the other contains output redirs
|
||||||
|
fn split_by_channel(self) -> (Vec<Redir>,Vec<Redir>);
|
||||||
|
}
|
||||||
|
|
||||||
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>>()
|
||||||
@@ -41,3 +49,25 @@ impl<'t> TkVecUtils<Tk<'t>> for Vec<Tk<'t>> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl RedirVecUtils<Redir> for Vec<Redir> {
|
||||||
|
fn split_by_channel(self) -> (Vec<Redir>,Vec<Redir>) {
|
||||||
|
let mut input = vec![];
|
||||||
|
let mut output = vec![];
|
||||||
|
for redir in self {
|
||||||
|
match redir.class {
|
||||||
|
RedirType::Input => input.push(redir),
|
||||||
|
RedirType::Pipe => {
|
||||||
|
match redir.io_mode.tgt_fd() {
|
||||||
|
STDIN_FILENO => input.push(redir),
|
||||||
|
STDOUT_FILENO |
|
||||||
|
STDERR_FILENO => output.push(redir),
|
||||||
|
_ => unreachable!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => output.push(redir)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(input,output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
use std::collections::VecDeque;
|
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}, libsh::error::ShResult, prelude::*, procio::{IoFrame, IoPipe, 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}, jobs::{dispatch_job, ChildProc, Job, JobBldr, JobStack}, libsh::{error::{ShErrKind, ShResult}, utils::RedirVecUtils}, prelude::*, procio::{IoFrame, IoMode, IoStack}, state::{self, write_vars}};
|
||||||
|
|
||||||
use super::{lex::{Span, Tk, TkFlags}, AssignKind, ConjunctNode, ConjunctOp, NdFlags, NdRule, Node, Redir, RedirType};
|
use super::{lex::{Span, Tk, TkFlags}, AssignKind, CondNode, ConjunctNode, ConjunctOp, LoopKind, NdFlags, NdRule, Node, Redir, RedirType};
|
||||||
|
|
||||||
pub enum AssignBehavior {
|
pub enum AssignBehavior {
|
||||||
Export,
|
Export,
|
||||||
@@ -40,13 +40,13 @@ impl ExecArgs {
|
|||||||
pub struct Dispatcher<'t> {
|
pub struct Dispatcher<'t> {
|
||||||
nodes: VecDeque<Node<'t>>,
|
nodes: VecDeque<Node<'t>>,
|
||||||
pub io_stack: IoStack,
|
pub io_stack: IoStack,
|
||||||
pub curr_job: Option<JobBldr>
|
pub job_stack: JobStack
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'t> Dispatcher<'t> {
|
impl<'t> Dispatcher<'t> {
|
||||||
pub fn new(nodes: Vec<Node<'t>>) -> Self {
|
pub fn new(nodes: Vec<Node<'t>>) -> Self {
|
||||||
let nodes = VecDeque::from(nodes);
|
let nodes = VecDeque::from(nodes);
|
||||||
Self { nodes, io_stack: IoStack::new(), curr_job: None }
|
Self { nodes, io_stack: IoStack::new(), job_stack: JobStack::new() }
|
||||||
}
|
}
|
||||||
pub fn begin_dispatch(&mut self) -> ShResult<()> {
|
pub fn begin_dispatch(&mut self) -> ShResult<()> {
|
||||||
flog!(TRACE, "beginning dispatch");
|
flog!(TRACE, "beginning dispatch");
|
||||||
@@ -57,8 +57,10 @@ impl<'t> Dispatcher<'t> {
|
|||||||
}
|
}
|
||||||
pub fn dispatch_node(&mut self, node: Node<'t>) -> ShResult<()> {
|
pub fn dispatch_node(&mut self, node: Node<'t>) -> ShResult<()> {
|
||||||
match node.class {
|
match node.class {
|
||||||
NdRule::CmdList {..} => self.exec_conjunction(node)?,
|
NdRule::Conjunction {..} => self.exec_conjunction(node)?,
|
||||||
NdRule::Pipeline {..} => self.exec_pipeline(node)?,
|
NdRule::Pipeline {..} => self.exec_pipeline(node)?,
|
||||||
|
NdRule::IfNode {..} => self.exec_if(node)?,
|
||||||
|
NdRule::LoopNode {..} => self.exec_loop(node)?,
|
||||||
NdRule::Command {..} => self.dispatch_cmd(node)?,
|
NdRule::Command {..} => self.dispatch_cmd(node)?,
|
||||||
_ => unreachable!()
|
_ => unreachable!()
|
||||||
}
|
}
|
||||||
@@ -75,7 +77,7 @@ impl<'t> Dispatcher<'t> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn exec_conjunction(&mut self, conjunction: Node<'t>) -> ShResult<()> {
|
pub fn exec_conjunction(&mut self, conjunction: Node<'t>) -> ShResult<()> {
|
||||||
let NdRule::CmdList { elements } = conjunction.class else {
|
let NdRule::Conjunction { elements } = conjunction.class else {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -93,11 +95,96 @@ impl<'t> Dispatcher<'t> {
|
|||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
pub fn exec_loop(&mut self, loop_stmt: Node<'t>) -> ShResult<()> {
|
||||||
|
let NdRule::LoopNode { kind, cond_node } = loop_stmt.class else {
|
||||||
|
unreachable!();
|
||||||
|
};
|
||||||
|
let keep_going = |kind: LoopKind, status: i32| -> bool {
|
||||||
|
match kind {
|
||||||
|
LoopKind::While => status == 0,
|
||||||
|
LoopKind::Until => status != 0
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let io_frame = self.io_stack.pop_frame();
|
||||||
|
let (mut cond_frame,mut body_frame) = io_frame.split_frame();
|
||||||
|
let (in_redirs,out_redirs) = loop_stmt.redirs.split_by_channel();
|
||||||
|
cond_frame.extend(in_redirs);
|
||||||
|
body_frame.extend(out_redirs);
|
||||||
|
|
||||||
|
let CondNode { cond, body } = cond_node;
|
||||||
|
loop {
|
||||||
|
self.io_stack.push(cond_frame.clone());
|
||||||
|
|
||||||
|
if let Err(e) = self.dispatch_node(*cond.clone()) {
|
||||||
|
state::set_status(1);
|
||||||
|
return Err(e.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
let status = state::get_status();
|
||||||
|
if keep_going(kind,status) {
|
||||||
|
self.io_stack.push(body_frame.clone());
|
||||||
|
for node in &body {
|
||||||
|
if let Err(e) = self.dispatch_node(node.clone()) {
|
||||||
|
match e.kind() {
|
||||||
|
ShErrKind::LoopBreak => break,
|
||||||
|
ShErrKind::LoopContinue => continue,
|
||||||
|
_ => return Err(e.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
pub fn exec_if(&mut self, if_stmt: Node<'t>) -> ShResult<()> {
|
||||||
|
let NdRule::IfNode { cond_nodes, else_block } = if_stmt.class else {
|
||||||
|
unreachable!();
|
||||||
|
};
|
||||||
|
// Pop the current frame and split it
|
||||||
|
let io_frame = self.io_stack.pop_frame();
|
||||||
|
let (mut cond_frame,mut body_frame) = io_frame.split_frame();
|
||||||
|
let (in_redirs,out_redirs) = if_stmt.redirs.split_by_channel();
|
||||||
|
cond_frame.extend(in_redirs); // Condition gets input redirs
|
||||||
|
body_frame.extend(out_redirs); // Body gets output redirs
|
||||||
|
|
||||||
|
for node in cond_nodes {
|
||||||
|
let CondNode { cond, body } = node;
|
||||||
|
self.io_stack.push(cond_frame.clone());
|
||||||
|
|
||||||
|
if let Err(e) = self.dispatch_node(*cond) {
|
||||||
|
state::set_status(1);
|
||||||
|
return Err(e.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
match state::get_status() {
|
||||||
|
0 => {
|
||||||
|
for body_node in body {
|
||||||
|
self.io_stack.push(body_frame.clone());
|
||||||
|
self.dispatch_node(body_node)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !else_block.is_empty() {
|
||||||
|
for node in else_block {
|
||||||
|
self.io_stack.push(body_frame.clone());
|
||||||
|
self.dispatch_node(node)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
pub fn exec_pipeline(&mut self, pipeline: Node<'t>) -> ShResult<()> {
|
pub fn exec_pipeline(&mut self, pipeline: Node<'t>) -> ShResult<()> {
|
||||||
let NdRule::Pipeline { cmds, pipe_err } = pipeline.class else {
|
let NdRule::Pipeline { cmds, pipe_err } = pipeline.class else {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
self.curr_job = Some(JobBldr::new());
|
self.job_stack.new_job();
|
||||||
// Zip the commands and their respective pipes into an iterator
|
// Zip the commands and their respective pipes into an iterator
|
||||||
let pipes_and_cmds = get_pipe_stack(cmds.len())
|
let pipes_and_cmds = get_pipe_stack(cmds.len())
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@@ -112,21 +199,18 @@ impl<'t> Dispatcher<'t> {
|
|||||||
}
|
}
|
||||||
self.dispatch_node(cmd)?;
|
self.dispatch_node(cmd)?;
|
||||||
}
|
}
|
||||||
let job = self.finalize_job();
|
let job = self.job_stack.finalize_job().unwrap();
|
||||||
let is_bg = pipeline.flags.contains(NdFlags::BACKGROUND);
|
let is_bg = pipeline.flags.contains(NdFlags::BACKGROUND);
|
||||||
dispatch_job(job, is_bg)?;
|
dispatch_job(job, is_bg)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
pub fn finalize_job(&mut self) -> Job {
|
|
||||||
self.curr_job.take().unwrap().build()
|
|
||||||
}
|
|
||||||
pub fn exec_builtin(&mut self, mut cmd: Node<'t>) -> ShResult<()> {
|
pub fn exec_builtin(&mut self, mut cmd: Node<'t>) -> ShResult<()> {
|
||||||
let NdRule::Command { ref mut assignments, argv } = &mut cmd.class else {
|
let NdRule::Command { ref mut assignments, argv } = &mut cmd.class else {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
let env_vars_to_unset = self.set_assignments(mem::take(assignments), AssignBehavior::Export);
|
let env_vars_to_unset = self.set_assignments(mem::take(assignments), AssignBehavior::Export);
|
||||||
let cmd_raw = cmd.get_command().unwrap();
|
let cmd_raw = cmd.get_command().unwrap();
|
||||||
let curr_job_mut = self.curr_job.as_mut().unwrap();
|
let curr_job_mut = self.job_stack.curr_job_mut().unwrap();
|
||||||
let io_stack_mut = &mut self.io_stack;
|
let io_stack_mut = &mut self.io_stack;
|
||||||
|
|
||||||
flog!(TRACE, "doing builtin");
|
flog!(TRACE, "doing builtin");
|
||||||
@@ -178,7 +262,7 @@ impl<'t> Dispatcher<'t> {
|
|||||||
run_fork(
|
run_fork(
|
||||||
io_frame,
|
io_frame,
|
||||||
Some(exec_args),
|
Some(exec_args),
|
||||||
self.curr_job.as_mut().unwrap(),
|
self.job_stack.curr_job_mut().unwrap(),
|
||||||
def_child_action,
|
def_child_action,
|
||||||
def_parent_action
|
def_parent_action
|
||||||
)?;
|
)?;
|
||||||
@@ -318,9 +402,9 @@ pub fn get_pipe_stack(num_cmds: usize) -> Vec<(Option<Redir>,Option<Redir>)> {
|
|||||||
if i == num_cmds - 1 {
|
if i == num_cmds - 1 {
|
||||||
stack.push((prev_read.take(), None));
|
stack.push((prev_read.take(), None));
|
||||||
} else {
|
} else {
|
||||||
let (rpipe,wpipe) = IoPipe::get_pipes();
|
let (rpipe,wpipe) = IoMode::get_pipes();
|
||||||
let r_redir = Redir::new(Box::new(rpipe), RedirType::Input);
|
let r_redir = Redir::new(rpipe, RedirType::Input);
|
||||||
let w_redir = Redir::new(Box::new(wpipe), RedirType::Output);
|
let w_redir = Redir::new(wpipe, RedirType::Output);
|
||||||
|
|
||||||
// Push (prev_read, Some(w_redir)) and set prev_read to r_redir
|
// Push (prev_read, Some(w_redir)) and set prev_read to r_redir
|
||||||
stack.push((prev_read.take(), Some(w_redir)));
|
stack.push((prev_read.take(), Some(w_redir)));
|
||||||
|
|||||||
182
src/parse/mod.rs
182
src/parse/mod.rs
@@ -4,13 +4,13 @@ use bitflags::bitflags;
|
|||||||
use fmt::Display;
|
use fmt::Display;
|
||||||
use lex::{Span, Tk, TkFlags, TkRule};
|
use lex::{Span, Tk, TkFlags, TkRule};
|
||||||
|
|
||||||
use crate::{libsh::{error::{ShErr, ShErrKind, ShResult}, utils::TkVecUtils}, prelude::*, procio::{IoFd, IoFile, IoInfo}};
|
use crate::{libsh::{error::{ShErr, ShErrKind, ShResult}, utils::TkVecUtils}, prelude::*, procio::IoMode};
|
||||||
|
|
||||||
pub mod lex;
|
pub mod lex;
|
||||||
pub mod execute;
|
pub mod execute;
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Clone,Debug)]
|
||||||
pub struct Node<'t> {
|
pub struct Node<'t> {
|
||||||
pub class: NdRule<'t>,
|
pub class: NdRule<'t>,
|
||||||
pub flags: NdFlags,
|
pub flags: NdFlags,
|
||||||
@@ -39,27 +39,27 @@ impl<'t> Node<'t> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
#[derive(Debug)]
|
#[derive(Clone,Copy,Debug)]
|
||||||
pub struct NdFlags: u32 {
|
pub struct NdFlags: u32 {
|
||||||
const BACKGROUND = 0b000001;
|
const BACKGROUND = 0b000001;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Clone,Debug)]
|
||||||
pub struct Redir {
|
pub struct Redir {
|
||||||
pub io_info: Box<dyn IoInfo>,
|
pub io_mode: IoMode,
|
||||||
pub class: RedirType
|
pub class: RedirType
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Redir {
|
impl Redir {
|
||||||
pub fn new(io_info: Box<dyn IoInfo>, class: RedirType) -> Self {
|
pub fn new(io_mode: IoMode, class: RedirType) -> Self {
|
||||||
Self { io_info, class }
|
Self { io_mode, class }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default,Debug)]
|
#[derive(Default,Debug)]
|
||||||
pub struct RedirBldr {
|
pub struct RedirBldr {
|
||||||
pub io_info: Option<Box<dyn IoInfo>>,
|
pub io_mode: Option<IoMode>,
|
||||||
pub class: Option<RedirType>,
|
pub class: Option<RedirType>,
|
||||||
pub tgt_fd: Option<RawFd>,
|
pub tgt_fd: Option<RawFd>,
|
||||||
}
|
}
|
||||||
@@ -68,20 +68,20 @@ impl RedirBldr {
|
|||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Default::default()
|
Default::default()
|
||||||
}
|
}
|
||||||
pub fn with_io_info(self, io_info: Box<dyn IoInfo>) -> Self {
|
pub fn with_io_mode(self, io_mode: IoMode) -> Self {
|
||||||
let Self { io_info: _, class, tgt_fd } = self;
|
let Self { io_mode: _, class, tgt_fd } = self;
|
||||||
Self { io_info: Some(io_info), class, tgt_fd }
|
Self { io_mode: Some(io_mode), class, tgt_fd }
|
||||||
}
|
}
|
||||||
pub fn with_class(self, class: RedirType) -> Self {
|
pub fn with_class(self, class: RedirType) -> Self {
|
||||||
let Self { io_info, class: _, tgt_fd } = self;
|
let Self { io_mode, class: _, tgt_fd } = self;
|
||||||
Self { io_info, class: Some(class), tgt_fd }
|
Self { io_mode, class: Some(class), tgt_fd }
|
||||||
}
|
}
|
||||||
pub fn with_tgt(self, tgt_fd: RawFd) -> Self {
|
pub fn with_tgt(self, tgt_fd: RawFd) -> Self {
|
||||||
let Self { io_info, class, tgt_fd: _ } = self;
|
let Self { io_mode, class, tgt_fd: _ } = self;
|
||||||
Self { io_info, class, tgt_fd: Some(tgt_fd) }
|
Self { io_mode, class, tgt_fd: Some(tgt_fd) }
|
||||||
}
|
}
|
||||||
pub fn build(self) -> Redir {
|
pub fn build(self) -> Redir {
|
||||||
Redir::new(self.io_info.unwrap(), self.class.unwrap())
|
Redir::new(self.io_mode.unwrap(), self.class.unwrap())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,8 +155,8 @@ impl FromStr for RedirBldr {
|
|||||||
});
|
});
|
||||||
redir = redir.with_tgt(tgt_fd);
|
redir = redir.with_tgt(tgt_fd);
|
||||||
if let Ok(src_fd) = src_fd.parse::<i32>() {
|
if let Ok(src_fd) = src_fd.parse::<i32>() {
|
||||||
let io_info = IoFd::new(tgt_fd, src_fd);
|
let io_mode = IoMode::fd(tgt_fd, src_fd);
|
||||||
redir = redir.with_io_info(Box::new(io_info));
|
redir = redir.with_io_mode(io_mode);
|
||||||
}
|
}
|
||||||
Ok(redir)
|
Ok(redir)
|
||||||
}
|
}
|
||||||
@@ -174,13 +174,13 @@ pub enum RedirType {
|
|||||||
HereString, // <<<
|
HereString, // <<<
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Clone,Debug)]
|
||||||
pub struct CondNode<'t> {
|
pub struct CondNode<'t> {
|
||||||
pub cond: Box<Node<'t>>,
|
pub cond: Box<Node<'t>>,
|
||||||
pub body: Vec<Node<'t>>
|
pub body: Vec<Node<'t>>
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Clone,Debug)]
|
||||||
pub struct CaseNode<'t> {
|
pub struct CaseNode<'t> {
|
||||||
pub pattern: Tk<'t>,
|
pub pattern: Tk<'t>,
|
||||||
pub body: Vec<Node<'t>>
|
pub body: Vec<Node<'t>>
|
||||||
@@ -193,13 +193,13 @@ pub enum ConjunctOp {
|
|||||||
Null
|
Null
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Clone,Debug)]
|
||||||
pub struct ConjunctNode<'t> {
|
pub struct ConjunctNode<'t> {
|
||||||
pub cmd: Box<Node<'t>>,
|
pub cmd: Box<Node<'t>>,
|
||||||
pub operator: ConjunctOp
|
pub operator: ConjunctOp
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Clone,Copy,Debug)]
|
||||||
pub enum LoopKind {
|
pub enum LoopKind {
|
||||||
While,
|
While,
|
||||||
Until
|
Until
|
||||||
@@ -225,7 +225,7 @@ impl Display for LoopKind {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Clone,Debug)]
|
||||||
pub enum AssignKind {
|
pub enum AssignKind {
|
||||||
Eq,
|
Eq,
|
||||||
PlusEq,
|
PlusEq,
|
||||||
@@ -234,7 +234,7 @@ pub enum AssignKind {
|
|||||||
DivEq,
|
DivEq,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Clone,Debug)]
|
||||||
pub enum NdRule<'t> {
|
pub enum NdRule<'t> {
|
||||||
IfNode { cond_nodes: Vec<CondNode<'t>>, else_block: Vec<Node<'t>> },
|
IfNode { cond_nodes: Vec<CondNode<'t>>, else_block: Vec<Node<'t>> },
|
||||||
LoopNode { kind: LoopKind, cond_node: CondNode<'t> },
|
LoopNode { kind: LoopKind, cond_node: CondNode<'t> },
|
||||||
@@ -242,7 +242,7 @@ pub enum NdRule<'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>> },
|
||||||
Pipeline { cmds: Vec<Node<'t>>, pipe_err: bool },
|
Pipeline { cmds: Vec<Node<'t>>, pipe_err: bool },
|
||||||
CmdList { elements: Vec<ConjunctNode<'t>> },
|
Conjunction { elements: Vec<ConjunctNode<'t>> },
|
||||||
Assignment { kind: AssignKind, var: Tk<'t>, val: Tk<'t> },
|
Assignment { kind: AssignKind, var: Tk<'t>, val: Tk<'t> },
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -294,6 +294,28 @@ impl<'t> ParseStream<'t> {
|
|||||||
node_tks.push(self.next_tk().unwrap());
|
node_tks.push(self.next_tk().unwrap());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
fn assert_separator(&mut self, node_tks: &mut Vec<Tk<'t>>) -> ShResult<()> {
|
||||||
|
let next_class = self.next_tk_class();
|
||||||
|
match next_class {
|
||||||
|
TkRule::EOI |
|
||||||
|
TkRule::Or |
|
||||||
|
TkRule::Bg |
|
||||||
|
TkRule::And |
|
||||||
|
TkRule::Pipe => Ok(()),
|
||||||
|
|
||||||
|
TkRule::Sep => {
|
||||||
|
if let Some(tk) = self.next_tk() {
|
||||||
|
node_tks.push(tk);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
Err(
|
||||||
|
ShErr::simple(ShErrKind::ParseErr, "Expected a semicolon or newline here")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
fn next_tk_is_some(&self) -> bool {
|
fn next_tk_is_some(&self) -> bool {
|
||||||
self.tokens.first().is_some_and(|tk| tk.class != TkRule::EOI)
|
self.tokens.first().is_some_and(|tk| tk.class != TkRule::EOI)
|
||||||
}
|
}
|
||||||
@@ -302,6 +324,11 @@ impl<'t> ParseStream<'t> {
|
|||||||
tk.flags.contains(TkFlags::KEYWORD) && tk.span.as_str() == kw
|
tk.flags.contains(TkFlags::KEYWORD) && tk.span.as_str() == kw
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
fn check_redir(&self) -> bool {
|
||||||
|
self.tokens.first().is_some_and(|tk| {
|
||||||
|
tk.class == TkRule::Redir
|
||||||
|
})
|
||||||
|
}
|
||||||
/// 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());
|
||||||
@@ -334,7 +361,7 @@ impl<'t> ParseStream<'t> {
|
|||||||
Ok(None)
|
Ok(None)
|
||||||
} else {
|
} else {
|
||||||
Ok(Some(Node {
|
Ok(Some(Node {
|
||||||
class: NdRule::CmdList { elements },
|
class: NdRule::Conjunction { elements },
|
||||||
flags: NdFlags::empty(),
|
flags: NdFlags::empty(),
|
||||||
redirs: vec![],
|
redirs: vec![],
|
||||||
tokens: node_tks
|
tokens: node_tks
|
||||||
@@ -370,6 +397,7 @@ impl<'t> ParseStream<'t> {
|
|||||||
let mut node_tks: Vec<Tk> = vec![];
|
let mut node_tks: Vec<Tk> = vec![];
|
||||||
let mut cond_nodes: Vec<CondNode> = vec![];
|
let mut cond_nodes: Vec<CondNode> = vec![];
|
||||||
let mut else_block: Vec<Node> = vec![];
|
let mut else_block: Vec<Node> = vec![];
|
||||||
|
let mut redirs = vec![];
|
||||||
|
|
||||||
if !self.check_keyword("if") || !self.next_tk_is_some() {
|
if !self.check_keyword("if") || !self.next_tk_is_some() {
|
||||||
return Ok(None)
|
return Ok(None)
|
||||||
@@ -442,12 +470,51 @@ impl<'t> ParseStream<'t> {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
node_tks.push(self.next_tk().unwrap());
|
node_tks.push(self.next_tk().unwrap());
|
||||||
self.catch_separator(&mut node_tks);
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.assert_separator(&mut node_tks)?;
|
||||||
|
|
||||||
let node = Node {
|
let node = Node {
|
||||||
class: NdRule::IfNode { cond_nodes, else_block },
|
class: NdRule::IfNode { cond_nodes, else_block },
|
||||||
flags: NdFlags::empty(),
|
flags: NdFlags::empty(),
|
||||||
redirs: vec![],
|
redirs,
|
||||||
tokens: node_tks
|
tokens: node_tks
|
||||||
};
|
};
|
||||||
Ok(Some(node))
|
Ok(Some(node))
|
||||||
@@ -505,7 +572,7 @@ impl<'t> ParseStream<'t> {
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
node_tks.push(self.next_tk().unwrap());
|
node_tks.push(self.next_tk().unwrap());
|
||||||
self.catch_separator(&mut node_tks);
|
self.assert_separator(&mut node_tks)?;
|
||||||
|
|
||||||
cond_node = CondNode { cond: Box::new(cond), body };
|
cond_node = CondNode { cond: Box::new(cond), body };
|
||||||
let loop_node = Node {
|
let loop_node = Node {
|
||||||
@@ -594,7 +661,7 @@ impl<'t> ParseStream<'t> {
|
|||||||
TkRule::Redir => {
|
TkRule::Redir => {
|
||||||
node_tks.push(tk.clone());
|
node_tks.push(tk.clone());
|
||||||
let redir_bldr = tk.span.as_str().parse::<RedirBldr>().unwrap();
|
let redir_bldr = tk.span.as_str().parse::<RedirBldr>().unwrap();
|
||||||
if redir_bldr.io_info.is_none() {
|
if redir_bldr.io_mode.is_none() {
|
||||||
let path_tk = tk_iter.next();
|
let path_tk = tk_iter.next();
|
||||||
|
|
||||||
if path_tk.is_none_or(|tk| tk.class == TkRule::EOI) {
|
if path_tk.is_none_or(|tk| tk.class == TkRule::EOI) {
|
||||||
@@ -610,29 +677,10 @@ impl<'t> ParseStream<'t> {
|
|||||||
|
|
||||||
let path_tk = path_tk.unwrap();
|
let path_tk = path_tk.unwrap();
|
||||||
node_tks.push(path_tk.clone());
|
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) = (match redir_bldr.class.unwrap() {
|
let Ok(file) = get_redir_file(redir_class, pathbuf) else {
|
||||||
RedirType::Input => {
|
|
||||||
OpenOptions::new()
|
|
||||||
.read(true)
|
|
||||||
.open(Path::new(path_tk.span.as_str()))
|
|
||||||
}
|
|
||||||
RedirType::Output => {
|
|
||||||
OpenOptions::new()
|
|
||||||
.write(true)
|
|
||||||
.create(true)
|
|
||||||
.truncate(true)
|
|
||||||
.open(Path::new(path_tk.span.as_str()))
|
|
||||||
}
|
|
||||||
RedirType::Append => {
|
|
||||||
OpenOptions::new()
|
|
||||||
.write(true)
|
|
||||||
.create(true)
|
|
||||||
.append(true)
|
|
||||||
.open(Path::new(path_tk.span.as_str()))
|
|
||||||
}
|
|
||||||
_ => unreachable!()
|
|
||||||
}) else {
|
|
||||||
self.flags |= ParseFlags::ERROR;
|
self.flags |= ParseFlags::ERROR;
|
||||||
return Err(parse_err_full(
|
return Err(parse_err_full(
|
||||||
"Error opening file for redirection",
|
"Error opening file for redirection",
|
||||||
@@ -640,8 +688,8 @@ impl<'t> ParseStream<'t> {
|
|||||||
));
|
));
|
||||||
};
|
};
|
||||||
|
|
||||||
let io_info = IoFile::new(redir_bldr.tgt_fd.unwrap(), file);
|
let io_mode = IoMode::file(redir_bldr.tgt_fd.unwrap(), file);
|
||||||
let redir_bldr = redir_bldr.with_io_info(Box::new(io_info));
|
let redir_bldr = redir_bldr.with_io_mode(io_mode);
|
||||||
let redir = redir_bldr.build();
|
let redir = redir_bldr.build();
|
||||||
redirs.push(redir);
|
redirs.push(redir);
|
||||||
}
|
}
|
||||||
@@ -797,6 +845,32 @@ fn node_is_punctuated<'t>(tokens: &Vec<Tk>) -> bool {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_redir_file(class: RedirType, path: PathBuf) -> ShResult<File> {
|
||||||
|
let result = match class {
|
||||||
|
RedirType::Input => {
|
||||||
|
OpenOptions::new()
|
||||||
|
.read(true)
|
||||||
|
.open(Path::new(&path))
|
||||||
|
}
|
||||||
|
RedirType::Output => {
|
||||||
|
OpenOptions::new()
|
||||||
|
.write(true)
|
||||||
|
.create(true)
|
||||||
|
.truncate(true)
|
||||||
|
.open(Path::new(&path))
|
||||||
|
}
|
||||||
|
RedirType::Append => {
|
||||||
|
OpenOptions::new()
|
||||||
|
.write(true)
|
||||||
|
.create(true)
|
||||||
|
.append(true)
|
||||||
|
.open(Path::new(&path))
|
||||||
|
}
|
||||||
|
_ => unimplemented!()
|
||||||
|
};
|
||||||
|
Ok(result?)
|
||||||
|
}
|
||||||
|
|
||||||
fn parse_err_full<'t>(reason: &str, blame: &Span<'t>) -> ShErr {
|
fn parse_err_full<'t>(reason: &str, blame: &Span<'t>) -> ShErr {
|
||||||
ShErr::full(
|
ShErr::full(
|
||||||
ShErrKind::ParseErr,
|
ShErrKind::ParseErr,
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ pub use std::path::{ Path, PathBuf };
|
|||||||
pub use std::ffi::{ CStr, CString };
|
pub use std::ffi::{ CStr, CString };
|
||||||
pub use std::process::exit;
|
pub use std::process::exit;
|
||||||
pub use std::time::Instant;
|
pub use std::time::Instant;
|
||||||
|
pub use std::rc::Rc;
|
||||||
pub use std::mem;
|
pub use std::mem;
|
||||||
pub use std::env;
|
pub use std::env;
|
||||||
pub use std::fmt;
|
pub use std::fmt;
|
||||||
|
|||||||
272
src/procio.rs
272
src/procio.rs
@@ -1,188 +1,67 @@
|
|||||||
use std::{fmt::Debug, ops::{Deref, DerefMut}};
|
use std::{fmt::Debug, ops::{Deref, DerefMut}};
|
||||||
|
|
||||||
use crate::{libsh::error::ShResult, parse::{Redir, RedirType}, prelude::*};
|
use crate::{libsh::{error::ShResult, utils::RedirVecUtils}, parse::{Redir, RedirType}, prelude::*};
|
||||||
|
|
||||||
// Credit to fish-shell for many of the implementation ideas present in this module
|
// Credit to fish-shell for many of the implementation ideas present in this module
|
||||||
// https://fishshell.com/
|
// https://fishshell.com/
|
||||||
|
|
||||||
|
#[derive(Clone,Debug)]
|
||||||
pub enum IoMode {
|
pub enum IoMode {
|
||||||
Fd,
|
Fd { tgt_fd: RawFd, src_fd: Rc<OwnedFd> },
|
||||||
File,
|
File { tgt_fd: RawFd, file: Rc<File> },
|
||||||
Pipe,
|
Pipe { tgt_fd: RawFd, pipe: Rc<OwnedFd> },
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait IoInfo: Read {
|
impl IoMode {
|
||||||
fn mode(&self) -> IoMode;
|
pub fn fd(tgt_fd: RawFd, src_fd: RawFd) -> Self {
|
||||||
/// The fildesc that is replaced by src_fd in dup2()
|
let src_fd = unsafe { OwnedFd::from_raw_fd(src_fd).into() };
|
||||||
/// e.g. `dup2(src_fd, tgt_fd)`
|
Self::Fd { tgt_fd, src_fd }
|
||||||
fn tgt_fd(&self) -> RawFd;
|
}
|
||||||
/// The fildesc that replaces tgt_fd in dup2()
|
pub fn file(tgt_fd: RawFd, file: File) -> Self {
|
||||||
/// e.g. `dup2(src_fd, tgt_fd)`
|
let file = file.into();
|
||||||
fn src_fd(&self) -> RawFd;
|
Self::File { tgt_fd, file }
|
||||||
fn print(&self) -> String;
|
}
|
||||||
fn close(&mut self) -> ShResult<()>;
|
pub fn pipe(tgt_fd: RawFd, pipe: OwnedFd) -> Self {
|
||||||
}
|
let pipe = pipe.into();
|
||||||
|
Self::Pipe { tgt_fd, pipe }
|
||||||
macro_rules! read_impl {
|
}
|
||||||
($type:path) => {
|
pub fn tgt_fd(&self) -> RawFd {
|
||||||
impl Read for $type {
|
match self {
|
||||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
IoMode::Fd { tgt_fd, src_fd: _ } |
|
||||||
let src_fd = self.src_fd();
|
IoMode::File { tgt_fd, file: _ } |
|
||||||
|
IoMode::Pipe { tgt_fd, pipe: _ } => *tgt_fd
|
||||||
Ok(read(src_fd, buf)?)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
}
|
|
||||||
read_impl!(IoPipe);
|
|
||||||
read_impl!(IoFile);
|
|
||||||
read_impl!(IoFd);
|
|
||||||
|
|
||||||
|
|
||||||
// TODO: implement this
|
|
||||||
impl Debug for Box<dyn IoInfo> {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(f,"{}",self.print())
|
|
||||||
}
|
}
|
||||||
}
|
pub fn src_fd(&self) -> RawFd {
|
||||||
|
match self {
|
||||||
/// A redirection to a raw fildesc
|
IoMode::Fd { tgt_fd: _, src_fd } => src_fd.as_raw_fd(),
|
||||||
/// e.g. `2>&1`
|
IoMode::File { tgt_fd: _, file } => file.as_raw_fd(),
|
||||||
#[derive(Debug)]
|
IoMode::Pipe { tgt_fd: _, pipe } => pipe.as_raw_fd()
|
||||||
pub struct IoFd {
|
|
||||||
tgt_fd: RawFd,
|
|
||||||
src_fd: RawFd
|
|
||||||
}
|
|
||||||
|
|
||||||
impl IoFd {
|
|
||||||
pub fn new(tgt_fd: RawFd, src_fd: RawFd) -> Self {
|
|
||||||
Self { tgt_fd, src_fd }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl IoInfo for IoFd {
|
|
||||||
fn mode(&self) -> IoMode {
|
|
||||||
IoMode::Fd
|
|
||||||
}
|
|
||||||
fn tgt_fd(&self) -> RawFd {
|
|
||||||
self.tgt_fd
|
|
||||||
}
|
|
||||||
fn src_fd(&self) -> RawFd {
|
|
||||||
self.src_fd
|
|
||||||
}
|
|
||||||
fn close(&mut self) -> ShResult<()> {
|
|
||||||
if self.src_fd == -1 {
|
|
||||||
return Ok(())
|
|
||||||
}
|
}
|
||||||
close(self.src_fd)?;
|
|
||||||
self.src_fd = -1;
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
fn print(&self) -> String {
|
pub fn get_pipes() -> (Self,Self) {
|
||||||
format!("{:?}",self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A redirection to a file
|
|
||||||
/// e.g. `> file.txt`
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct IoFile {
|
|
||||||
tgt_fd: RawFd,
|
|
||||||
file: File
|
|
||||||
}
|
|
||||||
|
|
||||||
impl IoFile {
|
|
||||||
pub fn new(tgt_fd: RawFd, file: File) -> Self {
|
|
||||||
Self { tgt_fd, file }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl IoInfo for IoFile {
|
|
||||||
fn mode(&self) -> IoMode {
|
|
||||||
IoMode::File
|
|
||||||
}
|
|
||||||
fn tgt_fd(&self) -> RawFd {
|
|
||||||
self.tgt_fd
|
|
||||||
}
|
|
||||||
fn src_fd(&self) -> RawFd {
|
|
||||||
self.file.as_raw_fd()
|
|
||||||
}
|
|
||||||
fn close(&mut self) -> ShResult<()> {
|
|
||||||
// Closes on it's own when it's dropped
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
fn print(&self) -> String {
|
|
||||||
format!("{:?}",self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A redirection to a pipe
|
|
||||||
/// e.g. `echo foo | sed s/foo/bar/`
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct IoPipe {
|
|
||||||
tgt_fd: RawFd,
|
|
||||||
pipe_fd: OwnedFd
|
|
||||||
}
|
|
||||||
|
|
||||||
impl IoPipe {
|
|
||||||
pub fn new(tgt_fd: RawFd, pipe_fd: OwnedFd) -> Self {
|
|
||||||
Self { tgt_fd, pipe_fd }
|
|
||||||
}
|
|
||||||
pub fn get_pipes() -> (Self, Self) {
|
|
||||||
let (rpipe,wpipe) = pipe().unwrap();
|
let (rpipe,wpipe) = pipe().unwrap();
|
||||||
let r_iopipe = Self::new(STDIN_FILENO, rpipe);
|
(
|
||||||
let w_iopipe = Self::new(STDOUT_FILENO, wpipe);
|
Self::Pipe { tgt_fd: STDIN_FILENO, pipe: rpipe.into() },
|
||||||
|
Self::Pipe { tgt_fd: STDOUT_FILENO, pipe: wpipe.into() }
|
||||||
(r_iopipe,w_iopipe)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IoInfo for IoPipe {
|
impl Read for IoMode {
|
||||||
fn mode(&self) -> IoMode {
|
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||||
IoMode::Pipe
|
let src_fd = self.src_fd();
|
||||||
}
|
Ok(read(src_fd, buf)?)
|
||||||
fn tgt_fd(&self) -> RawFd {
|
|
||||||
self.tgt_fd
|
|
||||||
}
|
|
||||||
fn src_fd(&self) -> RawFd {
|
|
||||||
self.pipe_fd.as_raw_fd()
|
|
||||||
}
|
|
||||||
fn close(&mut self) -> ShResult<()> {
|
|
||||||
// Closes on it's own
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
fn print(&self) -> String {
|
|
||||||
format!("{:?}",self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct FdWriter {
|
|
||||||
tgt: OwnedFd
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FdWriter {
|
|
||||||
pub fn new(fd: i32) -> Self {
|
|
||||||
let tgt = unsafe { OwnedFd::from_raw_fd(fd) };
|
|
||||||
Self { tgt }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Write for FdWriter {
|
|
||||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
|
||||||
Ok(write(&self.tgt, buf)?)
|
|
||||||
}
|
|
||||||
fn flush(&mut self) -> io::Result<()> {
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A struct wrapping three fildescs representing `stdin`, `stdout`, and `stderr` respectively
|
/// A struct wrapping three fildescs representing `stdin`, `stdout`, and `stderr` respectively
|
||||||
#[derive(Debug)]
|
#[derive(Debug,Clone)]
|
||||||
pub struct IoGroup(OwnedFd,OwnedFd,OwnedFd);
|
pub struct IoGroup(RawFd,RawFd,RawFd);
|
||||||
|
|
||||||
/// A single stack frame used with the IoStack
|
/// A single stack frame used with the IoStack
|
||||||
/// Each stack frame represents the redirections of a single command
|
/// Each stack frame represents the redirections of a single command
|
||||||
#[derive(Default,Debug)]
|
#[derive(Default,Clone,Debug)]
|
||||||
pub struct IoFrame {
|
pub struct IoFrame {
|
||||||
redirs: Vec<Redir>,
|
redirs: Vec<Redir>,
|
||||||
saved_io: Option<IoGroup>,
|
saved_io: Option<IoGroup>,
|
||||||
@@ -196,65 +75,44 @@ impl<'e> IoFrame {
|
|||||||
Self { redirs, saved_io: None }
|
Self { redirs, saved_io: None }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This method returns a 2-tuple of `IoFrames`.
|
/// Splits the frame into two frames
|
||||||
/// This is to be used in the case of shell structures such as `if-then` and `while-do`.
|
|
||||||
/// # Params
|
|
||||||
/// * redirs: a vector of redirections
|
|
||||||
///
|
///
|
||||||
/// # Returns
|
/// One frame contains input redirections, the other contains output redirections
|
||||||
/// * An `IoFrame` containing all of the redirections which target stdin
|
/// This is used in shell structures to route redirections either *to* the condition, or *from* the body
|
||||||
/// * An `IoFrame` containing all of the redirections which target stdout/stderr
|
/// The first field of the tuple contains input redirections (used for the condition)
|
||||||
///
|
/// The second field contains output redirections (used for the body)
|
||||||
/// # Purpose
|
pub fn split_frame(self) -> (Self,Self) {
|
||||||
/// In the case of something like `if cat; then echo foo; fi < input.txt > output.txt`
|
let Self { redirs, saved_io: _ } = self;
|
||||||
/// This will cleanly separate the redirections such that `cat` can receive the input from input.txt
|
let (input_redirs,output_redirs) = redirs.split_by_channel();
|
||||||
/// and `echo foo` can redirect it's output to output.txt
|
(
|
||||||
pub fn cond_and_body(redirs: Vec<Redir>) -> (Self, Self) {
|
Self::from_redirs(input_redirs),
|
||||||
let mut output_redirs = vec![];
|
Self::from_redirs(output_redirs)
|
||||||
let mut input_redirs = vec![];
|
)
|
||||||
for redir in redirs {
|
|
||||||
match redir.class {
|
|
||||||
RedirType::Input => input_redirs.push(redir),
|
|
||||||
RedirType::Pipe => {
|
|
||||||
match redir.io_info.tgt_fd() {
|
|
||||||
STDIN_FILENO => input_redirs.push(redir),
|
|
||||||
STDOUT_FILENO |
|
|
||||||
STDERR_FILENO => output_redirs.push(redir),
|
|
||||||
_ => unreachable!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => output_redirs.push(redir)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(Self::from_redirs(input_redirs),Self::from_redirs(output_redirs))
|
|
||||||
}
|
}
|
||||||
pub fn save(&'e mut self) {
|
pub fn save(&'e mut self) {
|
||||||
unsafe {
|
let saved_in = dup(STDIN_FILENO).unwrap();
|
||||||
let saved_in = OwnedFd::from_raw_fd(dup(STDIN_FILENO).unwrap());
|
let saved_out = dup(STDOUT_FILENO).unwrap();
|
||||||
let saved_out = OwnedFd::from_raw_fd(dup(STDOUT_FILENO).unwrap());
|
let saved_err = dup(STDERR_FILENO).unwrap();
|
||||||
let saved_err = OwnedFd::from_raw_fd(dup(STDERR_FILENO).unwrap());
|
self.saved_io = Some(IoGroup(saved_in,saved_out,saved_err));
|
||||||
self.saved_io = Some(IoGroup(saved_in,saved_out,saved_err));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
pub fn redirect(&mut self) -> ShResult<()> {
|
pub fn redirect(&mut self) -> ShResult<()> {
|
||||||
self.save();
|
self.save();
|
||||||
for redir in &mut self.redirs {
|
for redir in &mut self.redirs {
|
||||||
let io_info = &mut redir.io_info;
|
let io_mode = &mut redir.io_mode;
|
||||||
let tgt_fd = io_info.tgt_fd();
|
let tgt_fd = io_mode.tgt_fd();
|
||||||
let src_fd = io_info.src_fd();
|
let src_fd = io_mode.src_fd();
|
||||||
dup2(src_fd, tgt_fd)?;
|
dup2(src_fd, tgt_fd)?;
|
||||||
io_info.close()?;
|
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
pub fn restore(&mut self) -> ShResult<()> {
|
pub fn restore(&mut self) -> ShResult<()> {
|
||||||
while let Some(mut redir) = self.pop() {
|
|
||||||
redir.io_info.close()?;
|
|
||||||
}
|
|
||||||
if let Some(saved) = self.saved_io.take() {
|
if let Some(saved) = self.saved_io.take() {
|
||||||
dup2(saved.0.as_raw_fd(), STDIN_FILENO)?;
|
dup2(saved.0, STDIN_FILENO)?;
|
||||||
dup2(saved.1.as_raw_fd(), STDOUT_FILENO)?;
|
close(saved.0)?;
|
||||||
dup2(saved.2.as_raw_fd(), STDERR_FILENO)?;
|
dup2(saved.1, STDOUT_FILENO)?;
|
||||||
|
close(saved.1)?;
|
||||||
|
dup2(saved.2, STDERR_FILENO)?;
|
||||||
|
close(saved.2)?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ expression: nodes
|
|||||||
[
|
[
|
||||||
Ok(
|
Ok(
|
||||||
Node {
|
Node {
|
||||||
class: CmdList {
|
class: Conjunction {
|
||||||
elements: [
|
elements: [
|
||||||
ConjunctNode {
|
ConjunctNode {
|
||||||
cmd: Node {
|
cmd: Node {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ expression: nodes
|
|||||||
[
|
[
|
||||||
Ok(
|
Ok(
|
||||||
Node {
|
Node {
|
||||||
class: CmdList {
|
class: Conjunction {
|
||||||
elements: [
|
elements: [
|
||||||
ConjunctNode {
|
ConjunctNode {
|
||||||
cmd: Node {
|
cmd: Node {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ expression: nodes
|
|||||||
[
|
[
|
||||||
Ok(
|
Ok(
|
||||||
Node {
|
Node {
|
||||||
class: CmdList {
|
class: Conjunction {
|
||||||
elements: [
|
elements: [
|
||||||
ConjunctNode {
|
ConjunctNode {
|
||||||
cmd: Node {
|
cmd: Node {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ expression: nodes
|
|||||||
[
|
[
|
||||||
Ok(
|
Ok(
|
||||||
Node {
|
Node {
|
||||||
class: CmdList {
|
class: Conjunction {
|
||||||
elements: [
|
elements: [
|
||||||
ConjunctNode {
|
ConjunctNode {
|
||||||
cmd: Node {
|
cmd: Node {
|
||||||
|
|||||||
435
src/tests/snapshots/fern__tests__parser__parse_if_redirs.snap
Normal file
435
src/tests/snapshots/fern__tests__parser__parse_if_redirs.snap
Normal file
@@ -0,0 +1,435 @@
|
|||||||
|
---
|
||||||
|
source: src/tests/parser.rs
|
||||||
|
expression: nodes
|
||||||
|
---
|
||||||
|
[
|
||||||
|
Ok(
|
||||||
|
Node {
|
||||||
|
class: Conjunction {
|
||||||
|
elements: [
|
||||||
|
ConjunctNode {
|
||||||
|
cmd: Node {
|
||||||
|
class: IfNode {
|
||||||
|
cond_nodes: [
|
||||||
|
CondNode {
|
||||||
|
cond: Node {
|
||||||
|
class: Pipeline {
|
||||||
|
cmds: [
|
||||||
|
Node {
|
||||||
|
class: Command {
|
||||||
|
assignments: [],
|
||||||
|
argv: [
|
||||||
|
Tk {
|
||||||
|
class: Str,
|
||||||
|
span: Span {
|
||||||
|
range: 3..6,
|
||||||
|
source: "if foo; then echo bar; fi > file.txt",
|
||||||
|
},
|
||||||
|
flags: TkFlags(
|
||||||
|
IS_CMD,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
flags: NdFlags(
|
||||||
|
0x0,
|
||||||
|
),
|
||||||
|
redirs: [],
|
||||||
|
tokens: [
|
||||||
|
Tk {
|
||||||
|
class: Str,
|
||||||
|
span: Span {
|
||||||
|
range: 3..6,
|
||||||
|
source: "if foo; then echo bar; fi > file.txt",
|
||||||
|
},
|
||||||
|
flags: TkFlags(
|
||||||
|
IS_CMD,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
Tk {
|
||||||
|
class: Sep,
|
||||||
|
span: Span {
|
||||||
|
range: 6..8,
|
||||||
|
source: "if foo; then echo bar; fi > file.txt",
|
||||||
|
},
|
||||||
|
flags: TkFlags(
|
||||||
|
0x0,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
pipe_err: false,
|
||||||
|
},
|
||||||
|
flags: NdFlags(
|
||||||
|
0x0,
|
||||||
|
),
|
||||||
|
redirs: [],
|
||||||
|
tokens: [
|
||||||
|
Tk {
|
||||||
|
class: Str,
|
||||||
|
span: Span {
|
||||||
|
range: 3..6,
|
||||||
|
source: "if foo; then echo bar; fi > file.txt",
|
||||||
|
},
|
||||||
|
flags: TkFlags(
|
||||||
|
IS_CMD,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
Tk {
|
||||||
|
class: Sep,
|
||||||
|
span: Span {
|
||||||
|
range: 6..8,
|
||||||
|
source: "if foo; then echo bar; fi > file.txt",
|
||||||
|
},
|
||||||
|
flags: TkFlags(
|
||||||
|
0x0,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
body: [
|
||||||
|
Node {
|
||||||
|
class: Pipeline {
|
||||||
|
cmds: [
|
||||||
|
Node {
|
||||||
|
class: Command {
|
||||||
|
assignments: [],
|
||||||
|
argv: [
|
||||||
|
Tk {
|
||||||
|
class: Str,
|
||||||
|
span: Span {
|
||||||
|
range: 13..17,
|
||||||
|
source: "if foo; then echo bar; fi > file.txt",
|
||||||
|
},
|
||||||
|
flags: TkFlags(
|
||||||
|
IS_CMD | BUILTIN,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
Tk {
|
||||||
|
class: Str,
|
||||||
|
span: Span {
|
||||||
|
range: 18..21,
|
||||||
|
source: "if foo; then echo bar; fi > file.txt",
|
||||||
|
},
|
||||||
|
flags: TkFlags(
|
||||||
|
0x0,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
flags: NdFlags(
|
||||||
|
0x0,
|
||||||
|
),
|
||||||
|
redirs: [],
|
||||||
|
tokens: [
|
||||||
|
Tk {
|
||||||
|
class: Str,
|
||||||
|
span: Span {
|
||||||
|
range: 13..17,
|
||||||
|
source: "if foo; then echo bar; fi > file.txt",
|
||||||
|
},
|
||||||
|
flags: TkFlags(
|
||||||
|
IS_CMD | BUILTIN,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
Tk {
|
||||||
|
class: Str,
|
||||||
|
span: Span {
|
||||||
|
range: 18..21,
|
||||||
|
source: "if foo; then echo bar; fi > file.txt",
|
||||||
|
},
|
||||||
|
flags: TkFlags(
|
||||||
|
0x0,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
Tk {
|
||||||
|
class: Sep,
|
||||||
|
span: Span {
|
||||||
|
range: 21..23,
|
||||||
|
source: "if foo; then echo bar; fi > file.txt",
|
||||||
|
},
|
||||||
|
flags: TkFlags(
|
||||||
|
0x0,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
pipe_err: false,
|
||||||
|
},
|
||||||
|
flags: NdFlags(
|
||||||
|
0x0,
|
||||||
|
),
|
||||||
|
redirs: [],
|
||||||
|
tokens: [
|
||||||
|
Tk {
|
||||||
|
class: Str,
|
||||||
|
span: Span {
|
||||||
|
range: 13..17,
|
||||||
|
source: "if foo; then echo bar; fi > file.txt",
|
||||||
|
},
|
||||||
|
flags: TkFlags(
|
||||||
|
IS_CMD | BUILTIN,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
Tk {
|
||||||
|
class: Str,
|
||||||
|
span: Span {
|
||||||
|
range: 18..21,
|
||||||
|
source: "if foo; then echo bar; fi > file.txt",
|
||||||
|
},
|
||||||
|
flags: TkFlags(
|
||||||
|
0x0,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
Tk {
|
||||||
|
class: Sep,
|
||||||
|
span: Span {
|
||||||
|
range: 21..23,
|
||||||
|
source: "if foo; then echo bar; fi > file.txt",
|
||||||
|
},
|
||||||
|
flags: TkFlags(
|
||||||
|
0x0,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
else_block: [],
|
||||||
|
},
|
||||||
|
flags: NdFlags(
|
||||||
|
0x0,
|
||||||
|
),
|
||||||
|
redirs: [
|
||||||
|
Redir {
|
||||||
|
io_mode: File {
|
||||||
|
tgt_fd: 1,
|
||||||
|
file: File {
|
||||||
|
fd: 3,
|
||||||
|
path: "/home/pagedmov/Coding/projects/rust/fern/file.txt",
|
||||||
|
read: false,
|
||||||
|
write: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
class: Output,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
tokens: [
|
||||||
|
Tk {
|
||||||
|
class: Str,
|
||||||
|
span: Span {
|
||||||
|
range: 0..2,
|
||||||
|
source: "if foo; then echo bar; fi > file.txt",
|
||||||
|
},
|
||||||
|
flags: TkFlags(
|
||||||
|
KEYWORD,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
Tk {
|
||||||
|
class: Str,
|
||||||
|
span: Span {
|
||||||
|
range: 3..6,
|
||||||
|
source: "if foo; then echo bar; fi > file.txt",
|
||||||
|
},
|
||||||
|
flags: TkFlags(
|
||||||
|
IS_CMD,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
Tk {
|
||||||
|
class: Sep,
|
||||||
|
span: Span {
|
||||||
|
range: 6..8,
|
||||||
|
source: "if foo; then echo bar; fi > file.txt",
|
||||||
|
},
|
||||||
|
flags: TkFlags(
|
||||||
|
0x0,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
Tk {
|
||||||
|
class: Str,
|
||||||
|
span: Span {
|
||||||
|
range: 8..12,
|
||||||
|
source: "if foo; then echo bar; fi > file.txt",
|
||||||
|
},
|
||||||
|
flags: TkFlags(
|
||||||
|
KEYWORD,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
Tk {
|
||||||
|
class: Str,
|
||||||
|
span: Span {
|
||||||
|
range: 13..17,
|
||||||
|
source: "if foo; then echo bar; fi > file.txt",
|
||||||
|
},
|
||||||
|
flags: TkFlags(
|
||||||
|
IS_CMD | BUILTIN,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
Tk {
|
||||||
|
class: Str,
|
||||||
|
span: Span {
|
||||||
|
range: 18..21,
|
||||||
|
source: "if foo; then echo bar; fi > file.txt",
|
||||||
|
},
|
||||||
|
flags: TkFlags(
|
||||||
|
0x0,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
Tk {
|
||||||
|
class: Sep,
|
||||||
|
span: Span {
|
||||||
|
range: 21..23,
|
||||||
|
source: "if foo; then echo bar; fi > file.txt",
|
||||||
|
},
|
||||||
|
flags: TkFlags(
|
||||||
|
0x0,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
Tk {
|
||||||
|
class: Str,
|
||||||
|
span: Span {
|
||||||
|
range: 23..25,
|
||||||
|
source: "if foo; then echo bar; fi > file.txt",
|
||||||
|
},
|
||||||
|
flags: TkFlags(
|
||||||
|
KEYWORD,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
Tk {
|
||||||
|
class: Redir,
|
||||||
|
span: Span {
|
||||||
|
range: 26..27,
|
||||||
|
source: "if foo; then echo bar; fi > file.txt",
|
||||||
|
},
|
||||||
|
flags: TkFlags(
|
||||||
|
0x0,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
Tk {
|
||||||
|
class: Str,
|
||||||
|
span: Span {
|
||||||
|
range: 28..36,
|
||||||
|
source: "if foo; then echo bar; fi > file.txt",
|
||||||
|
},
|
||||||
|
flags: TkFlags(
|
||||||
|
0x0,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
operator: Null,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
flags: NdFlags(
|
||||||
|
0x0,
|
||||||
|
),
|
||||||
|
redirs: [],
|
||||||
|
tokens: [
|
||||||
|
Tk {
|
||||||
|
class: Str,
|
||||||
|
span: Span {
|
||||||
|
range: 0..2,
|
||||||
|
source: "if foo; then echo bar; fi > file.txt",
|
||||||
|
},
|
||||||
|
flags: TkFlags(
|
||||||
|
KEYWORD,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
Tk {
|
||||||
|
class: Str,
|
||||||
|
span: Span {
|
||||||
|
range: 3..6,
|
||||||
|
source: "if foo; then echo bar; fi > file.txt",
|
||||||
|
},
|
||||||
|
flags: TkFlags(
|
||||||
|
IS_CMD,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
Tk {
|
||||||
|
class: Sep,
|
||||||
|
span: Span {
|
||||||
|
range: 6..8,
|
||||||
|
source: "if foo; then echo bar; fi > file.txt",
|
||||||
|
},
|
||||||
|
flags: TkFlags(
|
||||||
|
0x0,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
Tk {
|
||||||
|
class: Str,
|
||||||
|
span: Span {
|
||||||
|
range: 8..12,
|
||||||
|
source: "if foo; then echo bar; fi > file.txt",
|
||||||
|
},
|
||||||
|
flags: TkFlags(
|
||||||
|
KEYWORD,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
Tk {
|
||||||
|
class: Str,
|
||||||
|
span: Span {
|
||||||
|
range: 13..17,
|
||||||
|
source: "if foo; then echo bar; fi > file.txt",
|
||||||
|
},
|
||||||
|
flags: TkFlags(
|
||||||
|
IS_CMD | BUILTIN,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
Tk {
|
||||||
|
class: Str,
|
||||||
|
span: Span {
|
||||||
|
range: 18..21,
|
||||||
|
source: "if foo; then echo bar; fi > file.txt",
|
||||||
|
},
|
||||||
|
flags: TkFlags(
|
||||||
|
0x0,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
Tk {
|
||||||
|
class: Sep,
|
||||||
|
span: Span {
|
||||||
|
range: 21..23,
|
||||||
|
source: "if foo; then echo bar; fi > file.txt",
|
||||||
|
},
|
||||||
|
flags: TkFlags(
|
||||||
|
0x0,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
Tk {
|
||||||
|
class: Str,
|
||||||
|
span: Span {
|
||||||
|
range: 23..25,
|
||||||
|
source: "if foo; then echo bar; fi > file.txt",
|
||||||
|
},
|
||||||
|
flags: TkFlags(
|
||||||
|
KEYWORD,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
Tk {
|
||||||
|
class: Redir,
|
||||||
|
span: Span {
|
||||||
|
range: 26..27,
|
||||||
|
source: "if foo; then echo bar; fi > file.txt",
|
||||||
|
},
|
||||||
|
flags: TkFlags(
|
||||||
|
0x0,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
Tk {
|
||||||
|
class: Str,
|
||||||
|
span: Span {
|
||||||
|
range: 28..36,
|
||||||
|
source: "if foo; then echo bar; fi > file.txt",
|
||||||
|
},
|
||||||
|
flags: TkFlags(
|
||||||
|
0x0,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -5,7 +5,7 @@ expression: nodes
|
|||||||
[
|
[
|
||||||
Ok(
|
Ok(
|
||||||
Node {
|
Node {
|
||||||
class: CmdList {
|
class: Conjunction {
|
||||||
elements: [
|
elements: [
|
||||||
ConjunctNode {
|
ConjunctNode {
|
||||||
cmd: Node {
|
cmd: Node {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ expression: nodes
|
|||||||
[
|
[
|
||||||
Ok(
|
Ok(
|
||||||
Node {
|
Node {
|
||||||
class: CmdList {
|
class: Conjunction {
|
||||||
elements: [
|
elements: [
|
||||||
ConjunctNode {
|
ConjunctNode {
|
||||||
cmd: Node {
|
cmd: Node {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ expression: nodes
|
|||||||
[
|
[
|
||||||
Ok(
|
Ok(
|
||||||
Node {
|
Node {
|
||||||
class: CmdList {
|
class: Conjunction {
|
||||||
elements: [
|
elements: [
|
||||||
ConjunctNode {
|
ConjunctNode {
|
||||||
cmd: Node {
|
cmd: Node {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ expression: nodes
|
|||||||
[
|
[
|
||||||
Ok(
|
Ok(
|
||||||
Node {
|
Node {
|
||||||
class: CmdList {
|
class: Conjunction {
|
||||||
elements: [
|
elements: [
|
||||||
ConjunctNode {
|
ConjunctNode {
|
||||||
cmd: Node {
|
cmd: Node {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ expression: nodes
|
|||||||
[
|
[
|
||||||
Ok(
|
Ok(
|
||||||
Node {
|
Node {
|
||||||
class: CmdList {
|
class: Conjunction {
|
||||||
elements: [
|
elements: [
|
||||||
ConjunctNode {
|
ConjunctNode {
|
||||||
cmd: Node {
|
cmd: Node {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ expression: nodes
|
|||||||
[
|
[
|
||||||
Ok(
|
Ok(
|
||||||
Node {
|
Node {
|
||||||
class: CmdList {
|
class: Conjunction {
|
||||||
elements: [
|
elements: [
|
||||||
ConjunctNode {
|
ConjunctNode {
|
||||||
cmd: Node {
|
cmd: Node {
|
||||||
@@ -198,7 +198,7 @@ expression: nodes
|
|||||||
),
|
),
|
||||||
Ok(
|
Ok(
|
||||||
Node {
|
Node {
|
||||||
class: CmdList {
|
class: Conjunction {
|
||||||
elements: [
|
elements: [
|
||||||
ConjunctNode {
|
ConjunctNode {
|
||||||
cmd: Node {
|
cmd: Node {
|
||||||
@@ -391,7 +391,7 @@ expression: nodes
|
|||||||
),
|
),
|
||||||
Ok(
|
Ok(
|
||||||
Node {
|
Node {
|
||||||
class: CmdList {
|
class: Conjunction {
|
||||||
elements: [
|
elements: [
|
||||||
ConjunctNode {
|
ConjunctNode {
|
||||||
cmd: Node {
|
cmd: Node {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ expression: nodes
|
|||||||
[
|
[
|
||||||
Ok(
|
Ok(
|
||||||
Node {
|
Node {
|
||||||
class: CmdList {
|
class: Conjunction {
|
||||||
elements: [
|
elements: [
|
||||||
ConjunctNode {
|
ConjunctNode {
|
||||||
cmd: Node {
|
cmd: Node {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ expression: nodes
|
|||||||
[
|
[
|
||||||
Ok(
|
Ok(
|
||||||
Node {
|
Node {
|
||||||
class: CmdList {
|
class: Conjunction {
|
||||||
elements: [
|
elements: [
|
||||||
ConjunctNode {
|
ConjunctNode {
|
||||||
cmd: Node {
|
cmd: Node {
|
||||||
|
|||||||
Reference in New Issue
Block a user