use std::convert::From;
use std::error::Error;
use std::iter::empty as empty_iter;
use std::fmt;
use std::mem;
use std::str::FromStr;
use ast::{self, DefaultArithmetic, DefaultParameter};
use ast::builder::{self, Builder, SimpleWordKind};
use ast::builder::ComplexWordKind::{self, Concat, Single};
use ast::builder::WordKind::{self, DoubleQuoted, Simple, SingleQuoted};
use self::iter::{PeekableIterator, PositionIterator, TokenIter, TokenIterator, TokenIterWrapper};
use token::Token;
use token::Token::*;
mod iter;
const CASE: &'static str = "case";
const DO: &'static str = "do";
const DONE: &'static str = "done";
const ELIF: &'static str = "elif";
const ELSE: &'static str = "else";
const ESAC: &'static str = "esac";
const FI: &'static str = "fi";
const FOR: &'static str = "for";
const FUNCTION: &'static str = "function";
const IF: &'static str = "if";
const IN: &'static str = "in";
const THEN: &'static str = "then";
const UNTIL: &'static str = "until";
const WHILE: &'static str = "while";
pub type DefaultParser<I> = Parser<I, builder::StringBuilder>;
pub type ParseResult<T, E> = Result<T, ParseError<E>>;
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub struct SourcePos {
pub byte: usize,
pub line: usize,
pub col: usize,
}
impl Default for SourcePos {
fn default() -> Self {
Self::new()
}
}
impl SourcePos {
pub fn new() -> SourcePos {
SourcePos { byte: 0, line: 1, col: 1 }
}
pub fn advance(&mut self, next: &Token) {
let newlines = match *next {
Name(ref s) |
Literal(ref s) |
Whitespace(ref s) => s.chars().filter(|&c| c == '\n').count(),
Newline => 1,
_ => 0,
};
let tok_len = next.len();
self.byte += tok_len;
self.line += newlines;
self.col = if newlines == 0 { self.col + tok_len } else { 1 };
}
fn advance_tabs(&mut self, num_tab: usize) {
self.byte += num_tab;
self.col += num_tab;
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ParseError<T> {
BadFd(SourcePos, SourcePos),
BadIdent(String, SourcePos),
BadSubst(Token, SourcePos),
Unmatched(Token, SourcePos),
IncompleteCmd(&'static str, SourcePos, &'static str, SourcePos),
Unexpected(Token, SourcePos),
UnexpectedEOF,
Custom(T),
}
impl<T: Error> Error for ParseError<T> {
fn description(&self) -> &str {
match *self {
ParseError::BadFd(..) => "bad file descriptor found",
ParseError::BadIdent(..) => "bad identifier found",
ParseError::BadSubst(..) => "bad substitution found",
ParseError::Unmatched(..) => "unmatched token",
ParseError::IncompleteCmd(..)=> "incomplete command",
ParseError::Unexpected(..) => "unexpected token found",
ParseError::UnexpectedEOF => "unexpected end of input",
ParseError::Custom(ref e) => e.description(),
}
}
fn cause(&self) -> Option<&Error> {
match *self {
ParseError::BadFd(..) |
ParseError::BadIdent(..) |
ParseError::BadSubst(..) |
ParseError::Unmatched(..) |
ParseError::IncompleteCmd(..)|
ParseError::Unexpected(..) |
ParseError::UnexpectedEOF => None,
ParseError::Custom(ref e) => Some(e),
}
}
}
impl<T: fmt::Display> fmt::Display for ParseError<T> {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
match *self {
ParseError::BadFd(ref start, ref end) =>
write!(fmt, "file descriptor found between lines {} - {} cannot possibly be a valid", start, end),
ParseError::BadIdent(ref id, pos) => write!(fmt, "not a valid identifier {}: {}", pos, id),
ParseError::BadSubst(ref t, pos) => write!(fmt, "bad substitution {}: invalid token: {}", pos, t),
ParseError::Unmatched(ref t, pos) => write!(fmt, "unmatched `{}` starting on line {}", t, pos),
ParseError::IncompleteCmd(c, start, kw, kw_pos) => write!(fmt,
"did not find `{}` keyword on line {}, in `{}` command which starts on line {}",
kw, kw_pos, c, start),
ParseError::Unexpected(Newline, pos) => write!(fmt, "found unexpected token on line {}: \\n", pos),
ParseError::Unexpected(ref t, pos) => write!(fmt, "found unexpected token on line {}: {}", pos, t),
ParseError::UnexpectedEOF => fmt.write_str("unexpected end of input"),
ParseError::Custom(ref e) => write!(fmt, "{}", e),
}
}
}
impl<T> From<T> for ParseError<T> {
fn from(err: T) -> Self {
ParseError::Custom(err)
}
}
impl fmt::Display for SourcePos {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
write!(fmt, "{}:{}", self.line, self.col)
}
}
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
enum CompoundCmdKeyword {
For,
Case,
If,
While,
Until,
Brace,
Subshell,
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct CommandGroupDelimiters<'a, 'b, 'c> {
pub reserved_tokens: &'a [Token],
pub reserved_words: &'b [&'static str],
pub exact_tokens: &'c [Token],
}
impl Default for CommandGroupDelimiters<'static, 'static, 'static> {
fn default() -> Self {
CommandGroupDelimiters {
reserved_tokens: &[],
reserved_words: &[],
exact_tokens: &[],
}
}
}
#[must_use = "iterator adaptors are lazy and do nothing unless consumed"]
#[derive(Debug)]
pub struct ParserIterator<I, B> {
parser: Option<Parser<I, B>>,
}
impl<I, B> ParserIterator<I, B> {
fn new(parser: Parser<I, B>) -> Self {
ParserIterator {
parser: Some(parser),
}
}
}
if_nightly! {
impl<I, B> ::std::iter::FusedIterator for ParserIterator<I, B>
where I: Iterator<Item = Token>,
B: Builder,
{}
}
impl<I, B> Iterator for ParserIterator<I, B>
where I: Iterator<Item = Token>,
B: Builder,
{
type Item = ParseResult<B::Command, B::Error>;
fn next(&mut self) -> Option<Self::Item> {
match self.parser.as_mut().map(Parser::complete_command) {
None => None,
Some(ret) => match ret {
Ok(Some(c)) => Some(Ok(c)),
Ok(None) => {
let _ = self.parser.take();
None
},
Err(e) => {
let _ = self.parser.take();
Some(Err(e))
},
}
}
}
}
impl<I, B> IntoIterator for Parser<I, B>
where I: Iterator<Item = Token>,
B: Builder,
{
type IntoIter = ParserIterator<I, B>;
type Item = <Self::IntoIter as Iterator>::Item;
fn into_iter(self) -> Self::IntoIter {
ParserIterator::new(self)
}
}
#[derive(Debug)]
pub struct Parser<I, B> {
iter: TokenIterWrapper<I>,
builder: B,
}
impl<I: Iterator<Item = Token>, B: Builder + Default> Parser<I, B> {
pub fn new<T>(iter: T) -> Parser<I, B> where T: IntoIterator<Item = Token, IntoIter = I> {
Parser::with_builder(iter.into_iter(), Default::default())
}
}
macro_rules! eat_maybe {
($parser:expr, { $($tok:pat => $blk:block),+; _ => $default:block, }) => {
eat_maybe!($parser, { $($tok => $blk),+; _ => $default })
};
($parser:expr, { $($tok:pat => $blk:block),+,}) => {
eat_maybe!($parser, { $($tok => $blk),+ })
};
($parser:expr, { $($tok:pat => $blk:block),+ }) => {
eat_maybe!($parser, { $($tok => $blk),+; _ => {} })
};
($parser:expr, { $($tok:pat => $blk:block),+; _ => $default:block }) => {
$(if let Some(&$tok) = $parser.iter.peek() {
$parser.iter.next();
$blk
} else)+ {
$default
}
};
}
macro_rules! eat {
($parser:expr, { $($tok:pat => $blk:block),+, }) => { eat!($parser, {$($tok => $blk),+}) };
($parser:expr, {$($tok:pat => $blk:block),+}) => {
eat_maybe!($parser, {$($tok => $blk),+; _ => { return Err($parser.make_unexpected_err()) } })
};
}
macro_rules! arith_parse {
($fn_name:ident, $next_expr:ident, $($tok:pat => $constructor:path),+) => {
#[inline]
fn $fn_name(&mut self) -> ParseResult<DefaultArithmetic, B::Error> {
let mut expr = try!(self.$next_expr());
loop {
self.skip_whitespace();
eat_maybe!(self, {
$($tok => {
let next = try!(self.$next_expr());
expr = $constructor(Box::new(expr), Box::new(next));
}),+;
_ => { break },
});
}
Ok(expr)
}
}
}
impl<I: Iterator<Item = Token>, B: Builder> Parser<I, B> {
#[inline]
fn make_unexpected_err(&mut self) -> ParseError<B::Error> {
let pos = self.iter.pos();
self.iter.next().map_or(ParseError::UnexpectedEOF, |t| ParseError::Unexpected(t, pos))
}
pub fn with_builder(iter: I, builder: B) -> Self {
Parser {
iter: TokenIterWrapper::Regular(TokenIter::new(iter)),
builder: builder,
}
}
pub fn pos(&self) -> SourcePos { self.iter.pos() }
pub fn complete_command(&mut self) -> ParseResult<Option<B::Command>, B::Error> {
let pre_cmd_comments = self.linebreak();
if self.iter.peek().is_some() {
Ok(Some(try!(self.complete_command_with_leading_comments(pre_cmd_comments))))
} else {
if !pre_cmd_comments.is_empty() {
try!(self.builder.comments(pre_cmd_comments));
}
Ok(None)
}
}
fn complete_command_with_leading_comments(&mut self, pre_cmd_comments: Vec<builder::Newline>)
-> ParseResult<B::Command, B::Error>
{
let cmd = try!(self.and_or_list());
let (sep, cmd_comment) = eat_maybe!(self, {
Semi => { (builder::SeparatorKind::Semi, self.newline()) },
Amp => { (builder::SeparatorKind::Amp , self.newline()) };
_ => {
match self.newline() {
n@Some(_) => (builder::SeparatorKind::Newline, n),
None => (builder::SeparatorKind::Other, None),
}
}
});
Ok(try!(self.builder.complete_command(pre_cmd_comments, cmd, sep, cmd_comment)))
}
pub fn and_or_list(&mut self) -> ParseResult<B::CommandList, B::Error> {
let first = try!(self.pipeline());
let mut rest = Vec::new();
loop {
self.skip_whitespace();
let is_and = eat_maybe!(self, {
AndIf => { true },
OrIf => { false };
_ => { break },
});
let post_sep_comments = self.linebreak();
let next = try!(self.pipeline());
let next = if is_and {
ast::AndOr::And(next)
} else {
ast::AndOr::Or(next)
};
rest.push((post_sep_comments, next));
}
Ok(try!(self.builder.and_or_list(first, rest)))
}
pub fn pipeline(&mut self) -> ParseResult<B::ListableCommand, B::Error> {
self.skip_whitespace();
let bang = eat_maybe!(self, {
Bang => { true };
_ => { false },
});
let mut cmds = Vec::new();
loop {
if let Some(&Bang) = self.iter.peek() {
return Err(self.make_unexpected_err());
}
let cmd = try!(self.command());
eat_maybe!(self, {
Pipe => { cmds.push((self.linebreak(), cmd)) };
_ => {
cmds.push((Vec::new(), cmd));
break;
},
});
}
Ok(try!(self.builder.pipeline(bang, cmds)))
}
pub fn command(&mut self) -> ParseResult<B::PipeableCommand, B::Error> {
if let Some(kw) = self.next_compound_command_type() {
let compound = try!(self.compound_command_internal(Some(kw)));
Ok(try!(self.builder.compound_command_into_pipeable(compound)))
} else if let Some(fn_def) = try!(self.maybe_function_declaration()) {
Ok(fn_def)
} else {
self.simple_command()
}
}
pub fn simple_command(&mut self) -> ParseResult<B::PipeableCommand, B::Error> {
use ast::{RedirectOrCmdWord, RedirectOrEnvVar};
let mut vars = Vec::new();
let mut cmd_args = Vec::new();
loop {
self.skip_whitespace();
let is_name = {
let mut peeked = self.iter.multipeek();
if let Some(&Name(_)) = peeked.peek_next() {
Some(&Equals) == peeked.peek_next()
} else {
false
}
};
if is_name {
if let Some(Name(var)) = self.iter.next() {
self.iter.next();
let value = if let Some(&Whitespace(_)) = self.iter.peek() {
None
} else {
try!(self.word())
};
vars.push(RedirectOrEnvVar::EnvVar(var, value));
continue;
} else {
unreachable!();
}
}
let exec = match try!(self.redirect()) {
Some(Ok(redirect)) => {
vars.push(RedirectOrEnvVar::Redirect(redirect));
continue;
},
Some(Err(w)) => w,
None => break,
};
cmd_args.push(RedirectOrCmdWord::CmdWord(exec));
break;
}
let vars = vars;
loop {
match try!(self.redirect()) {
Some(Ok(redirect)) => cmd_args.push(RedirectOrCmdWord::Redirect(redirect)),
Some(Err(w)) => cmd_args.push(RedirectOrCmdWord::CmdWord(w)),
None => break,
}
}
if vars.is_empty() && cmd_args.is_empty() {
Err(self.make_unexpected_err())
} else {
Ok(try!(self.builder.simple_command(vars, cmd_args)))
}
}
pub fn redirect_list(&mut self) -> ParseResult<Vec<B::Redirect>, B::Error> {
let mut list = Vec::new();
loop {
self.skip_whitespace();
let start_pos = self.iter.pos();
match try!(self.redirect()) {
Some(Ok(io)) => list.push(io),
Some(Err(_)) => return Err(ParseError::BadFd(start_pos, self.iter.pos())),
None => break,
}
}
Ok(list)
}
pub fn redirect(&mut self) -> ParseResult<Option<Result<B::Redirect, B::Word>>, B::Error> {
fn could_be_numeric<C>(word: &WordKind<C>) -> bool {
let simple_could_be_numeric = |word: &SimpleWordKind<C>| match *word {
SimpleWordKind::Star |
SimpleWordKind::Question |
SimpleWordKind::SquareOpen |
SimpleWordKind::SquareClose |
SimpleWordKind::Tilde |
SimpleWordKind::Colon => false,
SimpleWordKind::Escaped(ref s) |
SimpleWordKind::Literal(ref s) => s.chars().all(|c| c.is_digit(10)),
SimpleWordKind::Param(_) |
SimpleWordKind::Subst(_) |
SimpleWordKind::CommandSubst(_) => true,
};
match *word {
Simple(ref s) => simple_could_be_numeric(s),
SingleQuoted(ref s) => s.chars().all(|c| c.is_digit(10)),
DoubleQuoted(ref fragments) => fragments.iter().all(simple_could_be_numeric),
}
}
fn as_num<C>(word: &ComplexWordKind<C>) -> Option<u16> {
match *word {
Single(Simple(SimpleWordKind::Literal(ref s))) => u16::from_str_radix(s, 10).ok(),
Single(_) => None,
Concat(ref fragments) => {
let mut buf = String::new();
for w in fragments {
if let Simple(SimpleWordKind::Literal(ref s)) = *w {
buf.push_str(s);
} else {
return None;
}
}
u16::from_str_radix(&buf, 10).ok()
}
}
}
let (src_fd, src_fd_as_word) = match try!(self.word_preserve_trailing_whitespace_raw()) {
None => (None, None),
Some(w) => match as_num(&w) {
Some(num) => (Some(num), Some(w)),
None => return Ok(Some(Err(try!(self.builder.word(w))))),
},
};
let redir_tok = match self.iter.peek() {
Some(&Less) |
Some(&Great) |
Some(&DGreat) |
Some(&Clobber) |
Some(&LessAnd) |
Some(&GreatAnd) |
Some(&LessGreat) => self.iter.next().unwrap(),
Some(&DLess) |
Some(&DLessDash) => return Ok(Some(Ok(try!(self.redirect_heredoc(src_fd))))),
_ => match src_fd_as_word {
Some(w) => return Ok(Some(Err(try!(self.builder.word(w))))),
None => return Ok(None),
},
};
self.skip_whitespace();
macro_rules! get_path {
($parser:expr) => {
match try!($parser.word_preserve_trailing_whitespace_raw()) {
Some(p) => try!($parser.builder.word(p)),
None => return Err(self.make_unexpected_err()),
}
}
}
macro_rules! get_dup_path {
($parser:expr) => {{
let path = if $parser.peek_reserved_token(&[Dash]).is_some() {
let dash = try!($parser.reserved_token(&[Dash]));
Single(Simple(SimpleWordKind::Literal(dash.to_string())))
} else {
let path_start_pos = $parser.iter.pos();
let path = if let Some(p) = try!($parser.word_preserve_trailing_whitespace_raw()) {
p
} else {
return Err($parser.make_unexpected_err())
};
let is_numeric = match path {
Single(ref p) => could_be_numeric(&p),
Concat(ref v) => v.iter().all(could_be_numeric),
};
if is_numeric {
path
} else {
return Err(ParseError::BadFd(path_start_pos, self.iter.pos()));
}
};
try!($parser.builder.word(path))
}}
}
let redirect = match redir_tok {
Less => builder::RedirectKind::Read(src_fd, get_path!(self)),
Great => builder::RedirectKind::Write(src_fd, get_path!(self)),
DGreat => builder::RedirectKind::Append(src_fd, get_path!(self)),
Clobber => builder::RedirectKind::Clobber(src_fd, get_path!(self)),
LessGreat => builder::RedirectKind::ReadWrite(src_fd, get_path!(self)),
LessAnd => builder::RedirectKind::DupRead(src_fd, get_dup_path!(self)),
GreatAnd => builder::RedirectKind::DupWrite(src_fd, get_dup_path!(self)),
_ => unreachable!(),
};
Ok(Some(Ok(try!(self.builder.redirect(redirect)))))
}
pub fn redirect_heredoc(&mut self, src_fd: Option<u16>) -> ParseResult<B::Redirect, B::Error> {
use std::iter::FromIterator;
macro_rules! try_map {
($result:expr) => {
try!($result.map_err(|e: iter::UnmatchedError| ParseError::Unmatched(e.0, e.1)))
}
}
let strip_tabs = eat!(self, {
DLess => { false },
DLessDash => { true },
});
self.skip_whitespace();
let mut delim_tokens = Vec::new();
loop {
if let Some(t) = self.iter.peek() {
if t.is_word_delimiter() && t != &ParenOpen { break; }
} else {
break;
}
for t in self.iter.balanced() {
delim_tokens.push(try_map!(t));
}
}
let mut iter = TokenIter::new(delim_tokens.into_iter());
let mut quoted = false;
let mut delim = String::new();
loop {
let start_pos = iter.pos();
match iter.next() {
Some(Backslash) => {
quoted = true;
iter.next().map(|t| delim.push_str(t.as_str()));
},
Some(SingleQuote) => {
quoted = true;
for t in iter.single_quoted(start_pos) {
delim.push_str(try_map!(t).as_str());
}
},
Some(DoubleQuote) => {
quoted = true;
let mut iter = iter.double_quoted(start_pos);
while let Some(next) = iter.next() {
match try_map!(next) {
Backslash => {
match iter.next() {
Some(Ok(tok@Dollar)) |
Some(Ok(tok@Backtick)) |
Some(Ok(tok@DoubleQuote)) |
Some(Ok(tok@Backslash)) |
Some(Ok(tok@Newline)) => delim.push_str(tok.as_str()),
Some(t) => {
let t = try_map!(t);
delim.push_str(Backslash.as_str());
delim.push_str(t.as_str());
},
None => delim.push_str(Backslash.as_str()),
}
},
t => delim.push_str(t.as_str()),
}
}
},
Some(Backtick) => {
quoted = true;
delim.push_str(Backtick.as_str());
for t in iter.backticked_remove_backslashes(start_pos) {
delim.push_str(try_map!(t).as_str());
}
delim.push_str(Backtick.as_str());
},
Some(t) => delim.push_str(t.as_str()),
None => break,
}
}
if delim.is_empty() {
return Err(self.make_unexpected_err());
}
delim.shrink_to_fit();
let (delim, quoted) = (delim, quoted);
let delim_len = delim.len();
let delim_r = String::from_iter(vec!(delim.as_str(), "\r"));
let delim_r_len = delim_r.len();
let saved_pos = self.iter.pos();
let mut saved_tokens = Vec::new();
while self.iter.peek().is_some() {
if let Some(&Newline) = self.iter.peek() {
saved_tokens.push(self.iter.next().unwrap());
break;
}
for t in self.iter.balanced() {
saved_tokens.push(try_map!(t));
}
}
let heredoc_start_pos = self.iter.pos();
let mut heredoc = Vec::new();
'heredoc: loop {
let mut line_start_pos = self.iter.pos();
let mut line = Vec::new();
'line: loop {
if strip_tabs {
let skip_next = if let Some(&Whitespace(ref w)) = self.iter.peek() {
let stripped = w.trim_left_matches('\t');
let num_tabs = w.len() - stripped.len();
line_start_pos.advance_tabs(num_tabs);
if !stripped.is_empty() {
line.push(Whitespace(stripped.to_owned()));
}
true
} else {
false
};
if skip_next {
self.iter.next();
}
}
let next = self.iter.next();
match next {
None if line.is_empty() => break 'heredoc,
None | Some(Newline) => {
let mut line_len = 0;
for t in &line {
line_len += t.len();
if line_len > delim_r_len {
break;
}
}
if line_len == delim_len || line_len == delim_r_len {
let line_str = concat_tokens(&line);
if line_str == delim || line_str == delim_r {
break 'heredoc;
}
}
if next == Some(Newline) { line.push(Newline); }
break 'line;
},
Some(t) => line.push(t),
}
}
heredoc.push((line, line_start_pos));
}
self.iter.buffer_tokens_to_yield_first(saved_tokens, saved_pos);
let body = if quoted {
let body = heredoc.into_iter().flat_map(|(t, _)| t).collect::<Vec<_>>();
Single(Simple(SimpleWordKind::Literal(concat_tokens(&body))))
} else {
let mut tok_iter = TokenIter::with_position(empty_iter(), heredoc_start_pos);
while let Some((line, pos)) = heredoc.pop() {
tok_iter.buffer_tokens_to_yield_first(line, pos);
}
let mut tok_backup = TokenIterWrapper::Buffered(tok_iter);
mem::swap(&mut self.iter, &mut tok_backup);
let mut body = try!(self.word_interpolated_raw(None, heredoc_start_pos));
let _ = mem::replace(&mut self.iter, tok_backup);
if body.len() > 1 {
Concat(body.into_iter().map(Simple).collect())
} else {
let body = body.pop().unwrap_or_else(|| SimpleWordKind::Literal(String::new()));
Single(Simple(body))
}
};
let word = try!(self.builder.word(body));
Ok(try!(self.builder.redirect(builder::RedirectKind::Heredoc(src_fd, word))))
}
pub fn word(&mut self) -> ParseResult<Option<B::Word>, B::Error> {
let ret = try!(self.word_preserve_trailing_whitespace());
self.skip_whitespace();
Ok(ret)
}
pub fn word_preserve_trailing_whitespace(&mut self) -> ParseResult<Option<B::Word>, B::Error> {
let w = match try!(self.word_preserve_trailing_whitespace_raw()) {
Some(w) => Some(try!(self.builder.word(w))),
None => None,
};
Ok(w)
}
fn word_preserve_trailing_whitespace_raw(&mut self)
-> ParseResult<Option<ComplexWordKind<B::Command>>, B::Error>
{
self.word_preserve_trailing_whitespace_raw_with_delim(None)
}
fn word_preserve_trailing_whitespace_raw_with_delim(&mut self, delim: Option<Token>)
-> ParseResult<Option<ComplexWordKind<B::Command>>, B::Error>
{
self.skip_whitespace();
if let Some(&Pound) = self.iter.peek() {
return Ok(None);
}
let mut words = Vec::new();
loop {
if delim.is_some() && self.iter.peek() == delim.as_ref() {
break;
}
match self.iter.peek() {
Some(&CurlyOpen) |
Some(&CurlyClose) |
Some(&SquareOpen) |
Some(&SquareClose) |
Some(&SingleQuote) |
Some(&DoubleQuote) |
Some(&Pound) |
Some(&Star) |
Some(&Question) |
Some(&Tilde) |
Some(&Bang) |
Some(&Backslash) |
Some(&Percent) |
Some(&Dash) |
Some(&Equals) |
Some(&Plus) |
Some(&Colon) |
Some(&At) |
Some(&Caret) |
Some(&Slash) |
Some(&Comma) |
Some(&Name(_)) |
Some(&Literal(_)) => {},
Some(&Backtick) => {
words.push(Simple(try!(self.backticked_raw())));
continue;
},
Some(&Dollar) |
Some(&ParamPositional(_)) => {
words.push(Simple(try!(self.parameter_raw())));
continue;
},
Some(&Newline) |
Some(&ParenOpen) |
Some(&ParenClose) |
Some(&Semi) |
Some(&Amp) |
Some(&Pipe) |
Some(&AndIf) |
Some(&OrIf) |
Some(&DSemi) |
Some(&Less) |
Some(&Great) |
Some(&DLess) |
Some(&DGreat) |
Some(&GreatAnd) |
Some(&LessAnd) |
Some(&DLessDash) |
Some(&Clobber) |
Some(&LessGreat) |
Some(&Whitespace(_)) |
None => break,
}
let start_pos = self.iter.pos();
let w = match self.iter.next().unwrap() {
tok@Bang |
tok@Pound |
tok@Percent |
tok@Dash |
tok@Equals |
tok@Plus |
tok@At |
tok@Caret |
tok@Slash |
tok@Comma |
tok@CurlyOpen |
tok@CurlyClose => Simple(SimpleWordKind::Literal(tok.to_string())),
Name(s) |
Literal(s) => Simple(SimpleWordKind::Literal(s)),
Star => Simple(SimpleWordKind::Star),
Question => Simple(SimpleWordKind::Question),
Tilde => Simple(SimpleWordKind::Tilde),
SquareOpen => Simple(SimpleWordKind::SquareOpen),
SquareClose => Simple(SimpleWordKind::SquareClose),
Colon => Simple(SimpleWordKind::Colon),
Backslash => match self.iter.next() {
Some(Newline) | None => break,
Some(t) => Simple(SimpleWordKind::Escaped(t.to_string())),
},
SingleQuote => {
let mut buf = String::new();
for t in self.iter.single_quoted(start_pos) {
buf.push_str(try!(t.map_err(|e| ParseError::Unmatched(e.0, e.1))).as_str())
}
SingleQuoted(buf)
},
DoubleQuote => DoubleQuoted(
try!(self.word_interpolated_raw(Some((DoubleQuote, DoubleQuote)), start_pos))
),
Backtick |
Dollar |
ParamPositional(_) => unreachable!(),
Newline |
ParenOpen |
ParenClose |
Semi |
Amp |
Pipe |
AndIf |
OrIf |
DSemi |
Less |
Great |
DLess |
DGreat |
GreatAnd |
LessAnd |
DLessDash |
Clobber |
LessGreat |
Whitespace(_) => unreachable!(),
};
words.push(w);
}
let ret = if words.is_empty() {
None
} else if words.len() == 1 {
Some(Single(words.pop().unwrap()))
} else {
Some(Concat(words))
};
Ok(ret)
}
fn word_interpolated_raw(&mut self, delim: Option<(Token, Token)>, start_pos: SourcePos)
-> ParseResult<Vec<SimpleWordKind<B::Command>>, B::Error>
{
let (delim_open, delim_close) = match delim {
Some((o,c)) => (Some(o), Some(c)),
None => (None, None),
};
let mut words = Vec::new();
let mut buf = String::new();
loop {
if self.iter.peek() == delim_close.as_ref() {
self.iter.next();
break;
}
macro_rules! store {
($word:expr) => {{
if !buf.is_empty() {
words.push(SimpleWordKind::Literal(buf));
buf = String::new();
}
words.push($word);
}}
}
match self.iter.peek() {
Some(&Dollar) |
Some(&ParamPositional(_)) => {
store!(try!(self.parameter_raw()));
continue;
},
Some(&Backtick) => {
store!(try!(self.backticked_raw()));
continue;
},
_ => {},
}
match self.iter.next() {
Some(Backslash) => {
let special = match self.iter.peek() {
Some(&Dollar) |
Some(&Backtick) |
Some(&DoubleQuote) |
Some(&Backslash) |
Some(&Newline) => true,
_ => false,
};
if special || self.iter.peek() == delim_close.as_ref() {
store!(SimpleWordKind::Escaped(self.iter.next().unwrap().to_string()))
} else {
buf.push_str(Backslash.as_str());
}
},
Some(Dollar) => unreachable!(), Some(Backtick) => unreachable!(),
Some(t) => buf.push_str(t.as_str()),
None => match delim_open {
Some(delim) => return Err(ParseError::Unmatched(delim, start_pos)),
None => break,
},
}
}
if !buf.is_empty() {
words.push(SimpleWordKind::Literal(buf));
}
Ok(words)
}
pub fn backticked_command_substitution(&mut self) -> ParseResult<B::Word, B::Error> {
let word = try!(self.backticked_raw());
Ok(try!(self.builder.word(Single(Simple(word)))))
}
fn backticked_raw(&mut self) -> ParseResult<SimpleWordKind<B::Command>, B::Error> {
let backtick_pos = self.iter.pos();
eat!(self, { Backtick => {} });
let tok_iter = try!(self.iter
.token_iter_from_backticked_with_removed_backslashes(backtick_pos)
.map_err(|e| ParseError::Unmatched(e.0, e.1))
);
let mut tok_backup = TokenIterWrapper::Buffered(tok_iter);
mem::swap(&mut self.iter, &mut tok_backup);
let cmd_subst = self.command_group_internal(CommandGroupDelimiters::default());
let _ = mem::replace(&mut self.iter, tok_backup);
Ok(SimpleWordKind::CommandSubst(try!(cmd_subst)))
}
pub fn parameter(&mut self) -> ParseResult<B::Word, B::Error> {
let param = try!(self.parameter_raw());
Ok(try!(self.builder.word(Single(Simple(param)))))
}
fn parameter_raw(&mut self) -> ParseResult<SimpleWordKind<B::Command>, B::Error> {
use ast::Parameter;
let start_pos = self.iter.pos();
match self.iter.next() {
Some(ParamPositional(p)) => Ok(SimpleWordKind::Param(Parameter::Positional(p as u32))),
Some(Dollar) => {
match self.iter.peek() {
Some(&Star) |
Some(&Pound) |
Some(&Question) |
Some(&Dollar) |
Some(&Bang) |
Some(&Dash) |
Some(&At) |
Some(&Name(_)) => Ok(SimpleWordKind::Param(try!(self.parameter_inner()))),
Some(&ParenOpen) |
Some(&CurlyOpen) => self.parameter_substitution_raw(),
_ => Ok(SimpleWordKind::Literal(Dollar.to_string())),
}
},
Some(t) => Err(ParseError::Unexpected(t, start_pos)),
None => Err(ParseError::UnexpectedEOF),
}
}
fn parameter_substitution_word_raw(&mut self, curly_open_pos: SourcePos)
-> ParseResult<Option<ComplexWordKind<B::Command>>, B::Error>
{
let mut words = Vec::new();
'capture_words: loop {
'capture_literals: loop {
let found_backslash = match self.iter.peek() {
None |
Some(&CurlyClose) => break 'capture_words,
Some(&Backslash) => true,
Some(t@&Pound) |
Some(t@&ParenOpen) |
Some(t@&ParenClose) |
Some(t@&Semi) |
Some(t@&Amp) |
Some(t@&Pipe) |
Some(t@&AndIf) |
Some(t@&OrIf) |
Some(t@&DSemi) |
Some(t@&Less) |
Some(t@&Great) |
Some(t@&DLess) |
Some(t@&DGreat) |
Some(t@&GreatAnd) |
Some(t@&LessAnd) |
Some(t@&DLessDash) |
Some(t@&Clobber) |
Some(t@&LessGreat) |
Some(t@&Whitespace(_)) |
Some(t@&Newline) => {
words.push(Simple(SimpleWordKind::Literal(t.as_str().to_owned())));
false
},
Some(&CurlyOpen) |
Some(&SquareOpen) |
Some(&SquareClose) |
Some(&SingleQuote) |
Some(&DoubleQuote) |
Some(&Star) |
Some(&Question) |
Some(&Tilde) |
Some(&Bang) |
Some(&Percent) |
Some(&Dash) |
Some(&Equals) |
Some(&Plus) |
Some(&Colon) |
Some(&At) |
Some(&Caret) |
Some(&Slash) |
Some(&Comma) |
Some(&Name(_)) |
Some(&Literal(_)) |
Some(&Backtick) |
Some(&Dollar) |
Some(&ParamPositional(_)) => break 'capture_literals,
};
let skip_twice = if found_backslash {
let mut peek = self.iter.multipeek();
peek.peek_next(); if let Some(t@&Newline) = peek.peek_next() {
words.push(Simple(SimpleWordKind::Escaped(t.as_str().to_owned())));
true
} else {
break 'capture_literals;
}
} else {
false
};
self.iter.next(); if skip_twice {
self.iter.next();
}
}
match try!(self.word_preserve_trailing_whitespace_raw_with_delim(Some(CurlyClose))) {
Some(Single(w)) => words.push(w),
Some(Concat(ws)) => words.extend(ws),
None => break 'capture_words,
}
}
eat_maybe!(self, {
CurlyClose => {};
_ => { return Err(ParseError::Unmatched(CurlyOpen, curly_open_pos)); }
});
if words.is_empty() {
Ok(None)
} else if words.len() == 1 {
Ok(Some(Single(words.pop().unwrap())))
} else {
Ok(Some(Concat(words)))
}
}
fn parameter_substitution_body_raw(
&mut self,
param: DefaultParameter,
curly_open_pos: SourcePos)
-> ParseResult<SimpleWordKind<B::Command>, B::Error>
{
use ast::Parameter;
use ast::builder::ParameterSubstitutionKind::*;
let has_colon = eat_maybe!(self, {
Colon => { true };
_ => { false },
});
let op_pos = self.iter.pos();
let op = match self.iter.next() {
Some(tok@Dash) |
Some(tok@Equals) |
Some(tok@Question) |
Some(tok@Plus) => tok,
Some(CurlyClose) => return Ok(SimpleWordKind::Param(param)),
Some(t) => return Err(ParseError::BadSubst(t, op_pos)),
None => return Err(ParseError::Unmatched(CurlyOpen, curly_open_pos)),
};
let word = try!(self.parameter_substitution_word_raw(curly_open_pos));
let maybe_len = param == Parameter::Pound && !has_colon && word.is_none();
let ret = if maybe_len && op == Dash {
Len(Parameter::Dash)
} else if maybe_len && op == Question {
Len(Parameter::Question)
} else {
match op {
Dash => Default(has_colon, param, word),
Equals => Assign(has_colon, param, word),
Question => Error(has_colon, param, word),
Plus => Alternative(has_colon, param, word),
_ => unreachable!(),
}
};
Ok(SimpleWordKind::Subst(Box::new(ret)))
}
fn parameter_substitution_raw(&mut self) -> ParseResult<SimpleWordKind<B::Command>, B::Error> {
use ast::Parameter;
use ast::builder::ParameterSubstitutionKind::*;
let start_pos = self.iter.pos();
match self.iter.peek() {
Some(&ParenOpen) => {
let is_arith = {
let mut peeked = self.iter.multipeek();
peeked.peek_next(); Some(&ParenOpen) == peeked.peek_next()
};
let subst = if is_arith {
eat!(self, { ParenOpen => {} });
eat!(self, { ParenOpen => {} });
self.skip_whitespace();
let subst = if let Some(&ParenClose) = self.iter.peek() {
None
} else {
Some(try!(self.arithmetic_substitution()))
};
self.skip_whitespace();
eat!(self, { ParenClose => {} });
self.skip_whitespace();
eat!(self, { ParenClose => {} });
Arith(subst)
} else {
Command(try!(self.subshell_internal(true)))
};
Ok(SimpleWordKind::Subst(Box::new(subst)))
},
Some(&CurlyOpen) => {
let curly_open_pos = start_pos;
self.iter.next();
let param = try!(self.parameter_inner());
let subst = match self.iter.peek() {
Some(&Percent) => {
self.iter.next();
eat_maybe!(self, {
Percent => {
let word = self.parameter_substitution_word_raw(curly_open_pos);
RemoveLargestSuffix(param, try!(word))
};
_ => {
let word = self.parameter_substitution_word_raw(curly_open_pos);
RemoveSmallestSuffix(param, try!(word))
}
})
},
Some(&Pound) => {
self.iter.next();
eat_maybe!(self, {
Pound => {
let word = self.parameter_substitution_word_raw(curly_open_pos);
RemoveLargestPrefix(param, try!(word))
};
_ => {
match try!(self.parameter_substitution_word_raw(curly_open_pos)) {
None if Parameter::Pound == param => Len(Parameter::Pound),
w => RemoveSmallestPrefix(param, w),
}
}
})
},
Some(&Colon) |
Some(&Dash) |
Some(&Equals) |
Some(&Question) |
Some(&Plus) |
Some(&CurlyClose) if Parameter::Pound == param =>
return self.parameter_substitution_body_raw(param, curly_open_pos),
_ if Parameter::Pound == param => {
let param = try!(self.parameter_inner());
eat!(self, { CurlyClose => { Len(param) } })
},
_ => return self.parameter_substitution_body_raw(param, curly_open_pos),
};
Ok(SimpleWordKind::Subst(Box::new(subst)))
},
_ => Err(self.make_unexpected_err()),
}
}
fn parameter_inner(&mut self) -> ParseResult<DefaultParameter, B::Error> {
use ast::Parameter;
let start_pos = self.iter.pos();
let param = match self.iter.next() {
Some(Star) => Parameter::Star,
Some(Pound) => Parameter::Pound,
Some(Question) => Parameter::Question,
Some(Dollar) => Parameter::Dollar,
Some(Bang) => Parameter::Bang,
Some(Dash) => Parameter::Dash,
Some(At) => Parameter::At,
Some(Name(n)) => Parameter::Var(n),
Some(Literal(s)) => match u32::from_str(&s) {
Ok(n) => Parameter::Positional(n),
Err(_) => return Err(ParseError::BadSubst(Literal(s), start_pos)),
},
Some(t) => return Err(ParseError::BadSubst(t, start_pos)),
None => return Err(ParseError::UnexpectedEOF),
};
Ok(param)
}
pub fn do_group(&mut self) -> ParseResult<builder::CommandGroup<B::Command>, B::Error> {
let start_pos = self.iter.pos();
try!(self.reserved_word(&[DO]).map_err(|_| self.make_unexpected_err()));
let result = try!(self.command_group(CommandGroupDelimiters {
reserved_words: &[DONE],
.. Default::default()
}));
try!(self.reserved_word(&[DONE])
.or_else(|()| Err(ParseError::IncompleteCmd(DO, start_pos, DONE, self.iter.pos()))));
Ok(result)
}
pub fn brace_group(&mut self) -> ParseResult<builder::CommandGroup<B::Command>, B::Error> {
let start_pos = self.iter.pos();
try!(self.reserved_token(&[CurlyOpen]));
let cmds = try!(self.command_group(CommandGroupDelimiters {
reserved_tokens: &[CurlyClose],
.. Default::default()
}));
try!(self.reserved_token(&[CurlyClose])
.or_else(|_| Err(ParseError::Unmatched(CurlyOpen, start_pos))));
Ok(cmds)
}
pub fn subshell(&mut self) -> ParseResult<builder::CommandGroup<B::Command>, B::Error> {
self.subshell_internal(false)
}
fn subshell_internal(&mut self, empty_body_ok: bool)
-> ParseResult<builder::CommandGroup<B::Command>, B::Error>
{
let start_pos = self.iter.pos();
eat!(self, { ParenOpen => {} });
let body = try!(self.command_group_internal(CommandGroupDelimiters {
exact_tokens: &[ParenClose],
.. Default::default()
}));
match self.iter.peek() {
Some(&ParenClose) if empty_body_ok || !body.commands.is_empty() => {
self.iter.next();
Ok(body)
},
Some(_) => Err(self.make_unexpected_err()),
None => Err(ParseError::Unmatched(ParenOpen, start_pos)),
}
}
fn next_compound_command_type(&mut self) -> Option<CompoundCmdKeyword> {
self.skip_whitespace();
if Some(&ParenOpen) == self.iter.peek() {
Some(CompoundCmdKeyword::Subshell)
} else if self.peek_reserved_token(&[CurlyOpen]).is_some() {
Some(CompoundCmdKeyword::Brace)
} else {
match self.peek_reserved_word(&[FOR, CASE, IF, WHILE, UNTIL]) {
Some(FOR) => Some(CompoundCmdKeyword::For),
Some(CASE) => Some(CompoundCmdKeyword::Case),
Some(IF) => Some(CompoundCmdKeyword::If),
Some(WHILE) => Some(CompoundCmdKeyword::While),
Some(UNTIL) => Some(CompoundCmdKeyword::Until),
_ => None,
}
}
}
pub fn compound_command(&mut self) -> ParseResult<B::CompoundCommand, B::Error> {
self.compound_command_internal(None)
}
fn compound_command_internal(&mut self, kw: Option<CompoundCmdKeyword>) -> ParseResult<B::CompoundCommand, B::Error> {
let cmd = match kw.or_else(|| self.next_compound_command_type()) {
Some(CompoundCmdKeyword::If) => {
let fragments = try!(self.if_command());
let io = try!(self.redirect_list());
try!(self.builder.if_command(fragments, io))
},
Some(CompoundCmdKeyword::While) |
Some(CompoundCmdKeyword::Until) => {
let (until, guard_body_pair) = try!(self.loop_command());
let io = try!(self.redirect_list());
try!(self.builder.loop_command(until, guard_body_pair, io))
},
Some(CompoundCmdKeyword::For) => {
let for_fragments = try!(self.for_command());
let io = try!(self.redirect_list());
try!(self.builder.for_command(for_fragments, io))
},
Some(CompoundCmdKeyword::Case) => {
let fragments = try!(self.case_command());
let io = try!(self.redirect_list());
try!(self.builder.case_command(fragments, io))
},
Some(CompoundCmdKeyword::Brace) => {
let cmds = try!(self.brace_group());
let io = try!(self.redirect_list());
try!(self.builder.brace_group(cmds, io))
},
Some(CompoundCmdKeyword::Subshell) => {
let cmds = try!(self.subshell());
let io = try!(self.redirect_list());
try!(self.builder.subshell(cmds, io))
},
None => return Err(self.make_unexpected_err()),
};
Ok(cmd)
}
pub fn loop_command(&mut self)
-> ParseResult<(builder::LoopKind, builder::GuardBodyPairGroup<B::Command>), B::Error>
{
let start_pos = self.iter.pos();
let kind = match try!(self.reserved_word(&[WHILE, UNTIL])
.map_err(|_| self.make_unexpected_err())) {
WHILE => builder::LoopKind::While,
UNTIL => builder::LoopKind::Until,
_ => unreachable!(),
};
let guard = try!(self.command_group(CommandGroupDelimiters {
reserved_words: &[DO],
.. Default::default()
}));
match self.peek_reserved_word(&[DO]) {
Some(_) => Ok((kind, builder::GuardBodyPairGroup {
guard: guard,
body: try!(self.do_group())
})),
None => Err(ParseError::IncompleteCmd(WHILE, start_pos, DO, self.iter.pos())),
}
}
pub fn if_command(&mut self) -> ParseResult<builder::IfFragments<B::Command>, B::Error> {
let start_pos = self.iter.pos();
try!(self.reserved_word(&[IF]).map_err(|_| self.make_unexpected_err()));
macro_rules! missing_fi {
() => { |_| ParseError::IncompleteCmd(IF, start_pos, FI, self.iter.pos()) }
}
macro_rules! missing_then {
() => { |_| ParseError::IncompleteCmd(IF, start_pos, THEN, self.iter.pos()) }
}
let mut conditionals = Vec::new();
loop {
let guard = try!(self.command_group(CommandGroupDelimiters {
reserved_words: &[THEN],
.. Default::default()
}));
try!(self.reserved_word(&[THEN]).map_err(missing_then!()));
let body = try!(self.command_group(CommandGroupDelimiters {
reserved_words: &[ELIF, ELSE, FI],
.. Default::default()
}));
conditionals.push(builder::GuardBodyPairGroup {
guard: guard,
body: body,
});
let els = match try!(self.reserved_word(&[ELIF, ELSE, FI]).map_err(missing_fi!())) {
ELIF => continue,
ELSE => {
let els = try!(self.command_group(CommandGroupDelimiters {
reserved_words: &[FI],
.. Default::default()
}));
try!(self.reserved_word(&[FI]).map_err(missing_fi!()));
Some(els)
},
FI => None,
_ => unreachable!(),
};
return Ok(builder::IfFragments { conditionals: conditionals, else_branch: els })
}
}
pub fn for_command(&mut self) -> ParseResult<builder::ForFragments<B::Word, B::Command>, B::Error> {
let start_pos = self.iter.pos();
try!(self.reserved_word(&[FOR]).map_err(|_| self.make_unexpected_err()));
self.skip_whitespace();
match self.iter.peek() {
Some(&Name(_)) |
Some(&Literal(_)) => {},
_ => return Err(self.make_unexpected_err()),
}
let var_pos = self.iter.pos();
let var = match self.iter.next() {
Some(Name(v)) => v,
Some(Literal(s)) => return Err(ParseError::BadIdent(s, var_pos)),
_ => unreachable!(),
};
let var_comment = self.newline();
let post_var_comments = self.linebreak();
let (words, pre_body_comments) = if self.peek_reserved_word(&[IN]).is_some() {
self.reserved_word(&[IN]).unwrap();
let mut words = Vec::new();
while let Some(w) = try!(self.word()) {
words.push(w);
}
let found_semi = eat_maybe!(self, {
Semi => { true };
_ => { false }
});
let words_comment = self.newline();
if !found_semi && words_comment.is_none() {
return Err(self.make_unexpected_err());
}
(Some((post_var_comments, words, words_comment)), self.linebreak())
} else if Some(&Semi) == self.iter.peek() {
eat!(self, { Semi => {} });
(None, self.linebreak())
} else if self.peek_reserved_word(&[DO]).is_none() {
return Err(ParseError::IncompleteCmd(FOR, start_pos, IN, self.iter.pos()));
} else {
(None, post_var_comments)
};
if self.peek_reserved_word(&[DO]).is_none() {
return Err(ParseError::IncompleteCmd(FOR, start_pos , DO, self.iter.pos()));
}
let body = try!(self.do_group());
Ok(builder::ForFragments {
var: var,
var_comment: var_comment,
words: words,
pre_body_comments: pre_body_comments,
body: body,
})
}
pub fn case_command(&mut self) -> ParseResult<builder::CaseFragments<B::Word, B::Command>, B::Error> {
let start_pos = self.iter.pos();
macro_rules! missing_in {
() => { |_| ParseError::IncompleteCmd(CASE, start_pos, IN, self.iter.pos()); }
}
macro_rules! missing_esac {
() => { |_| ParseError::IncompleteCmd(CASE, start_pos, ESAC, self.iter.pos()); }
}
try!(self.reserved_word(&[CASE]).map_err(|_| self.make_unexpected_err()));
let word = match try!(self.word()) {
Some(w) => w,
None => return Err(self.make_unexpected_err()),
};
let post_word_comments = self.linebreak();
try!(self.reserved_word(&[IN]).map_err(missing_in!()));
let in_comment = self.newline();
let mut pre_esac_comments = None;
let mut arms = Vec::new();
loop {
let pre_pattern_comments = self.linebreak();
if self.peek_reserved_word(&[ESAC]).is_some() {
debug_assert_eq!(pre_esac_comments, None);
pre_esac_comments = Some(pre_pattern_comments);
break;
}
if let Some(&ParenOpen) = self.iter.peek() {
self.iter.next();
}
if self.iter.peek().is_none() {
return Err(()).map_err(missing_esac!());
}
let mut patterns = Vec::new();
loop {
match try!(self.word()) {
Some(p) => patterns.push(p),
None => return Err(self.make_unexpected_err()),
}
match self.iter.peek() {
Some(&Pipe) => {
self.iter.next();
continue;
},
Some(&ParenClose) if !patterns.is_empty() => {
self.iter.next();
break;
},
None => return Err(()).map_err(missing_esac!()),
_ => return Err(self.make_unexpected_err()),
}
}
let pattern_comment = self.newline();
let body = try!(self.command_group_internal(CommandGroupDelimiters {
reserved_words: &[ESAC],
reserved_tokens: &[],
exact_tokens: &[DSemi]
}));
let (no_more_arms, arm_comment) = if Some(&DSemi) == self.iter.peek() {
self.iter.next();
(false, self.newline())
} else {
(true, None)
};
arms.push(builder::CaseArm {
patterns: builder::CasePatternFragments {
pre_pattern_comments: pre_pattern_comments,
pattern_alternatives: patterns,
pattern_comment: pattern_comment,
},
body: body,
arm_comment: arm_comment,
});
if no_more_arms {
break;
}
}
let remaining_comments = self.linebreak();
let pre_esac_comments = match pre_esac_comments {
Some(mut comments) => {
comments.extend(remaining_comments);
comments
},
None => remaining_comments,
};
try!(self.reserved_word(&[ESAC]).map_err(missing_esac!()));
Ok(builder::CaseFragments {
word: word,
post_word_comments: post_word_comments,
in_comment: in_comment,
arms: arms,
post_arms_comments: pre_esac_comments,
})
}
pub fn maybe_function_declaration(&mut self) -> ParseResult<Option<B::PipeableCommand>, B::Error> {
if self.peek_reserved_word(&[FUNCTION]).is_some() {
return self.function_declaration().map(Some);
}
let is_fn = {
let mut peeked = self.iter.multipeek();
if let Some(&Name(_)) = peeked.peek_next() {
match peeked.peek_next() {
Some(&Whitespace(_)) => Some(&ParenOpen) == peeked.peek_next(),
Some(&ParenOpen) => true,
_ => false,
}
} else {
false
}
};
if is_fn {
self.function_declaration().map(Some)
} else {
Ok(None)
}
}
pub fn function_declaration(&mut self) -> ParseResult<B::PipeableCommand, B::Error> {
let (name, post_name_comments, body) = try!(self.function_declaration_internal());
Ok(try!(self.builder.function_declaration(name, post_name_comments, body)))
}
fn function_declaration_internal(&mut self)
-> ParseResult<(String, Vec<builder::Newline>, B::CompoundCommand), B::Error>
{
let found_fn = match self.peek_reserved_word(&[FUNCTION]) {
Some(_) => { self.iter.next(); true },
None => false,
};
self.skip_whitespace();
match self.iter.peek() {
Some(&Name(_)) |
Some(&Literal(_)) => {},
_ => return Err(self.make_unexpected_err()),
}
let ident_pos = self.iter.pos();
let name = match self.iter.next() {
Some(Name(n)) => n,
Some(Literal(s)) => return Err(ParseError::BadIdent(s, ident_pos)),
_ => unreachable!(),
};
let body = if Some(&ParenOpen) == self.iter.peek() {
eat!(self, { ParenOpen => {} });
self.skip_whitespace();
eat!(self, { ParenClose => {} });
None
} else if found_fn && Some(&Newline) == self.iter.peek() {
None
} else {
eat!(self, { Whitespace(_) => {} });
self.skip_whitespace();
if !found_fn {
eat!(self, { ParenOpen => {} });
self.skip_whitespace();
eat!(self, { ParenClose => {} });
None
} else if Some(&ParenOpen) == self.iter.peek() {
let subshell = try!(self.subshell_internal(true));
if subshell.commands.is_empty() && subshell.trailing_comments.is_empty() {
None
} else {
Some(try!(self.builder.subshell(subshell, Vec::new())))
}
} else {
None
}
};
let (post_name_comments, body) = match body {
Some(subshell) => (Vec::new(), subshell),
None => (self.linebreak(), try!(self.compound_command())),
};
Ok((name, post_name_comments, body))
}
#[inline]
pub fn skip_whitespace(&mut self) {
loop {
while let Some(&Whitespace(_)) = self.iter.peek() {
self.iter.next();
}
let found_backslash_newline = {
let mut peeked = self.iter.multipeek();
Some(&Backslash) == peeked.peek_next() && Some(&Newline) == peeked.peek_next()
};
if found_backslash_newline {
self.iter.next();
self.iter.next();
} else {
break;
}
}
}
#[inline]
pub fn linebreak(&mut self) -> Vec<builder::Newline> {
let mut lines = Vec::new();
while let Some(n) = self.newline() {
lines.push(n);
}
lines
}
pub fn newline(&mut self) -> Option<builder::Newline> {
self.skip_whitespace();
match self.iter.peek() {
Some(&Pound) => {
let comment = self.iter.by_ref().take_while(|t| t != &Newline).collect::<Vec<_>>();
Some(builder::Newline(Some(concat_tokens(&comment))))
},
Some(&Newline) => {
self.iter.next();
Some(builder::Newline(None))
},
_ => None,
}
}
pub fn peek_reserved_token<'a>(&mut self, tokens: &'a [Token]) -> Option<&'a Token> {
if tokens.is_empty() {
return None;
}
let care_about_whitespace = tokens.iter().any(|tok| {
if let Whitespace(_) = *tok {
true
} else {
false
}
});
let num_tries = if care_about_whitespace {
2
} else {
self.skip_whitespace();
1
};
for _ in 0..num_tries {
{
let mut peeked = self.iter.multipeek();
let found_tok = match peeked.peek_next() {
Some(tok) => tokens.iter().find(|&t| t == tok),
None => return None,
};
if let ret@Some(_) = found_tok {
let found_delim = match peeked.peek_next() {
Some(delim) => delim.is_word_delimiter(),
None => true, };
if found_delim {
return ret;
}
}
}
self.skip_whitespace();
}
None
}
pub fn peek_reserved_word<'a>(&mut self, words: &'a [&str]) -> Option<&'a str> {
if words.is_empty() {
return None;
}
self.skip_whitespace();
let mut peeked = self.iter.multipeek();
let found_tok = match peeked.peek_next() {
Some(&Name(ref kw)) |
Some(&Literal(ref kw)) => words.iter().find(|&w| w == kw).map(|kw| *kw),
_ => None,
};
match peeked.peek_next() {
Some(delim) if delim.is_word_delimiter() => found_tok,
None => found_tok, _ => None,
}
}
pub fn reserved_token(&mut self, tokens: &[Token]) -> ParseResult<Token, B::Error> {
match self.peek_reserved_token(tokens) {
Some(_) => Ok(self.iter.next().unwrap()),
None => {
let skip_one = {
let peeked = self.iter.peek();
tokens.iter().any(|t| Some(t) == peeked)
};
if skip_one { self.iter.next(); }
Err(self.make_unexpected_err())
},
}
}
pub fn reserved_word<'a>(&mut self, words: &'a [&str]) -> Result<&'a str, ()> {
match self.peek_reserved_word(words) {
Some(s) => { self.iter.next(); Ok(s) },
None => Err(()),
}
}
pub fn command_group(&mut self, cfg: CommandGroupDelimiters)
-> ParseResult<builder::CommandGroup<B::Command>, B::Error>
{
let group = try!(self.command_group_internal(cfg));
if group.commands.is_empty() {
Err(self.make_unexpected_err())
} else {
Ok(group)
}
}
fn command_group_internal(&mut self, cfg: CommandGroupDelimiters)
-> ParseResult<builder::CommandGroup<B::Command>, B::Error>
{
let found_delim = |slf: &mut Parser<_, _>| {
let found_exact = ! cfg.exact_tokens.is_empty() && slf.iter.peek()
.map(|peeked| cfg.exact_tokens.iter().any(|tok| tok == peeked))
.unwrap_or(false);
found_exact
|| slf.peek_reserved_word(cfg.reserved_words).is_some()
|| slf.peek_reserved_token(cfg.reserved_tokens).is_some()
};
let mut cmds = Vec::new();
let mut trailing_comments = Vec::new();
loop {
if found_delim(self) {
break;
}
let leading_comments = self.linebreak();
if found_delim(self) || self.iter.peek().is_none() {
debug_assert!(trailing_comments.is_empty());
trailing_comments = leading_comments;
break;
}
cmds.push(try!(self.complete_command_with_leading_comments(leading_comments)));
}
Ok(builder::CommandGroup {
commands: cmds,
trailing_comments: trailing_comments,
})
}
pub fn arithmetic_substitution(&mut self) -> ParseResult<DefaultArithmetic, B::Error> {
let mut exprs = Vec::new();
loop {
self.skip_whitespace();
exprs.push(try!(self.arith_assig()));
eat_maybe!(self, {
Comma => {};
_ => { break },
});
}
if exprs.len() == 1 {
Ok(exprs.pop().unwrap())
} else {
Ok(ast::Arithmetic::Sequence(exprs))
}
}
fn arith_assig(&mut self) -> ParseResult<DefaultArithmetic, B::Error> {
use ast::Arithmetic::*;
self.skip_whitespace();
let assig = {
let mut assig = false;
let mut peeked = self.iter.multipeek();
'assig_check: loop {
match peeked.peek_next() {
Some(&Dollar) => continue, Some(&Name(_)) => loop {
match peeked.peek_next() {
Some(&Whitespace(_)) => continue, Some(&Star) |
Some(&Slash) |
Some(&Percent) |
Some(&Plus) |
Some(&Dash) |
Some(&DLess) |
Some(&DGreat) |
Some(&Amp) |
Some(&Pipe) |
Some(&Caret) => assig = Some(&Equals) == peeked.peek_next(),
Some(&Equals) => assig = Some(&Equals) != peeked.peek_next(),
_ => {}
}
break 'assig_check;
},
_ => break 'assig_check,
}
}
assig
};
if !assig {
return self.arith_ternary();
}
let var = try!(self.arith_var());
self.skip_whitespace();
let op = match self.iter.next() {
Some(op@Star) |
Some(op@Slash) |
Some(op@Percent) |
Some(op@Plus) |
Some(op@Dash) |
Some(op@DLess) |
Some(op@DGreat) |
Some(op@Amp) |
Some(op@Pipe) |
Some(op@Caret) => { eat!(self, { Equals => {} }); op },
Some(op@Equals) => op,
_ => unreachable!(),
};
let value = Box::new(try!(self.arith_assig()));
let expr = match op {
Star => Box::new(Mult(Box::new(Var(var.clone())), value)),
Slash => Box::new(Div(Box::new(Var(var.clone())), value)),
Percent => Box::new(Modulo(Box::new(Var(var.clone())), value)),
Plus => Box::new(Add(Box::new(Var(var.clone())), value)),
Dash => Box::new(Sub(Box::new(Var(var.clone())), value)),
DLess => Box::new(ShiftLeft(Box::new(Var(var.clone())), value)),
DGreat => Box::new(ShiftRight(Box::new(Var(var.clone())), value)),
Amp => Box::new(BitwiseAnd(Box::new(Var(var.clone())), value)),
Pipe => Box::new(BitwiseOr(Box::new(Var(var.clone())), value)),
Caret => Box::new(BitwiseXor(Box::new(Var(var.clone())), value)),
Equals => value,
_ => unreachable!(),
};
Ok(Assign(var, expr))
}
fn arith_ternary(&mut self) -> ParseResult<DefaultArithmetic, B::Error> {
let guard = try!(self.arith_logical_or());
self.skip_whitespace();
eat_maybe!(self, {
Question => {
let body = try!(self.arith_ternary());
self.skip_whitespace();
eat!(self, { Colon => {} });
let els = try!(self.arith_ternary());
Ok(ast::Arithmetic::Ternary(Box::new(guard), Box::new(body), Box::new(els)))
};
_ => { Ok(guard) },
})
}
arith_parse!(arith_logical_or, arith_logical_and, OrIf => ast::Arithmetic::LogicalOr);
arith_parse!(arith_logical_and, arith_bitwise_or, AndIf => ast::Arithmetic::LogicalAnd);
arith_parse!(arith_bitwise_or, arith_bitwise_xor, Pipe => ast::Arithmetic::BitwiseOr);
arith_parse!(arith_bitwise_xor, arith_bitwise_and, Caret => ast::Arithmetic::BitwiseXor);
arith_parse!(arith_bitwise_and, arith_eq, Amp => ast::Arithmetic::BitwiseAnd);
#[inline]
fn arith_eq(&mut self) -> ParseResult<DefaultArithmetic, B::Error> {
let mut expr = try!(self.arith_ineq());
loop {
self.skip_whitespace();
let eq_type = eat_maybe!(self, {
Equals => { true },
Bang => { false };
_ => { break }
});
eat!(self, { Equals => {} });
let next = try!(self.arith_ineq());
expr = if eq_type {
ast::Arithmetic::Eq(Box::new(expr), Box::new(next))
} else {
ast::Arithmetic::NotEq(Box::new(expr), Box::new(next))
};
}
Ok(expr)
}
#[inline]
fn arith_ineq(&mut self) -> ParseResult<DefaultArithmetic, B::Error> {
let mut expr = try!(self.arith_shift());
loop {
self.skip_whitespace();
eat_maybe!(self, {
Less => {
let eq = eat_maybe!(self, { Equals => { true }; _ => { false } });
let next = try!(self.arith_shift());
expr = if eq {
ast::Arithmetic::LessEq(Box::new(expr), Box::new(next))
} else {
ast::Arithmetic::Less(Box::new(expr), Box::new(next))
};
},
Great => {
let eq = eat_maybe!(self, { Equals => { true }; _ => { false } });
let next = try!(self.arith_shift());
expr = if eq {
ast::Arithmetic::GreatEq(Box::new(expr), Box::new(next))
} else {
ast::Arithmetic::Great(Box::new(expr), Box::new(next))
};
};
_ => { break },
});
}
Ok(expr)
}
arith_parse!(arith_shift, arith_add,
DLess => ast::Arithmetic::ShiftLeft,
DGreat => ast::Arithmetic::ShiftRight
);
arith_parse!(arith_add, arith_mult,
Plus => ast::Arithmetic::Add,
Dash => ast::Arithmetic::Sub
);
arith_parse!(arith_mult, arith_pow,
Star => ast::Arithmetic::Mult,
Slash => ast::Arithmetic::Div,
Percent => ast::Arithmetic::Modulo
);
fn arith_pow(&mut self) -> ParseResult<DefaultArithmetic, B::Error> {
let expr = try!(self.arith_unary_misc());
self.skip_whitespace();
let double_star = {
let mut peeked = self.iter.multipeek();
peeked.peek_next() == Some(&Star) && peeked.peek_next() == Some(&Star)
};
if double_star {
eat!(self, { Star => {} });
eat!(self, { Star => {} });
Ok(ast::Arithmetic::Pow(Box::new(expr), Box::new(try!(self.arith_pow()))))
} else {
Ok(expr)
}
}
fn arith_unary_misc(&mut self) -> ParseResult<DefaultArithmetic, B::Error> {
self.skip_whitespace();
let expr = eat_maybe!(self, {
Bang => { ast::Arithmetic::LogicalNot(Box::new(try!(self.arith_unary_misc()))) },
Tilde => { ast::Arithmetic::BitwiseNot(Box::new(try!(self.arith_unary_misc()))) },
Plus => {
eat_maybe!(self, {
Dash => {
let next = try!(self.arith_unary_misc());
ast::Arithmetic::UnaryPlus(Box::new(ast::Arithmetic::UnaryMinus(Box::new(next))))
},
Plus => { ast::Arithmetic::PreIncr(try!(self.arith_var())) };
_ => { ast::Arithmetic::UnaryPlus(Box::new(try!(self.arith_unary_misc()))) }
})
},
Dash => {
eat_maybe!(self, {
Plus => {
let next = try!(self.arith_unary_misc());
ast::Arithmetic::UnaryMinus(Box::new(ast::Arithmetic::UnaryPlus(Box::new(next))))
},
Dash => { ast::Arithmetic::PreDecr(try!(self.arith_var())) };
_ => { ast::Arithmetic::UnaryMinus(Box::new(try!(self.arith_unary_misc()))) }
})
};
_ => { try!(self.arith_post_incr()) }
});
Ok(expr)
}
#[inline]
fn arith_post_incr(&mut self) -> ParseResult<DefaultArithmetic, B::Error> {
self.skip_whitespace();
eat_maybe!(self, {
ParenOpen => {
let expr = try!(self.arithmetic_substitution());
self.skip_whitespace();
eat!(self, { ParenClose => {} });
return Ok(expr);
}
});
let num = if let Some(&Literal(ref s)) = self.iter.peek() {
if s.starts_with("0x") || s.starts_with("0X") {
let num = &s[2..];
if num.is_empty() {
Some(0)
} else {
isize::from_str_radix(&s[2..], 16).ok()
}
} else if s.starts_with('0') {
isize::from_str_radix(s, 8).ok()
} else {
isize::from_str_radix(s, 10).ok()
}
} else {
None
};
let expr = match num {
Some(num) => {
self.iter.next();
ast::Arithmetic::Literal(num)
},
None => {
let var = try!(self.arith_var());
let post_incr = {
self.skip_whitespace();
let mut peeked = self.iter.multipeek();
match peeked.peek_next() {
Some(&Plus) => peeked.peek_next() == Some(&Plus),
Some(&Dash) => peeked.peek_next() == Some(&Dash),
_ => false,
}
};
if post_incr {
eat!(self, {
Plus => { eat!(self, { Plus => { ast::Arithmetic::PostIncr(var) } }) },
Dash => { eat!(self, { Dash => { ast::Arithmetic::PostDecr(var) } }) },
})
} else {
ast::Arithmetic::Var(var)
}
}
};
Ok(expr)
}
#[inline]
fn arith_var(&mut self) -> ParseResult<String, B::Error> {
self.skip_whitespace();
eat_maybe!(self, { Dollar => {} });
if let Some(&Name(_)) = self.iter.peek() {
if let Some(Name(n)) = self.iter.next() { Ok(n) } else { unreachable!() }
} else {
return Err(self.make_unexpected_err())
}
}
}
fn concat_tokens(tokens: &[Token]) -> String {
let len = tokens.iter().fold(0, |len, t| len + t.len());
let mut s = String::with_capacity(len);
s.extend(tokens.iter().map(Token::as_str));
s
}
#[cfg(test)]
mod tests {
use ast::*;
use ast::builder::Newline;
use ast::Command::*;
use ast::CompoundCommandKind::*;
use lexer::Lexer;
use parse::*;
fn make_parser(src: &str) -> DefaultParser<Lexer<::std::str::Chars>> {
DefaultParser::new(Lexer::new(src.chars()))
}
fn word(s: &str) -> TopLevelWord<String> {
TopLevelWord(ComplexWord::Single(Word::Simple(SimpleWord::Literal(String::from(s)))))
}
fn cmd_args_simple(cmd: &str, args: &[&str]) -> Box<DefaultSimpleCommand> {
let mut cmd_args = Vec::with_capacity(args.len() + 1);
cmd_args.push(RedirectOrCmdWord::CmdWord(word(cmd)));
cmd_args.extend(args.iter().map(|&a| RedirectOrCmdWord::CmdWord(word(a))));
Box::new(SimpleCommand {
redirects_or_env_vars: vec!(),
redirects_or_cmd_words: cmd_args,
})
}
fn cmd(cmd: &str) -> TopLevelCommand<String> {
cmd_args(cmd, &[])
}
fn cmd_args(cmd: &str, args: &[&str]) -> TopLevelCommand<String> {
TopLevelCommand(List(CommandList {
first: ListableCommand::Single(PipeableCommand::Simple(cmd_args_simple(cmd, args))),
rest: vec!(),
}))
}
#[test]
fn test_function_declaration_comments_before_body() {
use std::iter::repeat;
let cases_brace = vec!(
"function foo() #comment1\n\n#comment2\n { echo body; }",
"function foo () #comment1\n\n#comment2\n { echo body; }",
"function foo ( ) #comment1\n\n#comment2\n { echo body; }",
"function foo( ) #comment1\n\n#comment2\n { echo body; }",
"function foo #comment1\n\n#comment2\n { echo body; }",
"foo() #comment1\n\n#comment2\n { echo body; }",
"foo () #comment1\n\n#comment2\n { echo body; }",
"foo ( ) #comment1\n\n#comment2\n { echo body; }",
"foo( ) #comment1\n\n#comment2\n { echo body; }",
);
let cases_subshell = vec!(
"function foo() #comment1\n\n#comment2\n (echo body)",
"function foo #comment1\n\n#comment2\n (echo body)",
"foo() #comment1\n\n#comment2\n (echo body)",
"foo () #comment1\n\n#comment2\n (echo body)",
);
let comments = vec!(
Newline(Some(String::from("#comment1"))),
Newline(None),
Newline(Some(String::from("#comment2"))),
);
let name = String::from("foo");
let body = vec!(cmd_args("echo", &["body"]));
let body_brace = CompoundCommand {
kind: Brace(body.clone()),
io: vec!(),
};
let body_subshell = CompoundCommand {
kind: Subshell(body),
io: vec!(),
};
let iter = cases_brace.into_iter().zip(repeat(body_brace))
.chain(cases_subshell.into_iter().zip(repeat(body_subshell)))
.map(|(src, body)| (src, (name.clone(), comments.clone(), body)));
for (src, correct) in iter {
assert_eq!(correct, make_parser(src).function_declaration_internal().unwrap());
}
}
#[test]
fn test_word_preserve_trailing_whitespace() {
let mut p = make_parser("test ");
p.word_preserve_trailing_whitespace().unwrap();
assert!(p.iter.next().is_some());
}
#[test]
fn test_parameter_substitution_command_can_contain_comments() {
let param_subst = builder::SimpleWordKind::Subst(Box::new(
builder::ParameterSubstitutionKind::Command(builder::CommandGroup {
commands: vec!(cmd("foo")),
trailing_comments: vec!(Newline(Some("#comment".into()))),
})
));
assert_eq!(Ok(param_subst), make_parser("$(foo\n#comment\n)").parameter_raw());
}
#[test]
fn test_backticked_command_can_contain_comments() {
let cmd_subst = builder::SimpleWordKind::CommandSubst(builder::CommandGroup {
commands: vec!(cmd("foo")),
trailing_comments: vec!(Newline(Some("#comment".into()))),
});
assert_eq!(Ok(cmd_subst), make_parser("`foo\n#comment\n`").backticked_raw());
}
}