use crate::*;
use crate::parse::{Token, ParseError as E, ParseErrorKind as K};
grammar<'i>;
extern {
type Location = usize;
type Error = E;
enum Token<'i> {
NEWLINE => Token::Newline,
NOSP => Token::Join,
WORD => Token::Word(<&'i str>),
ESCAPE => Token::Escape(<char>),
STRING_FRAGMENT => Token::Verbatim(<&'i str>),
"$VAR" => Token::Variable(<&'i str>),
"if" => Token::If,
"else" => Token::Else,
"for" => Token::For,
"in" => Token::In,
"while" => Token::While,
"break" => Token::Break,
"continue" => Token::Continue,
"function" => Token::Function,
"return" => Token::Return,
"begin" => Token::Begin,
"end" => Token::End,
"switch" => Token::Switch,
"case" => Token::Case,
"and" => Token::And,
"or" => Token::Or,
"not" => Token::Not,
"$(" => Token::DollarLParen,
"(" => Token::LParen,
")" => Token::RParen,
"{" => Token::LBrace,
"}" => Token::RBrace,
"," => Token::Comma,
"|" => Token::Pipe,
"&|" => Token::AmpPipe,
";" => Token::Semi,
"*" => Token::Star,
"**" => Token::StarStar,
"~" => Token::Tilde,
"~/" => Token::TildeSlash,
"\"" => Token::DQuote,
"'" => Token::SQuote,
"&&" => Token::AmpAmp,
"||" => Token::PipePipe,
"&>" => Token::AmpGt,
">" => Token::Gt(<&'i str>),
">&" => Token::GtAmp(<&'i str>),
">?" => Token::GtQus(<&'i str>),
"<" => Token::Lt(<&'i str>),
"<&" => Token::LtAmp(<&'i str>),
"<?" => Token::LtQus(<&'i str>),
">>" => Token::GtGt(<&'i str>),
">|" => Token::GtPipe(<&'i str>),
}
}
pub SourceFile: SourceFile =
<StmtList> => SourceFile { stmts: <> };
StmtList: Vec<Stmt> = {
=> Vec::new(),
<Stmt> => vec![<>],
<mut ls:StmtList> EOS <s:Stmt?> => { ls.extend(s); ls },
};
EOS: () = { NEWLINE, ";" };
Stmt: Stmt = {
#[precedence(level = "0")]
<Control> => <>,
#[precedence(level = "0")]
<pos:@L> <ws:Word+> => Stmt::Command(pos as _, ws),
#[precedence(level = "1")]
<pos:@L> <mut s:Stmt> <r:Redirect> => {
match &mut s {
Stmt::Redirect(_, _, rs) => { rs.push(r); s },
_ => Stmt::Redirect(pos as _, s.into(), vec![r]),
}
},
#[precedence(level = "2")]
#[assoc(side = "right")]
<lhs:Stmt> <pos:@L> <port:PipeOp> <mut rhs:Stmt> => {
match &mut rhs {
Stmt::Pipe(_, pipes, _) => {
// FIXME: O(n^2) time.
pipes.insert(0, (lhs, port));
rhs
}
_ => Stmt::Pipe(pos as _, vec![(lhs, port)], rhs.into()),
}
},
#[precedence(level = "3")]
<pos:@L> "not" <s:Stmt> => Stmt::Not(pos as _, s.into()),
#[precedence(level = "4")]
#[assoc(side = "right")]
<pos:@L> <lhs:Stmt> "and" <rhs:Stmt> => Stmt::BinaryAnd(pos as _, lhs.into(), rhs.into()),
#[precedence(level = "4")]
#[assoc(side = "right")]
<pos:@L> <lhs:Stmt> "&&" <rhs:Stmt> => Stmt::BinaryAnd(pos as _, lhs.into(), rhs.into()),
#[precedence(level = "5")]
#[assoc(side = "right")]
<pos:@L> <lhs:Stmt> "or" <rhs:Stmt> => Stmt::BinaryOr(pos as _, lhs.into(), rhs.into()),
#[precedence(level = "5")]
#[assoc(side = "right")]
<pos:@L> <lhs:Stmt> "||" <rhs:Stmt> => Stmt::BinaryOr(pos as _, lhs.into(), rhs.into()),
#[precedence(level = "6")]
<pos:@L> "and" <s:Stmt> => Stmt::UnaryAnd(pos as _, s.into()),
#[precedence(level = "6")]
<pos:@L> "or" <s:Stmt> => Stmt::UnaryOr(pos as _, s.into()),
};
PipeOp: RedirectPort = {
"|" => RedirectPort::STDOUT,
"&|" => RedirectPort::STDOUT_STDERR,
<lpos:@L> <port:">|"> =>? {
port[..port.len() - 2].parse()
.map_err(|_| E::new(lpos, lpos + port.len() - 2, K::InvalidRedirectPort).into())
}
}
Redirect: Redirect = {
<lpos:@L> <tup:RedirectOp> <dest:Word> =>? {
let (port_str, mode) = tup;
let port = if port_str == "&" {
RedirectPort::STDOUT_STDERR
} else if port_str.is_empty() {
mode.default_port()
} else {
port_str.parse().map_err(|_| E::new(lpos, lpos + port_str.len(), K::InvalidRedirectPort))?
};
Ok(Redirect { port, mode, dest })
},
};
RedirectOp: (&'i str, RedirectMode) = {
"&>" => ("&", RedirectMode::Write),
<s:"<" > => (&s[..s.len() - 1], RedirectMode::Read),
<s:"<?"> => (&s[..s.len() - 2], RedirectMode::ReadOrNull),
<s:"<&"> => (&s[..s.len() - 2], RedirectMode::ReadFd),
<s:">" > => (&s[..s.len() - 1], RedirectMode::Write),
<s:">?"> => (&s[..s.len() - 2], RedirectMode::WriteNoClobber),
<s:">&"> => (&s[..s.len() - 2], RedirectMode::WriteFd),
<s:">>"> => (&s[..s.len() - 2], RedirectMode::Append),
};
StmtBlock: Stmt =
<pos:@L> <ls:StmtList> => Stmt::Block(pos as _, ls);
Control: Stmt = {
<pos:@L> "begin" EOS <ls:StmtList> "end" => Stmt::Block(pos as _, ls),
<If> => <>,
<pos:@L> "for" <var:Word> "in" <seq:Word*> EOS <body:StmtBlock> "end" => Stmt::For(pos as _, var, seq, body.into()),
<pos:@L> "while" <cond:Stmt> EOS <body:StmtBlock> "end" => Stmt::While(pos as _, cond.into(), body.into()),
<pos:@L> "break" => Stmt::Break(pos as _),
<pos:@L> "continue" => Stmt::Continue(pos as _),
<pos:@L> "function" <def:Word*> EOS <body:StmtBlock> "end" => Stmt::Function(pos as _, def, body.into()),
<pos:@L> "return" <w:Word?> => Stmt::Return(pos as _, w),
<pos:@L> "switch" <testee:Word> EOS <cases:SwitchCase*> "end" => Stmt::Switch(pos as _, testee, cases),
};
If: Stmt = {
<pos:@L> "if" <cond:Stmt> EOS <then:StmtBlock> "end" =>
Stmt::If(pos as _, cond.into(), then.into(), None),
<pos:@L> "if" <cond:Stmt> EOS <then:StmtBlock> "else" <else_:If> =>
Stmt::If(pos as _, cond.into(), then.into(), Some(else_.into())),
<pos:@L> "if" <cond:Stmt> EOS <then:StmtBlock> "else" EOS <else_:StmtBlock> "end" =>
Stmt::If(pos as _, cond.into(), then.into(), Some(else_.into())),
};
SwitchCase: SwitchCase =
"case" <globs:Word*> EOS <body:StmtBlock> => SwitchCase { globs, body };
Word: Word = {
"~" => Word::Complex(vec![WordFrag::Home { slash: false }]),
<WordJoined>,
};
WordJoined: Word = {
<SingleWordFrag> => Word::from(<>),
<String> => Word::Complex(<>),
<w:WordJoined> NOSP <frag:SingleWordFrag> => w.append(frag),
<w:WordJoined> NOSP <frags:String> => {
let fs = match w {
Word::Simple(w) => std::iter::once(WordFrag::Literal(w)).chain(frags).collect(),
Word::Complex(mut fs) => { fs.extend(frags); fs },
};
Word::Complex(fs)
}
};
SingleWordFrag: WordFrag = {
"~/" => WordFrag::Home { slash: true },
<WORD> => WordFrag::Literal(<>.into()),
<lpos:@L> <s:"$VAR"> <rpos:@R> =>? {
let name = s.trim_start_matches('$');
let deref = u8::try_from(s.len() - name.len() - 1).map_err(|_| E::new(lpos, rpos, K::TooManyDeref))?;
Ok(WordFrag::Variable { name: name.into(), deref })
},
<ESCAPE> => WordFrag::Literal(<>.into()),
"*" => WordFrag::Wildcard,
"**" => WordFrag::WildcardRecursive,
"$(" <StmtBlock> ")" => WordFrag::Command(<>),
"(" <StmtBlock> ")" => WordFrag::Command(<>),
<BraceWord>,
};
String: Vec<WordFrag> = {
"\"" <DQuoteFrag*> "\"" => <>,
"'" <SQuoteFrag*> "'" => <>,
};
DQuoteFrag: WordFrag = {
<lpos:@L> <s:"$VAR"> <rpos:@R> =>? {
let name = s.trim_start_matches('$');
let deref = u8::try_from(s.len() - name.len() - 1).map_err(|_| E::new(lpos, rpos, K::TooManyDeref))?;
Ok(WordFrag::VariableNoSplit { name: name.into(), deref })
},
"$(" <StmtBlock> ")" => WordFrag::CommandNoSplit(<>),
<STRING_FRAGMENT> => WordFrag::Literal(<>.into()),
};
SQuoteFrag: WordFrag =
<STRING_FRAGMENT> => WordFrag::Literal(<>.into());
BraceWord: WordFrag =
"{" <mut alts:(<BraceAltWord> ",")*> <last:BraceAltWord> "}" => {
alts.push(last);
WordFrag::Brace(alts)
};
BraceAltWord: Word = <BraceAlt> => Word::Complex(<>);
BraceAlt: Vec<WordFrag> = {
=> Vec::new(),
<mut frags:BraceAlt> <s:STRING_FRAGMENT> => { frags.push(WordFrag::Literal(s.into())); frags },
// NOSP is not emitted inside braces.
<mut frags:BraceAlt> <w:SingleWordFrag> => { frags.push(w); frags },
<mut frags:BraceAlt> <s:String> => { frags.extend(s); frags },
};