use std::os::unix::io::RawFd;
#[derive(Debug, Clone)]
pub struct Program(pub Vec<Command>);
#[derive(Debug, Clone)]
pub enum Command {
Simple(Vec<Assignment>, Vec<Word>, Vec<Redirect>),
Compound(Vec<Command>),
Not(Box<Command>),
And(Box<Command>, Box<Command>),
Or(Box<Command>, Box<Command>),
Subshell(Box<Program>),
Pipeline(Box<Command>, Box<Command>),
Background(Box<Command>),
Lang(Interpreter, String),
}
#[derive(Debug, Clone)]
pub struct Word(pub String);
#[derive(Debug, Clone)]
pub enum Redirect {
RW { n: RawFd, filename: String },
Read {
n: RawFd,
filename: String,
duplicate: bool,
},
Write {
n: RawFd,
filename: String,
duplicate: bool,
clobber: bool,
append: bool,
},
}
impl Redirect {
pub fn fd(&mut self) -> &mut RawFd {
match self {
Redirect::RW { ref mut n, .. } => n,
Redirect::Read { ref mut n, .. } => n,
Redirect::Write { ref mut n, .. } => n,
}
}
}
#[derive(Debug, Clone)]
pub struct Assignment(pub String, pub String);
impl Command {
pub fn push(mut self, command: &Command) -> Self {
match self {
Command::Compound(ref mut c) => {
c.push(command.clone());
},
c => return Command::Compound(vec![c, command.clone()]),
}
self
}
pub fn insert(mut self, command: &Command) -> Self {
match self {
Command::Compound(ref mut c) => {
c.insert(0, command.clone());
},
c => return Command::Compound(vec![command.clone(), c]),
}
self
}
}
#[derive(Debug, Clone)]
pub enum Interpreter {
Primary,
Alternate,
HashLang(String),
Shebang(String),
}
impl Program {
pub(crate) fn insert(mut self, command: &Command) -> Self {
self.0.insert(0, command.clone());
self
}
pub(crate) fn append(mut self, program: &Program) -> Self {
self.0.append(&mut program.0.to_vec());
self
}
}
#[cfg(test)]
mod tests {
use lalrpop_util::ParseError;
use crate::program::posix::{
parse::{ProgramParser, CommandParser},
lex::{Lexer, Token, Error},
};
use super::*;
fn parse_program<'a>(text: &'a str)
-> Result<Program, ParseError<usize, Token<'a>, Error>>
{
let lexer = Lexer::new(text);
let parser = ProgramParser::new();
parser.parse(text, lexer)
}
#[test]
fn program() {
assert!(parse_program("").is_err());
assert_eq!(1, parse_program("cat README.md").unwrap().0.len());
assert_eq!(1, parse_program("ls;").unwrap().0.len());
assert_eq!(2, parse_program("ls; date").unwrap().0.len());
assert_eq!(3, parse_program("git s; ls -la; true;").unwrap().0.len());
}
fn parse_command<'a>(text: &'a str)
-> Result<Command, ParseError<usize, Token<'a>, Error>>
{
let lexer = Lexer::new(text);
let parser = CommandParser::new();
parser.parse(text, lexer)
}
#[test]
fn simple_command() {
assert!(parse_command("ls").is_ok());
assert!(parse_command("git s").is_ok());
assert!(parse_command("ls -la").is_ok());
}
#[test]
fn compound_command() {
assert!(parse_command("{ls}").is_err());
assert!(parse_command("{ls; date}").is_err());
let text = "{ls;}";
let command = parse_command(text).unwrap();
assert_matches!(&command, Command::Compound(c) if c.len() == 1);
let text = "{ls; date;}";
let command = parse_command(text).unwrap();
assert_matches!(&command, Command::Compound(c) if c.len() == 2);
let text = "{git s; ls -la; true;}";
let command = parse_command(text).unwrap();
assert_matches!(&command, Command::Compound(c) if c.len() == 3);
}
#[test]
fn not_command() {
let command = parse_command("! true").unwrap();
assert_matches!(command, Command::Not(_));
let command = parse_command("! true || false").unwrap();
assert_matches!(command, Command::Or(box Command::Not(_),_));
}
#[test]
fn and_command() {
let command = parse_command("true && false").unwrap();
assert_matches!(command, Command::And(_,_));
let command = parse_command("true || false && true").unwrap();
assert_matches!(command, Command::And(_,_));
}
#[test]
fn or_command() {
let command = parse_command("true || false").unwrap();
assert_matches!(command, Command::Or(_,_));
let command = parse_command("true && false || true").unwrap();
assert_matches!(command, Command::Or(_,_));
}
#[test]
fn subshell_command() {
assert!(parse_command("()").is_err());
assert!(parse_command("$()").is_ok());
let command = parse_command("$(ls)").unwrap();
assert_matches!(command, Command::Subshell(_));
let command = parse_command("$(date;)").unwrap();
assert_matches!(command, Command::Subshell(_));
let command = parse_command("$(date; ls)").unwrap();
assert_matches!(command, Command::Subshell(_));
let command = parse_command("$(date; ls -la;)").unwrap();
assert_matches!(command, Command::Subshell(_));
}
}