1use std::fmt;
3use std::str::FromStr;
4
5mod error;
6mod parse;
7mod validate;
8pub mod visit;
9
10#[cfg(test)]
11mod tests;
12
13pub use error::{ParseError, ParseErrorKind};
14
15pub const KEYWORDS: &[&str] = &[
16 "begin", "end", "if", "else", "switch", "case", "for", "in", "and", "or", "not", "function",
17];
18
19pub type Pos = u32;
20
21#[derive(Debug, Clone, PartialEq)]
22pub struct SourceFile {
23 pub stmts: Vec<Stmt>,
24}
25
26#[derive(Debug, Clone, PartialEq)]
27pub enum Stmt {
28 Command(Pos, Words),
29 Block(Pos, Vec<Stmt>),
30 If(Pos, Box<Stmt>, Box<Stmt>, Option<Box<Stmt>>),
31 For(Pos, Word, Words, Box<Stmt>),
32 While(Pos, Box<Stmt>, Box<Stmt>),
33 Break(Pos),
34 Continue(Pos),
35 Function(Pos, Words, Box<Stmt>),
36 Return(Pos, Option<Word>),
37 Switch(Pos, Word, Vec<SwitchCase>),
38
39 Redirect(Pos, Box<Stmt>, Vec<Redirect>),
40 Pipe(Pos, Vec<(Stmt, RedirectPort)>, Box<Stmt>),
41
42 Not(Pos, Box<Stmt>),
43 UnaryAnd(Pos, Box<Stmt>),
44 UnaryOr(Pos, Box<Stmt>),
45 BinaryAnd(Pos, Box<Stmt>, Box<Stmt>),
46 BinaryOr(Pos, Box<Stmt>, Box<Stmt>),
47}
48
49impl Stmt {
50 pub fn pos(&self) -> u32 {
51 match self {
52 Stmt::Command(pos, ..)
53 | Stmt::Block(pos, ..)
54 | Stmt::If(pos, ..)
55 | Stmt::For(pos, ..)
56 | Stmt::While(pos, ..)
57 | Stmt::Break(pos, ..)
58 | Stmt::Continue(pos, ..)
59 | Stmt::Function(pos, ..)
60 | Stmt::Return(pos, ..)
61 | Stmt::Switch(pos, ..)
62 | Stmt::Redirect(pos, ..)
63 | Stmt::Pipe(pos, ..)
64 | Stmt::Not(pos, ..)
65 | Stmt::UnaryAnd(pos, ..)
66 | Stmt::UnaryOr(pos, ..)
67 | Stmt::BinaryAnd(pos, ..)
68 | Stmt::BinaryOr(pos, ..) => *pos,
69 }
70 }
71}
72
73#[derive(Debug, Clone, PartialEq)]
74pub struct SwitchCase {
75 pub globs: Vec<Word>,
76 pub body: Stmt,
77}
78
79#[derive(Debug, Clone, PartialEq)]
80pub struct Redirect {
81 pub port: RedirectPort,
82 pub mode: RedirectMode,
83 pub dest: Word,
84}
85
86impl Redirect {
87 pub fn new(port: RedirectPort, mode: RedirectMode, dest: Word) -> Self {
88 Self { port, mode, dest }
89 }
90}
91
92#[derive(Clone, Copy, PartialEq, Eq)]
93pub struct RedirectPort(u16);
94
95impl RedirectPort {
96 pub const STDIN: Self = Self(0);
97 pub const STDOUT: Self = Self(1);
98 pub const STDERR: Self = Self(2);
99 pub const STDOUT_STDERR: Self = Self(!0);
100}
101
102impl fmt::Debug for RedirectPort {
103 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
104 f.write_str(match *self {
105 Self::STDIN => "STDIN",
106 Self::STDOUT => "STDOUT",
107 Self::STDERR => "STDERR",
108 Self::STDOUT_STDERR => "STDOUT_STDERR",
109 _ => return f.debug_tuple("RedirectPort").field(&self.0).finish(),
110 })
111 }
112}
113
114impl FromStr for RedirectPort {
115 type Err = ();
116
117 fn from_str(s: &str) -> Result<Self, Self::Err> {
118 let v = s.parse::<u16>().ok().filter(|&p| p != !0).ok_or(())?;
119 Ok(Self(v))
120 }
121}
122
123impl RedirectPort {
124 pub fn port(self) -> Option<u16> {
125 (self.0 != !0).then_some(self.0)
126 }
127}
128
129#[derive(Debug, Clone, Copy, PartialEq)]
130pub enum RedirectMode {
131 Read,
132 ReadOrNull,
133 ReadFd,
134
135 Write,
136 WriteNoClobber,
137 Append,
138 WriteFd,
139}
140
141impl RedirectMode {
142 #[must_use]
143 pub fn default_port(self) -> RedirectPort {
144 match self {
145 RedirectMode::Read | RedirectMode::ReadOrNull | RedirectMode::ReadFd => {
146 RedirectPort::STDIN
147 }
148 RedirectMode::Write
149 | RedirectMode::WriteNoClobber
150 | RedirectMode::Append
151 | RedirectMode::WriteFd => RedirectPort::STDOUT,
152 }
153 }
154}
155
156pub type Words = Vec<Word>;
157
158#[derive(Debug, Clone, PartialEq)]
159pub enum Word {
160 Simple(String),
161 Complex(Vec<WordFrag>),
162}
163
164impl From<WordFrag> for Word {
165 fn from(frag: WordFrag) -> Self {
166 match frag {
167 WordFrag::Literal(s) => Word::Simple(s),
168 frag => Word::Complex(vec![frag]),
169 }
170 }
171}
172
173impl Word {
174 fn append(self, frag: WordFrag) -> Self {
175 let v = match self {
176 Word::Simple(lit) => vec![WordFrag::Literal(lit), frag],
177 Word::Complex(mut frags) => {
178 frags.push(frag);
179 frags
180 }
181 };
182 Self::Complex(v)
183 }
184}
185
186#[derive(Debug, Clone, PartialEq)]
187pub enum WordFrag {
188 Literal(String),
189 Variable { name: String, deref: u8 },
190 VariableNoSplit { name: String, deref: u8 },
191 Command(Stmt),
192 CommandNoSplit(Stmt),
193
194 Brace(Vec<Word>),
195
196 Home { slash: bool },
197 Wildcard,
198 WildcardRecursive,
199}
200
201pub fn parse_source(src: &str) -> Result<SourceFile, Vec<ParseError>> {
202 let mut file = parse::parse_source(src).map_err(|err| vec![err])?;
203 validate::validate_fixup(&mut file)?;
204 Ok(file)
205}