From e0c3acab63a771645282b667b2f161f71e393da8 Mon Sep 17 00:00:00 2001 From: Michael Sippel Date: Mon, 30 Oct 2023 23:23:45 +0100 Subject: [PATCH] parser: correct handling of spaces in double quotes --- src/sh/ast.rs | 115 +++++++++++ src/sh/mod.rs | 7 + src/sh/mod.rs~ | 3 + src/sh/parse.rs | 484 +++++++++++++++++++++++++++++++++++++++++++++++ src/sh/parse.rs~ | 438 ++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 1047 insertions(+) create mode 100644 src/sh/ast.rs create mode 100644 src/sh/mod.rs create mode 100644 src/sh/mod.rs~ create mode 100644 src/sh/parse.rs create mode 100644 src/sh/parse.rs~ diff --git a/src/sh/ast.rs b/src/sh/ast.rs new file mode 100644 index 0000000..87e7aae --- /dev/null +++ b/src/sh/ast.rs @@ -0,0 +1,115 @@ +use std::boxed::Box; + +//<<<<>>>><<>><><<>><<<*>>><<>><><<>><<<<>>>>\\ + +#[derive(Debug, PartialEq)] +pub enum Command { + Simple { + assignments: Vec, + command_word: Word, + redirections: Vec + }, + Pipeline(Vec), + Sequence(Vec), + ShortCircuitConjunction(Vec), + ShortCircuitDisjunction(Vec), + Negation(Box), + While { + condition: Box, + loop_body: Box + }, + For { + varname: String, + sequence: Word, + loop_body: Box + }, + If { + condition: Box, + then_branch: Box, + else_branch: Box + }, + Case { + expr: Word, + cases: Vec<(Word, Command)> + }, + Function { + name: String, + body: Box + } +} + +//<<<<>>>><<>><><<>><<<*>>><<>><><<>><<<<>>>>\\ + +#[derive(Debug, PartialEq)] +pub struct Assignment { + pub name: String, + pub value: Word +} + +#[derive(Debug, PartialEq)] +pub struct Word { + pub segments: Vec +} + +#[derive(Debug, PartialEq)] +pub enum WordSegment { + Tilde(String), + Literal(String), + Parameter(String, ParameterFormat), + Subshell(Command), + DoubleQuote(Word), +} + +#[derive(Debug, PartialEq)] +pub enum ParameterFormat { + Normal, + Length, + Default(Word), + Assign(Word), + Error(Word), + Alt(Word), + Sub(ParamSubSide, ParamSubMode, Word), +} + +#[derive(Debug, PartialEq)] +pub enum ParamSubMode { + Shortest, Longest +} + +#[derive(Debug, PartialEq)] +pub enum ParamSubSide { + Prefix, Suffix +} + +//<<<<>>>><<>><><<>><<<*>>><<>><><<>><<<<>>>>\\ + +#[derive(Debug, PartialEq)] +pub struct Redirection { + redirection_type: RedirectionType, + fd: u64, + target: Word +} + +#[derive(Debug, PartialEq)] +pub enum RedirectionType { + File(FileRedirectionType), + Dup(DupRedirectionType), + Heredoc // '<<' +} + +#[derive(Debug, PartialEq)] +pub enum FileRedirectionType { + In, // '<' + InOut, // '<>' + Out, // '>' + OutReplace, // '>|' + OutAppend, // '>>' +} + +#[derive(Debug, PartialEq)] +pub enum DupRedirectionType { + In, // '<&' + Out // '>&' +} + +//<<<<>>>><<>><><<>><<<*>>><<>><><<>><<<<>>>>\\ diff --git a/src/sh/mod.rs b/src/sh/mod.rs new file mode 100644 index 0000000..0c08c79 --- /dev/null +++ b/src/sh/mod.rs @@ -0,0 +1,7 @@ + +pub mod ast; +pub mod parse; + + +pub use ast::*; +pub use parse::*; diff --git a/src/sh/mod.rs~ b/src/sh/mod.rs~ new file mode 100644 index 0000000..cf2b51c --- /dev/null +++ b/src/sh/mod.rs~ @@ -0,0 +1,3 @@ + +pub mod ast; +pub mod parse; diff --git a/src/sh/parse.rs b/src/sh/parse.rs new file mode 100644 index 0000000..6b5691a --- /dev/null +++ b/src/sh/parse.rs @@ -0,0 +1,484 @@ +use { + crate::sh::ast::*, + std::iter::{Peekable}, +}; + + +#[derive(Debug, PartialEq)] +pub enum LexError { + UnexpectedEnd(Vec>), + UnexpectedToken(char), + InvalidFileRedirectionType +} + + +///! iterates chars until it finds some char in `delim` +pub struct DelimIter<'a, It> +where It: Iterator { + chars: &'a mut Peekable, + delim: Vec<(Option, bool)> +} + +impl<'a, It> DelimIter<'a, It> +where It: Iterator { + fn new(chars: &'a mut Peekable, delim: Vec<(Option, bool)>) -> Self { + DelimIter { chars, delim } + } + + fn new_whitespace(chars: &'a mut Peekable) -> Self { + DelimIter::new(chars, vec![ + (None, true), + (Some(' '), true), + (Some('\t'), true), + (Some('\n'), true) + ]) + } + + fn new_shell_word(chars: &'a mut Peekable) -> Self { + DelimIter::new(chars, vec![ + (None, true), + (Some(' '), true), + (Some('\t'), true), + (Some('\n'), true), + (Some('|'), false), + (Some('&'), false), + (Some(';'), false), + (Some('\"'), false), + (Some('\''), false) + ]) + } + + fn new_shell_word_or_assignment(chars: &'a mut Peekable) -> Self { + DelimIter::new(chars, vec![ + (None, true), + (Some(' '), true), + (Some('\t'), true), + (Some('\n'), true), + (Some('='), false), + (Some('|'), false), + (Some('&'), false), + (Some(';'), false), + (Some('\"'), false), + (Some('\''), false) + ]) + } +} + +impl<'a, It> Iterator for DelimIter<'a, It> +where It: 'a + Iterator { + type Item = Result; + + fn next(&mut self) -> Option> { + for (delim, consume) in self.delim.iter() { + if self.chars.peek().cloned() == *delim { + if *consume { + self.chars.next(); + } + return None; + } + } + + match self.chars.next() { + Some(c) => Some(Ok(c)), + None => Some(Err(LexError::UnexpectedEnd(vec![]))) + } + } +} + + +pub struct WordLexer<'a, It> +where It: 'a + Iterator { + chars: &'a mut Peekable +} + +impl<'a, It> WordLexer<'a, It> +where It: Iterator { + fn collect_until(&mut self, close: Option) -> Result { + DelimIter::new(&mut self.chars, vec![(close, true)]) + .try_collect::() + } +} + +pub struct SubstLexer<'a, It> +where It: 'a + Iterator { + chars: &'a mut Peekable +} + +pub fn skip_whitespace(chars: &mut Peekable) +where It: Iterator +{ + while let Some(c) = chars.peek() { + if c.is_whitespace() { + chars.next(); + } else { + break; + } + } +} + +pub fn parse_quoted(chars: &mut Peekable) -> Result +where It: Iterator +{ + assert_eq!( chars.next(), Some('\'')); + let quoted = DelimIter::new(chars, vec![(Some('\''), true)]).try_collect::(); + match quoted { + Ok(s) => { + Ok(WordSegment::Literal(s)) + }, + Err(e) => Err(e) + } +} + +pub fn parse_doublequoted(chars: &mut Peekable) -> Result +where It: Iterator +{ + assert_eq!( chars.next(), Some('\"')); + let quoted = DelimIter::new(chars, vec![(Some('\"'), true)]).try_collect::(); + + match quoted { + Ok(s) => { + let word = Word { + segments: SubstLexer { chars: &mut s.chars().peekable() } + .try_collect::>()? +// .scan((), |_, x| x.ok()) +// .collect::>() + }; + + Ok(WordSegment::DoubleQuote(word)) + }, + Err(e) => Err(e) + } +} + +pub fn parse_word(chars: &mut Peekable) -> Result +where It: Iterator +{ + Ok(Word { + segments: WordLexer{ chars }.try_collect::>()? + }) +} + +impl std::str::FromStr for FileRedirectionType { + type Err = LexError; + + fn from_str(s: &str) -> Result { + match s { + "<" => Ok(FileRedirectionType::In), + "<>" => Ok(FileRedirectionType::InOut), + ">" => Ok(FileRedirectionType::Out), + ">|" => Ok(FileRedirectionType::OutReplace), + ">>" => Ok(FileRedirectionType::OutAppend), + _ => Err(LexError::InvalidFileRedirectionType) + } + } +} + +pub fn parse_redirection(chars: &mut Peekable) -> Result +where It: Iterator +{ + Err(LexError::InvalidFileRedirectionType) + // let name = DelimIterator::new(chars, vec!['<', '>']).collect::(); +} + +pub fn parse_simple_cmd(chars: &mut Peekable) -> Result, LexError> +where It: Iterator +{ + let mut assignments = Vec::new(); + let mut redirections = Vec::new(); + + if chars.peek() == None { + return Ok(None); + } + + loop { + skip_whitespace(chars); + let mut name = DelimIter::new_shell_word_or_assignment(chars).try_collect::()?; + + match chars.peek().clone() { + Some('=') => { + chars.next(); + let mut lex = WordLexer{ chars }; + match lex.next() { + Some(Ok(value)) => { + assignments.push(Assignment { name, value: Word{ segments: vec![ value ] } }); + }, + Some(Err(e)) => { + return Err(e); + }, + None => { + return Err(LexError::UnexpectedEnd(vec![])); + } + } + } + _ => { + let mut cmd_segments = WordLexer{ chars }.try_collect::>()?; + cmd_segments.insert(0, WordSegment::Literal(name)); + + return Ok(Some(Command::Simple { + assignments, + command_word: Word { segments: cmd_segments }, + redirections, + })); + } + } + } +} + +pub fn parse_cmd(chars: &mut Peekable) -> Result, LexError> +where It: Iterator +{ + skip_whitespace(chars); + match chars.peek() { + Some('!') => { + chars.next(); + if let Some(cmd) = parse_cmd(chars)? { + Ok(Some(Command::Negation(Box::new(cmd)))) + } else { + Err(LexError::UnexpectedEnd(vec![])) + } + } + _ => { + if let Some(head) = parse_simple_cmd(chars)? { + skip_whitespace(chars); + + match chars.peek() { + Some(';') => { + chars.next(); + + let tail = parse_cmd( chars ) ?; + match tail { + Some(Command::Sequence(mut s)) => { + s.insert(0, head); + Ok(Some(Command::Sequence(s))) + } + Some(tail) => { + Ok(Some(Command::Sequence(vec![ head, tail ]))) + } + None => { + Ok(Some(head)) + } + } + } + Some('|') => { + chars.next(); + match chars.peek() { + Some('|') => { + chars.next(); + + let tail = parse_cmd( chars ) ?; + match tail { + Some(Command::ShortCircuitDisjunction(mut s)) => { + s.insert(0, head); + Ok(Some(Command::ShortCircuitDisjunction(s))) + } + Some(tail) => { + Ok(Some(Command::ShortCircuitDisjunction(vec![ head, tail ]))) + } + None => { + Err(LexError::UnexpectedEnd(vec![Some('|')])) + } + } + } + _ => { + let tail = parse_cmd( chars ) ?; + match tail { + Some(Command::Pipeline(mut s)) => { + s.insert(0, head); + Ok(Some(Command::Pipeline(s))) + } + Some(tail) => { + Ok(Some(Command::Pipeline(vec![ head, tail ]))) + } + None => { + Err(LexError::UnexpectedEnd(vec![])) + } + } + } + } + } + Some('&') => { + chars.next(); + match chars.peek() { + Some('&') => { + chars.next(); + + let tail = parse_cmd( chars ) ?; + match tail { + Some(Command::ShortCircuitConjunction(mut s)) => { + s.insert(0, head); + Ok(Some(Command::ShortCircuitConjunction(s))) + } + Some(tail) => { + Ok(Some(Command::ShortCircuitConjunction(vec![ head, tail ]))) + } + None => { + Err(LexError::UnexpectedEnd(vec![Some('&'), Some('&')])) + } + } + } + Some(c) => { + Err(LexError::UnexpectedToken(*c)) + } + None => { + // todo: + // background job + Ok(Some(head)) + } + } + } + Some(c) => { + Err(LexError::UnexpectedToken(*c)) + } + None => { + Ok(Some(head)) + } + } + } else { + Ok(None) + } + } + } +} +impl<'a, It> Iterator for SubstLexer<'a, It> +where It: 'a + Iterator { + type Item = Result; + + fn next(&mut self) -> Option> { + match self.chars.peek().cloned() { + Some('$') => { + self.chars.next(); + match self.chars.peek() { + // curly-braced parameter e.g. `${PARAM}` + Some('{') => { + self.chars.next(); + match DelimIter::new(&mut self.chars, vec![(Some('}'), true)]).try_collect::() { + Ok(s) => { + Some(Ok(WordSegment::Parameter(s, ParameterFormat::Normal))) + } + Err(e) => Some(Err(e)) + } + } + + // Subshell + Some('(') => { + self.chars.next(); + let subcmd_str = DelimIter::new(&mut self.chars, vec![(Some(')'), true)]).try_collect::(); + match subcmd_str { + Ok(subcmd_str) => { + match parse_cmd(&mut subcmd_str.chars().peekable()) { + Ok(Some(subcmd)) => { + Some(Ok(WordSegment::Subshell(subcmd))) + } + Ok(None) => None, + Err(err) => Some(Err(err)) + } + } + Err(err) => Some(Err(err)) + } + } + + // plain parameter name e.g. `$PARAM` + _ => { + match DelimIter::new_whitespace(self.chars).collect() { + Ok(s) => { + Some(Ok(WordSegment::Parameter(s, ParameterFormat::Normal))) + } + Err(e) => Some(Err(e)) + } + } + } + } + + // not a substitution, + // take as literal until next $. + _ => { + let lit_str = DelimIter::new( + &mut self.chars, + vec![ + (None, true), + (Some('$'), false) + ] + ).try_collect::(); + + match lit_str { + Ok(s) => { + if s.len() > 0 { + Some(Ok(WordSegment::Literal(s))) + } else { + None + } + } + Err(e) => Some(Err(e)) + } + } + } + } +} + +impl<'a, It> Iterator for WordLexer<'a, It> +where It: 'a + Iterator { + type Item = Result; + + fn next(&mut self) -> Option> { + skip_whitespace(self.chars); + match self.chars.peek().cloned() { + Some('|') => { None } + Some('&') => { None } + Some(';') => { None } + Some('~') => { + self.chars.next(); + let user = DelimIter::new_whitespace(self.chars).collect(); + match user { + Ok(user) => Some(Ok(WordSegment::Tilde(user))), + Err(e) => Some(Err(e)) + } + } + Some('"') => { Some(parse_doublequoted(self.chars)) }, + Some('\'') => { Some(parse_quoted(self.chars)) }, + Some('$') => { + SubstLexer{ chars: &mut self.chars }.next() + } + Some(c) => { + let s : Result = DelimIter::new_shell_word(self.chars).collect(); + match s { + Ok(s) => Some(Ok(WordSegment::Literal(s))), + Err(e) => Some(Err(e)) + } + } + None => { + None + } + } + } +} + + +mod test { + use crate::sh::parse::*; + + #[test] + fn test_delim_iter() { + let mut cs = "test 1234".chars().peekable(); + let mut lexer = DelimIter::new_shell_word(&mut cs); + assert_eq!(lexer.try_collect::(), Ok(String::from("test"))); + } + + #[test] + fn test_word_lexer() { + let mut cs = "test 1234|test".chars().peekable(); + + { + let mut lexer = WordLexer{ chars: &mut cs }; + assert_eq!(lexer.next(), Some(Ok(WordSegment::Literal(String::from("test"))))); + assert_eq!(lexer.next(), Some(Ok(WordSegment::Literal(String::from("1234"))))); + assert_eq!(lexer.next(), None); + } + assert_eq!(cs.next(), Some('|')); + { + let mut lexer = WordLexer{ chars: &mut cs }; + assert_eq!(lexer.next(), Some(Ok(WordSegment::Literal(String::from("test"))))); + assert_eq!(lexer.next(), None); + } + } +} + diff --git a/src/sh/parse.rs~ b/src/sh/parse.rs~ new file mode 100644 index 0000000..b4db237 --- /dev/null +++ b/src/sh/parse.rs~ @@ -0,0 +1,438 @@ +use { + crate::ast::*, + std::iter::{Peekable}, +}; + + +#[derive(Debug, PartialEq)] +pub enum LexError { + UnexpectedEnd(Vec>), + UnexpectedToken(char), + InvalidFileRedirectionType +} + + +///! iterates chars until it finds some char in `delim` +pub struct DelimIter<'a, It> +where It: Iterator { + chars: &'a mut Peekable, + delim: Vec<(Option, bool)> +} + +impl<'a, It> DelimIter<'a, It> +where It: Iterator { + fn new(chars: &'a mut Peekable, delim: Vec<(Option, bool)>) -> Self { + DelimIter { chars, delim } + } + + fn new_whitespace(chars: &'a mut Peekable) -> Self { + DelimIter::new(chars, vec![ + (None, true), + (Some(' '), true), + (Some('\t'), true), + (Some('\n'), true) + ]) + } + + fn new_shell_word(chars: &'a mut Peekable) -> Self { + DelimIter::new(chars, vec![ + (None, true), + (Some(' '), true), + (Some('\t'), true), + (Some('\n'), true), + (Some('|'), false), + (Some('&'), false), + (Some(';'), false), + (Some('\"'), false), + (Some('\''), false) + ]) + } + + fn new_shell_word_or_assignment(chars: &'a mut Peekable) -> Self { + DelimIter::new(chars, vec![ + (None, true), + (Some(' '), true), + (Some('\t'), true), + (Some('\n'), true), + (Some('='), false), + (Some('|'), false), + (Some('&'), false), + (Some(';'), false), + (Some('\"'), false), + (Some('\''), false) + ]) + } +} + +impl<'a, It> Iterator for DelimIter<'a, It> +where It: 'a + Iterator { + type Item = Result; + + fn next(&mut self) -> Option> { + for (delim, consume) in self.delim.iter() { + if self.chars.peek().cloned() == *delim { + if *consume { + self.chars.next(); + } + return None; + } + } + + match self.chars.next() { + Some(c) => Some(Ok(c)), + None => Some(Err(LexError::UnexpectedEnd(vec![]))) + } + } +} + + +pub struct WordLexer<'a, It> +where It: 'a + Iterator { + chars: &'a mut Peekable +} + +impl<'a, It> WordLexer<'a, It> +where It: Iterator { + fn collect_until(&mut self, close: Option) -> Result { + DelimIter::new(&mut self.chars, vec![(close, true)]) + .try_collect::() + } +} + +pub fn skip_whitespace(chars: &mut Peekable) +where It: Iterator +{ + while let Some(c) = chars.peek() { + if c.is_whitespace() { + chars.next(); + } else { + break; + } + } +} + +pub fn parse_quoted(chars: &mut Peekable) -> Result +where It: Iterator +{ + assert_eq!( chars.next(), Some('\'')); + let quoted = DelimIter::new(chars, vec![(Some('\''), true)]).try_collect::(); + match quoted { + Ok(s) => { + Ok(WordSegment::Literal(s)) + }, + Err(e) => Err(e) + } +} + +pub fn parse_doublequoted(chars: &mut Peekable) -> Result +where It: Iterator +{ + assert_eq!( chars.next(), Some('\"')); + let quoted = DelimIter::new(chars, vec![(Some('\"'), true)]).try_collect::(); + match quoted { + Ok(s) => { + let word = Word { + segments: // fixme: handle spaces correctly -> create QuoteLexer + WordLexer { chars: &mut s.chars().peekable() } + .scan((), |_, x| x.ok()) + .collect::>() + }; + + Ok(WordSegment::DoubleQuote(word)) + }, + Err(e) => Err(e) + } +} + +pub fn parse_word(chars: &mut Peekable) -> Result +where It: Iterator +{ + Ok(Word { + segments: WordLexer{ chars }.try_collect::>()? + }) +} + +impl std::str::FromStr for FileRedirectionType { + type Err = LexError; + + fn from_str(s: &str) -> Result { + match s { + "<" => Ok(FileRedirectionType::In), + "<>" => Ok(FileRedirectionType::InOut), + ">" => Ok(FileRedirectionType::Out), + ">|" => Ok(FileRedirectionType::OutReplace), + ">>" => Ok(FileRedirectionType::OutAppend), + _ => Err(LexError::InvalidFileRedirectionType) + } + } +} + +pub fn parse_redirection(chars: &mut Peekable) -> Result +where It: Iterator +{ + Err(LexError::InvalidFileRedirectionType) + // let name = DelimIterator::new(chars, vec!['<', '>']).collect::(); +} + +pub fn parse_simple_cmd(chars: &mut Peekable) -> Result, LexError> +where It: Iterator +{ + let mut assignments = Vec::new(); + let mut redirections = Vec::new(); + + if chars.peek() == None { + return Ok(None); + } + + loop { + skip_whitespace(chars); + let mut name = DelimIter::new_shell_word_or_assignment(chars).try_collect::()?; + + match chars.peek().clone() { + Some('=') => { + chars.next(); + let mut lex = WordLexer{ chars }; + match lex.next() { + Some(Ok(value)) => { + assignments.push(Assignment { name, value: Word{ segments: vec![ value ] } }); + }, + Some(Err(e)) => { + return Err(e); + }, + None => { + return Err(LexError::UnexpectedEnd(vec![])); + } + } + } + _ => { + let mut cmd_segments = WordLexer{ chars }.try_collect::>()?; + cmd_segments.insert(0, WordSegment::Literal(name)); + + return Ok(Some(Command::Simple { + assignments, + command_word: Word { segments: cmd_segments }, + redirections, + })); + } + } + } +} + +pub fn parse_cmd(chars: &mut Peekable) -> Result, LexError> +where It: Iterator +{ + skip_whitespace(chars); + match chars.peek() { + Some('!') => { + chars.next(); + if let Some(cmd) = parse_cmd(chars)? { + Ok(Some(Command::Negation(Box::new(cmd)))) + } else { + Err(LexError::UnexpectedEnd(vec![])) + } + } + _ => { + if let Some(head) = parse_simple_cmd(chars)? { + skip_whitespace(chars); + + match chars.peek() { + Some(';') => { + chars.next(); + + let tail = parse_cmd( chars ) ?; + match tail { + Some(Command::Sequence(mut s)) => { + s.insert(0, head); + Ok(Some(Command::Sequence(s))) + } + Some(tail) => { + Ok(Some(Command::Sequence(vec![ head, tail ]))) + } + None => { + Ok(Some(head)) + } + } + } + Some('|') => { + chars.next(); + match chars.peek() { + Some('|') => { + chars.next(); + + let tail = parse_cmd( chars ) ?; + match tail { + Some(Command::ShortCircuitDisjunction(mut s)) => { + s.insert(0, head); + Ok(Some(Command::ShortCircuitDisjunction(s))) + } + Some(tail) => { + Ok(Some(Command::ShortCircuitDisjunction(vec![ head, tail ]))) + } + None => { + Err(LexError::UnexpectedEnd(vec![Some('|')])) + } + } + } + _ => { + let tail = parse_cmd( chars ) ?; + match tail { + Some(Command::Pipeline(mut s)) => { + s.insert(0, head); + Ok(Some(Command::Pipeline(s))) + } + Some(tail) => { + Ok(Some(Command::Pipeline(vec![ head, tail ]))) + } + None => { + Err(LexError::UnexpectedEnd(vec![])) + } + } + } + } + } + Some('&') => { + chars.next(); + match chars.peek() { + Some('&') => { + chars.next(); + + let tail = parse_cmd( chars ) ?; + match tail { + Some(Command::ShortCircuitConjunction(mut s)) => { + s.insert(0, head); + Ok(Some(Command::ShortCircuitConjunction(s))) + } + Some(tail) => { + Ok(Some(Command::ShortCircuitConjunction(vec![ head, tail ]))) + } + None => { + Err(LexError::UnexpectedEnd(vec![Some('&'), Some('&')])) + } + } + } + Some(c) => { + Err(LexError::UnexpectedToken(*c)) + } + None => { + // todo: + // background job + Ok(Some(head)) + } + } + } + Some(c) => { + Err(LexError::UnexpectedToken(*c)) + } + None => { + Ok(Some(head)) + } + } + } else { + Ok(None) + } + } + } +} + +impl<'a, It> Iterator for WordLexer<'a, It> +where It: 'a + Iterator { + type Item = Result; + + fn next(&mut self) -> Option> { + skip_whitespace(self.chars); + match self.chars.peek().cloned() { + Some('|') => { None } + Some('&') => { None } + Some(';') => { None } + Some('~') => { + self.chars.next(); + let user = DelimIter::new_whitespace(self.chars).collect(); + match user { + Ok(user) => Some(Ok(WordSegment::Tilde(user))), + Err(e) => Some(Err(e)) + } + } + Some('"') => { Some(parse_doublequoted(self.chars)) }, + Some('\'') => { Some(parse_quoted(self.chars)) }, + Some('$') => { + self.chars.next(); + match self.chars.peek() { + Some('{') => { + self.chars.next(); + match DelimIter::new(&mut self.chars, vec![(Some('}'), true)]).try_collect::() { + Ok(s) => { + Some(Ok(WordSegment::Parameter(s, ParameterFormat::Normal))) + } + Err(e) => Some(Err(e)) + } + } + Some('(') => { + self.chars.next(); + let subcmd_str = DelimIter::new(&mut self.chars, vec![(Some(')'), true)]).try_collect::(); + match subcmd_str { + Ok(subcmd_str) => { + match parse_cmd(&mut subcmd_str.chars().peekable()) { + Ok(Some(subcmd)) => { + Some(Ok(WordSegment::Subshell(subcmd))) + } + Ok(None) => None, + Err(err) => Some(Err(err)) + } + } + Err(err) => Some(Err(err)) + } + } + _ => { + match DelimIter::new_whitespace(self.chars).collect() { + Ok(s) => { + Some(Ok(WordSegment::Parameter(s, ParameterFormat::Normal))) + } + Err(e) => Some(Err(e)) + } + } + } + } + Some(c) => { + let s : Result = DelimIter::new_shell_word(self.chars).collect(); + match s { + Ok(s) => Some(Ok(WordSegment::Literal(s))), + Err(e) => Some(Err(e)) + } + } + None => { + None + } + } + } +} + + +mod test { + use crate::parse::*; + + #[test] + fn test_delim_iter() { + let mut cs = "test 1234".chars().peekable(); + let mut lexer = DelimIter::new_shell_word(&mut cs); + assert_eq!(lexer.try_collect::(), Ok(String::from("test"))); + } + + #[test] + fn test_word_lexer() { + let mut cs = "test 1234|test".chars().peekable(); + + { + let mut lexer = WordLexer{ chars: &mut cs }; + assert_eq!(lexer.next(), Some(Ok(WordSegment::Literal(String::from("test"))))); + assert_eq!(lexer.next(), Some(Ok(WordSegment::Literal(String::from("1234"))))); + assert_eq!(lexer.next(), None); + } + assert_eq!(cs.next(), Some('|')); + { + let mut lexer = WordLexer{ chars: &mut cs }; + assert_eq!(lexer.next(), Some(Ok(WordSegment::Literal(String::from("test"))))); + assert_eq!(lexer.next(), None); + } + } +} +