august_build/
parser.rs

1use chumsky::{combinator::DelimitedBy, prelude::*, primitive::Just};
2use std::{fmt::Display, hash::Hash, ops::Range};
3
4use crate::{
5    lexer::{Delim, Token},
6    Command, EnvCommand, FsCommand, IoCommand, Pragma,
7};
8
9#[derive(Debug, Clone)]
10pub struct Spanned<T>(T, Range<usize>);
11
12impl<T> Spanned<T> {
13    pub fn new(val: T) -> Self {
14        Self(val, 0..0)
15    }
16
17    pub fn span(&self) -> Range<usize> {
18        self.1.clone()
19    }
20
21    pub fn inner(&self) -> &T {
22        &self.0
23    }
24
25    pub fn map(mut self, mut f: impl FnMut(T) -> T) -> Self {
26        self.0 = f(self.0);
27        self
28    }
29}
30
31impl<T: Clone> Spanned<T> {
32    pub fn inner_owned(&self) -> T {
33        self.0.clone()
34    }
35}
36
37impl<T: PartialEq> PartialEq for Spanned<T> {
38    fn eq(&self, other: &Self) -> bool {
39        self.0 == other.0
40    }
41}
42
43impl<T: PartialEq<T>> PartialEq<T> for Spanned<T> {
44    fn eq(&self, other: &T) -> bool {
45        self.0 == *other
46    }
47}
48
49impl<T> Eq for Spanned<T> where T: PartialEq {}
50
51impl<T: Display> Display for Spanned<T> {
52    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
53        self.0.fmt(f)
54    }
55}
56
57impl<T: Hash> Hash for Spanned<T> {
58    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
59        self.0.hash(state);
60    }
61}
62
63#[derive(Debug, Clone, PartialEq, Eq)]
64pub enum AST {
65    Expose(Pragma, Spanned<String>),
66    Unit(Spanned<String>, Vec<Command>),
67    Err,
68}
69
70pub fn parser() -> impl Parser<Token, Vec<AST>, Error = Simple<Token>> {
71    choice((expose(), unit()))
72        .recover_with(skip_parser(
73            choice((expose().ignored(), unit().ignored()))
74                .not()
75                .repeated()
76                .ignore_then(any().rewind())
77                .to(AST::Err),
78        ))
79        .repeated()
80}
81
82pub fn expose() -> impl Parser<Token, AST, Error = Simple<Token>> + Clone {
83    just(Token::Expose)
84        .ignore_then(ident())
85        .then_ignore(just(Token::As))
86        .then(
87            with_ident("test")
88                .to(Pragma::Test)
89                .or(with_ident("build").to(Pragma::Build)),
90        )
91        .map(|(src, dst)| AST::Expose(dst, src))
92        .labelled("expose declaration")
93}
94
95pub fn unit() -> impl Parser<Token, AST, Error = Simple<Token>> {
96    just(Token::Unit)
97        .ignore_then(ident())
98        .then(command().repeated().curly_delimited())
99        .map(|(name, cmds)| AST::Unit(name, cmds))
100        .labelled("unit definition")
101}
102
103pub fn command() -> impl Parser<Token, Command, Error = Simple<Token>> {
104    recursive(|cmd| {
105        choice((
106            module_prefix("FS")
107                .ignore_then(fs_command())
108                .map(Command::Fs),
109            module_prefix("IO")
110                .ignore_then(io_command())
111                .map(Command::Io),
112            module_prefix("ENV")
113                .ignore_then(env_command())
114                .map(Command::Env),
115            with_ident("depends_on")
116                .ignore_then(ident().separated_by(just(Token::Comma)).round_delimited())
117                .map(Command::DependsOn),
118            with_ident("do")
119                .ignore_then(ident().separated_by(just(Token::Comma)).round_delimited())
120                .map(Command::Do),
121            with_ident("meta")
122                .to(Token::Attr)
123                .or(just(Token::Attr))
124                .ignore_then(
125                    just(Token::Attr)
126                        .ignore_then(ident())
127                        .then(str().map(|s| s.0))
128                        .repeated()
129                        .round_delimited()
130                        .collect()
131                        .map(Command::Meta),
132                ),
133            with_ident("exec")
134                .to(Token::Tilde)
135                .or(just(Token::Tilde))
136                .ignore_then(
137                    ident()
138                        .or(str())
139                        .or(select! {|span| Token::RawIdent(i) => Spanned(i, span)})
140                        .repeated()
141                        .round_delimited()
142                        .map(Command::Exec),
143                ),
144            with_ident("concurrent").ignore_then(
145                cmd.map(Box::new)
146                    .repeated()
147                    .curly_delimited()
148                    .map(Command::Concurrent),
149            ),
150        ))
151    })
152    .labelled("command call")
153}
154
155fn fs_command() -> impl Parser<Token, FsCommand, Error = Simple<Token>> {
156    choice((
157        with_ident("create")
158            .ignore_then(str().round_delimited())
159            .map(FsCommand::Create),
160        with_ident("create_dir")
161            .ignore_then(str().round_delimited())
162            .map(FsCommand::CreateDir),
163        with_ident("remove")
164            .ignore_then(str().round_delimited())
165            .map(FsCommand::Remove),
166        with_ident("copy")
167            .ignore_then(
168                str()
169                    .then_ignore(just(Token::Comma))
170                    .then(str())
171                    .round_delimited(),
172            )
173            .map(|(src, dst)| FsCommand::Copy(src, dst)),
174        with_ident("copy_to")
175            .ignore_then(
176                str()
177                    .then_ignore(just(Token::Comma))
178                    .then(binary_map())
179                    .round_delimited(),
180            )
181            .map(|(src, paths)| FsCommand::CopyTo(src, paths)),
182        with_ident("move")
183            .ignore_then(
184                str()
185                    .then_ignore(just(Token::Comma))
186                    .then(str())
187                    .round_delimited(),
188            )
189            .map(|(src, dst)| FsCommand::Move(src, dst)),
190        with_ident("move_to")
191            .ignore_then(
192                str()
193                    .then_ignore(just(Token::Comma))
194                    .then(binary_map())
195                    .round_delimited(),
196            )
197            .map(|(src, paths)| FsCommand::MoveTo(src, paths)),
198        with_ident("print_file")
199            .ignore_then(str().round_delimited())
200            .map(FsCommand::PrintFile),
201        with_ident("eprint_file")
202            .ignore_then(str().round_delimited())
203            .map(FsCommand::EPrintFile),
204    ))
205}
206
207fn io_command() -> impl Parser<Token, IoCommand, Error = Simple<Token>> {
208    choice((
209        with_ident("println")
210            .ignore_then(str().round_delimited())
211            .map(IoCommand::PrintLn),
212        with_ident("print")
213            .ignore_then(str().round_delimited())
214            .map(IoCommand::Print),
215        with_ident("eprintln")
216            .ignore_then(str().round_delimited())
217            .map(IoCommand::EPrintLn),
218        with_ident("eprint")
219            .ignore_then(str().round_delimited())
220            .map(IoCommand::EPrint),
221    ))
222}
223
224fn env_command() -> impl Parser<Token, EnvCommand, Error = Simple<Token>> {
225    choice((
226        with_ident("set_var")
227            .ignore_then(
228                str()
229                    .then_ignore(just(Token::Comma))
230                    .then(str())
231                    .round_delimited(),
232            )
233            .map(|(var, val)| EnvCommand::SetVar(var, val)),
234        with_ident("remove_var")
235            .ignore_then(str().round_delimited())
236            .map(EnvCommand::RemoveVar),
237        with_ident("path_push")
238            .ignore_then(str().round_delimited())
239            .map(EnvCommand::PathPush),
240        with_ident("path_remove")
241            .ignore_then(str().round_delimited())
242            .map(EnvCommand::PathRemove),
243    ))
244}
245
246fn module_prefix(s: impl AsRef<str>) -> impl Parser<Token, (), Error = Simple<Token>> {
247    select! { |span| Token::Ident(i) if i.eq_ignore_ascii_case(s.as_ref())  => Spanned(i, span) }
248        .ignored()
249        .then_ignore(just(Token::DoubleColon))
250        .or_not()
251        .to(())
252}
253
254// vvv Waiting for TAIT
255#[allow(clippy::type_complexity)]
256fn binary_map(
257) -> impl Parser<Token, Vec<(Spanned<String>, Option<Spanned<String>>)>, Error = Simple<Token>> + Clone
258{
259    str()
260        .then(just(Token::DoubleArrow).ignore_then(str()).or_not())
261        .separated_by(just(Token::Comma))
262        .square_delimited()
263}
264
265fn with_ident(
266    s: impl Into<String> + Clone,
267) -> impl Parser<Token, Spanned<String>, Error = Simple<Token>> + Clone {
268    select! { |span| Token::Ident(i) if i == s.clone().into() => Spanned(i, span) }
269}
270
271fn ident() -> impl Parser<Token, Spanned<String>, Error = Simple<Token>> + Clone {
272    select! { |span| Token::Ident(i) => Spanned(i, span) }
273}
274
275fn str() -> impl Parser<Token, Spanned<String>, Error = Simple<Token>> + Clone {
276    select! { |span| Token::String(i) => Spanned(i, span) }
277}
278
279/// Alias for the return type of [`ParserExt`]'s methods
280pub type TokenDelim<T> = DelimitedBy<
281    T,
282    Just<Token, Token, Simple<Token>>,
283    Just<Token, Token, Simple<Token>>,
284    Token,
285    Token,
286>;
287
288/// Utility trait for chumsky's Parser
289///
290/// Provides methods for wrapping [`Token`] groups in bracket delimiters
291pub trait ParserExt<O>: Parser<Token, O> + Sized {
292    fn round_delimited(self) -> TokenDelim<Self>;
293    fn square_delimited(self) -> TokenDelim<Self>;
294    fn curly_delimited(self) -> TokenDelim<Self>;
295    fn arrow_delimited(self) -> TokenDelim<Self>;
296}
297
298impl<O, P> ParserExt<O> for P
299where
300    P: Parser<Token, O, Error = Simple<Token>>,
301{
302    fn round_delimited(self) -> TokenDelim<Self> {
303        self.delimited_by(
304            just(Token::OpenDelim(Delim::Round)),
305            just(Token::CloseDelim(Delim::Round)),
306        )
307    }
308
309    fn square_delimited(self) -> TokenDelim<Self> {
310        self.delimited_by(
311            just(Token::OpenDelim(Delim::Square)),
312            just(Token::CloseDelim(Delim::Square)),
313        )
314    }
315
316    fn curly_delimited(self) -> TokenDelim<Self> {
317        self.delimited_by(
318            just(Token::OpenDelim(Delim::Curly)),
319            just(Token::CloseDelim(Delim::Curly)),
320        )
321    }
322
323    fn arrow_delimited(self) -> TokenDelim<Self> {
324        self.delimited_by(
325            just(Token::OpenDelim(Delim::Arrow)),
326            just(Token::CloseDelim(Delim::Arrow)),
327        )
328    }
329}