ghoti_syntax/
lib.rs

1//! WIP
2use 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}