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#[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
279pub type TokenDelim<T> = DelimitedBy<
281 T,
282 Just<Token, Token, Simple<Token>>,
283 Just<Token, Token, Simple<Token>>,
284 Token,
285 Token,
286>;
287
288pub 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}