//* vim: set ft=rust: */
use crate::program::posix::{ast, lex};
grammar<'input>(text: &'input str);
extern {
type Location = usize;
type Error = lex::Error;
enum lex::Token<'input> {
" " => lex::Token::Space,
"\t" => lex::Token::Tab,
"\n" => lex::Token::Linefeed,
";" => lex::Token::Semi,
"&" => lex::Token::Amper,
"{" => lex::Token::LBrace,
"}" => lex::Token::RBrace,
"(" => lex::Token::LParen,
")" => lex::Token::RParen,
"`" => lex::Token::Backtick,
"!" => lex::Token::Bang,
"|" => lex::Token::Pipe,
"$" => lex::Token::Dollar,
"=" => lex::Token::Equals,
"\\" => lex::Token::Backslash,
"\"" => lex::Token::DoubleQuote,
"'" => lex::Token::SingleQuote,
">" => lex::Token::Great,
">>" => lex::Token::DGreat,
">&" => lex::Token::GreatAnd,
">|" => lex::Token::Clobber,
"<" => lex::Token::Less,
"<<" => lex::Token::DLess,
"<<-" => lex::Token::DLessDash,
"<&" => lex::Token::LessAnd,
"<>" => lex::Token::LessGreat,
"&&" => lex::Token::And,
"||" => lex::Token::Or,
"if" => lex::Token::If,
"then" => lex::Token::Then,
"else" => lex::Token::Else,
"elif" => lex::Token::Elif,
"fi" => lex::Token::Fi,
"export" => lex::Token::Export,
"WORD" => lex::Token::Word(<&'input str>),
"IO_NUMBER" => lex::Token::IoNumber(<usize>),
"{#" => lex::Token::HashLang(<&'input str>),
"{#!" => lex::Token::Shebang(<&'input str>),
"TEXT" => lex::Token::Text(<&'input str>),
}
}
pub Program: ast::Program = {
<p: Program> "\n" <l: Jobs> => p.append(&l),
<p: Program> "\n" => p,
<p: Program> ";" <g: Jobs> => p.append(&g),
<p: Program> ";" => p,
Jobs => <>,
}
Jobs: ast::Program = {
<cs: Command> "&" <j: Jobs> => {
j.insert(&ast::Command::Background(Box::new(cs)))
},
Job => ast::Program(vec![<>]),
}
Job: ast::Command = {
<cs: Command> "&" => {
ast::Command::Background(Box::new(cs))
},
Command => <>,
}
Compound: ast::Command = {
<cs: Command> ";" <c: Compound> => {
match c {
c @ ast::Command::Compound(_) => c.insert(&cs),
c => ast::Command::Compound(vec![cs, c]),
}
},
<cs: Command> ";" => {
ast::Command::Compound(vec![cs])
},
}
pub Command: ast::Command = {
// TODO #15: Hopefully in fixing #8 and #10 this can play nicely.
// NOTE: This can be successfully complied, but will break a doc tests.
<s: "{#!"> <t: "TEXT"> "}" => {
let i = ast::Interpreter::Shebang(s.into());
ast::Command::Lang(i, t.into())
},
<l: "{#"> <t: "TEXT"> "}" => {
let i = if l.is_empty() {
ast::Interpreter::Alternate
} else {
ast::Interpreter::HashLang(l.into())
};
ast::Command::Lang(i, t.into())
},
"$" "(" <p: Program> ")" => ast::Command::Subshell(Box::new(p)),
"$" "(" ")" => ast::Command::Subshell(Box::new(ast::Program(vec![]))),
"{" <c: Compound> "}" => c,
"if" <cond: Compound> "then" <then: Compound> <els: Else> "fi" => {
let left = ast::Command::And(Box::new(cond), Box::new(then));
ast::Command::Or(Box::new(left), Box::new(els))
},
"if" <cond: Compound> "then" <then: Compound> "fi" => {
ast::Command::And(Box::new(cond), Box::new(then))
},
<cs: Command> "&&" <p: Pipeline> => {
ast::Command::And(Box::new(cs), Box::new(p))
},
<cs: Command> "||" <p: Pipeline> => {
ast::Command::Or(Box::new(cs), Box::new(p))
},
Pipeline => <>,
}
Else: ast::Command = {
"elif" <elif: Compound> "then" <then: Compound> => {
ast::Command::And(Box::new(elif), Box::new(then))
},
"elif" <elif: Compound> "then" <then: Compound> <els: Else> => {
let left = ast::Command::And(Box::new(elif), Box::new(then));
ast::Command::Or(Box::new(left), Box::new(els))
},
"else" <els: Compound> => els,
}
Pipeline: ast::Command = {
"!" <ps: PipelineSeq> => {
ast::Command::Not(Box::new(ps))
},
<ps: PipelineSeq> => ps,
}
PipelineSeq: ast::Command = {
<ps: PipelineSeq> "|" "\n"* <c: Simple> => {
ast::Command::Pipeline(Box::new(ps), Box::new(c))
},
<c: Simple> => c,
}
Simple: ast::Command = {
<assignments: Assignment+> => {
ast::Command::Simple(assignments, vec![], vec![])
},
<redirects: Redirect+> => {
ast::Command::Simple(vec![], vec![], redirects)
},
<assignments: Assignment*>
<mut prefix: Redirect*>
<words: "WORD"+>
<mut suffix: Redirect*> => {
let redirects = { prefix.append(&mut suffix); prefix };
ast::Command::Simple(assignments, words.iter().map(|w| {
ast::Word(w.to_string())
}).collect(), redirects)
},
// Export support.
"export" <assignments: Assignment+> => {
ast::Command::Simple(assignments, vec![], vec![])
},
}
Redirect: ast::Redirect = {
File => <>,
// Here => <>,
<n: "IO_NUMBER"> <mut r: File> => { *r.fd() = n as i32; r },
// <n: "IO_NUMBER"> <mut r: Here> => { *r.fd() = n; r },
}
File: ast::Redirect = {
"<" <f: "WORD"> => ast::Redirect::Read {
n: 0,
duplicate: false,
filename: f.into(),
},
"<&" <f: "WORD"> => ast::Redirect::Read {
n: 0,
duplicate: true,
filename: f.into(),
},
">" <f: "WORD"> => ast::Redirect::Write {
n: 1,
duplicate: false,
clobber: false,
append: false,
filename: f.into(),
},
">&" <f: "WORD"> => ast::Redirect::Write {
n: 1,
duplicate: true,
clobber: false,
append: false,
filename: f.into(),
},
">>" <f: "WORD"> => ast::Redirect::Write {
n: 1,
duplicate: false,
clobber: false,
append: true,
filename: f.into(),
},
">|" <f: "WORD"> => ast::Redirect::Write {
n: 1,
duplicate: false,
clobber: true,
append: false,
filename: f.into(),
},
"<>" <f: "WORD"> => ast::Redirect::RW {
n: 0,
filename: f.into(),
},
}
// Here: ast::Redirect = {
// "<<" < <words: "WORD"+> => {
// let s = "".into();
// ast::Redirect::Here { n: 0, leading: true, string: s }
// },
// "<<-" <words: "WORD"+> => {
// let s = "".into();
// ast::Redirect::Here { n: 0, leading: false, string: s }
// },
// }
// pub Word: ast::Word = {
// <w: "WORD"> => ast::Word(w.into()),
// "$" <v: "WORD"> => ast::Word(var(v).unwrap_or(format!("${}", v))),
// }
Assignment: ast::Assignment = {
// TODO: Variable expansion.
<k: "WORD"> "=" <v: "WORD"> => ast::Assignment(k.into(), v.into()),
}