1use chumsky::prelude::*;
2
3use crate::ast::*;
4
5fn line_comment<'a>() -> impl Parser<'a, &'a str, (), extra::Err<Rich<'a, char>>> + Clone {
7 just("//")
8 .then(none_of('\n').repeated())
9 .ignored()
10}
11
12fn block_comment<'a>() -> impl Parser<'a, &'a str, (), extra::Err<Rich<'a, char>>> + Clone {
14 just("/*")
15 .then(any().and_is(just("*/").not()).repeated())
16 .then(just("*/"))
17 .ignored()
18}
19
20fn comment<'a>() -> impl Parser<'a, &'a str, (), extra::Err<Rich<'a, char>>> + Clone {
22 line_comment().or(block_comment())
23}
24
25fn ws<'a>() -> impl Parser<'a, &'a str, (), extra::Err<Rich<'a, char>>> + Clone {
27 choice((
30 text::whitespace().at_least(1).ignored(),
31 comment(),
32 ))
33 .repeated()
34 .ignored()
35}
36
37trait PaddedWithComments<'a, O>: Parser<'a, &'a str, O, extra::Err<Rich<'a, char>>> + Sized {
39 fn padded_with_comments(self) -> impl Parser<'a, &'a str, O, extra::Err<Rich<'a, char>>> + Clone
40 where
41 Self: Clone,
42 {
43 ws().ignore_then(self).then_ignore(ws())
44 }
45}
46
47impl<'a, O, P> PaddedWithComments<'a, O> for P where P: Parser<'a, &'a str, O, extra::Err<Rich<'a, char>>> {}
48
49fn value_parser<'a>() -> impl Parser<'a, &'a str, Value, extra::Err<Rich<'a, char>>> + Clone {
51 recursive(|value| {
52 let string = just('"')
54 .ignore_then(none_of('"').repeated())
55 .then_ignore(just('"').labelled("closing quote '\"'"))
56 .to_slice()
57 .map(|s: &str| Value::String(s.to_string()))
58 .labelled("string literal");
59
60 let boolean = text::keyword("true")
62 .to(Value::Boolean(true))
63 .or(text::keyword("false").to(Value::Boolean(false)))
64 .labelled("boolean (true or false)");
65
66 let number = just('-')
68 .or_not()
69 .then(text::int(10))
70 .then(just('.').then(text::digits(10)).or_not())
71 .to_slice()
72 .map(|s: &str| Value::Number(s.parse().unwrap()))
73 .labelled("number");
74
75 let reference = just('@')
77 .ignore_then(
78 text::ascii::ident()
79 .labelled("reference path (e.g., state.user)")
80 .then(just('.').ignore_then(text::ascii::ident()).repeated())
81 .to_slice(),
82 )
83 .map(|s: &str| Value::Reference(s.to_string()))
84 .labelled("reference (@state.* or @actions.*)");
85
86 let list = value
88 .clone()
89 .padded_with_comments()
90 .separated_by(just(','))
91 .allow_trailing()
92 .collect()
93 .delimited_by(just('['), just(']').labelled("closing bracket ']'"))
94 .map(Value::List)
95 .labelled("list [...]");
96
97 let map_entry = text::ascii::ident()
99 .labelled("map key")
100 .padded_with_comments()
101 .then_ignore(just(':').labelled("':' after map key"))
102 .then(value.clone().padded_with_comments().labelled("map value"));
103
104 let map = map_entry
105 .separated_by(just(','))
106 .allow_trailing()
107 .collect::<Vec<_>>()
108 .delimited_by(just('{'), just('}').labelled("closing brace '}'"))
109 .map(|entries: Vec<(&str, Value)>| {
110 Value::Map(entries.into_iter().map(|(k, v)| (k.to_string(), v)).collect())
111 })
112 .labelled("map {...}");
113
114 let identifier = text::ascii::ident()
116 .map(|s: &str| Value::String(s.to_string()))
117 .labelled("identifier");
118
119 choice((string, reference, boolean, number, list, map, identifier))
120 .labelled("value")
121 })
122}
123
124pub fn component_parser<'a>(
126) -> impl Parser<'a, &'a str, ComponentSpecification, extra::Err<Rich<'a, char>>> + Clone {
127 recursive(|component| {
128 let declaration_keyword = text::keyword("module")
130 .to(DeclarationType::Module)
131 .or(text::keyword("component").to(DeclarationType::ComponentKeyword))
132 .labelled("declaration keyword (module or component)")
133 .padded_with_comments()
134 .or_not();
135
136 let name = just('.')
138 .or_not()
139 .then(text::ascii::ident())
140 .to_slice()
141 .map(|s: &str| s.to_string())
142 .labelled("component name")
143 .padded_with_comments();
144
145 let value = value_parser();
147
148 let arg = text::ascii::ident()
149 .then_ignore(just(':').padded_with_comments())
150 .then(value.clone())
151 .map(|(key, value)| (Some(key.to_string()), value))
152 .labelled("named argument (key: value)")
153 .or(value.map(|value| (None, value)).labelled("positional argument"));
154
155 let args_with_parens = arg
157 .clone()
158 .padded_with_comments()
159 .separated_by(just(',').labelled("',' between arguments"))
160 .allow_trailing()
161 .collect::<Vec<_>>()
162 .delimited_by(
163 just('(').labelled("'(' to start arguments"),
164 just(')').labelled("closing parenthesis ')'")
165 )
166 .map(|args| {
167 let arguments = args
168 .into_iter()
169 .enumerate()
170 .map(|(i, (key, value))| match key {
171 Some(k) => Argument::Named { key: k, value },
172 None => Argument::Positioned { position: i, value },
173 })
174 .collect();
175 ArgumentList::new(arguments)
176 })
177 .labelled("argument list (...)");
178
179 let args = args_with_parens
181 .or_not()
182 .map(|opt| opt.unwrap_or_else(ArgumentList::empty));
183
184 let arg_parser = arg
186 .clone()
187 .padded_with_comments()
188 .separated_by(just(',').labelled("',' between arguments"))
189 .allow_trailing()
190 .collect::<Vec<_>>()
191 .delimited_by(
192 just('('),
193 just(')').labelled("closing parenthesis ')'")
194 )
195 .map(|args| {
196 let arguments = args
197 .into_iter()
198 .enumerate()
199 .map(|(i, (key, value))| match key {
200 Some(k) => Argument::Named { key: k, value },
201 None => Argument::Positioned { position: i, value },
202 })
203 .collect();
204 ArgumentList::new(arguments)
205 })
206 .or(empty().to(ArgumentList::empty()));
207
208 let children_block = just('{')
211 .padded_with_comments()
212 .ignore_then(
213 component
214 .clone()
215 .padded_with_comments()
216 .repeated()
217 .collect::<Vec<_>>()
218 )
219 .then_ignore(just('}').labelled("closing brace '}' for children block").padded_with_comments())
220 .labelled("children block {...}")
221 .or_not();
222
223 let applicators = just('.')
225 .ignore_then(text::ascii::ident().labelled("applicator name"))
226 .then(arg_parser.clone())
227 .map(|(name, args)| ApplicatorSpecification {
228 name: name.to_string(),
229 arguments: args,
230 children: vec![],
231 internal_id: String::new(),
232 })
233 .labelled("applicator (.name(...))")
234 .padded_with_comments()
235 .repeated()
236 .collect::<Vec<_>>();
237
238 declaration_keyword
239 .then(name)
240 .then(args)
241 .then(children_block)
242 .then(applicators)
243 .map(|((((decl_type, name), args), children), applicators)| {
244 let base_component = ComponentSpecification::new(
246 id_gen::NodeId::next().to_string(),
247 name.clone(),
248 args, vec![],
250 fold_applicators(children.unwrap_or_default()),
251 MetaData {
252 internal_id: String::new(),
253 name_range: 0..0,
254 block_range: None,
255 },
256 )
257 .with_declaration_type(decl_type.unwrap_or(DeclarationType::Component));
258
259 if applicators.is_empty() {
261 base_component
262 } else {
263 ComponentSpecification {
264 applicators,
265 ..base_component
266 }
267 }
268 })
269 .labelled("component")
270 })
271}
272
273fn fold_applicators(components: Vec<ComponentSpecification>) -> Vec<ComponentSpecification> {
276 let mut result: Vec<ComponentSpecification> = Vec::new();
277
278 for component in components {
279 if component.name.starts_with('.') && !result.is_empty() {
280 let mut owner: ComponentSpecification = result.pop().unwrap();
282 owner.applicators.push(component.to_applicator());
283 result.push(owner);
284 } else {
285 result.push(component);
286 }
287 }
288
289 result
290}
291
292pub fn import_parser<'a>() -> impl Parser<'a, &'a str, ImportStatement, extra::Err<Rich<'a, char>>> + Clone {
296 let import_keyword = text::keyword("import")
298 .labelled("'import' keyword")
299 .padded_with_comments();
300
301 let string_literal = just('"')
303 .ignore_then(none_of('"').repeated().to_slice())
304 .then_ignore(just('"').labelled("closing quote '\"'"))
305 .map(|s: &str| s.to_string())
306 .labelled("import path string");
307
308 let named_imports = text::ascii::ident()
310 .map(|s: &str| s.to_string())
311 .labelled("component name")
312 .padded_with_comments()
313 .separated_by(just(','))
314 .allow_trailing()
315 .collect::<Vec<String>>()
316 .delimited_by(
317 just('{').padded_with_comments(),
318 just('}').labelled("closing brace '}' for named imports").padded_with_comments()
319 )
320 .map(ImportClause::Named)
321 .labelled("named imports { ... }");
322
323 let default_import = text::ascii::ident()
325 .map(|s: &str| s.to_string())
326 .map(ImportClause::Default)
327 .labelled("default import name");
328
329 let import_clause = named_imports.or(default_import).padded_with_comments();
331
332 let from_keyword = text::keyword("from")
334 .labelled("'from' keyword")
335 .padded_with_comments();
336
337 import_keyword
339 .ignore_then(import_clause)
340 .then_ignore(from_keyword)
341 .then(string_literal.padded_with_comments())
342 .map(|(clause, source_str)| {
343 let source = if source_str.starts_with("http://") || source_str.starts_with("https://") {
345 ImportSource::Url(source_str)
346 } else {
347 ImportSource::Local(source_str)
348 };
349 ImportStatement::new(clause, source)
350 })
351 .labelled("import statement")
352}
353
354pub fn document_parser<'a>() -> impl Parser<'a, &'a str, Document, extra::Err<Rich<'a, char>>> + Clone {
356 let imports = import_parser()
358 .padded_with_comments()
359 .repeated()
360 .collect::<Vec<ImportStatement>>();
361
362 let components = component_parser()
364 .padded_with_comments()
365 .repeated()
366 .collect::<Vec<ComponentSpecification>>();
367
368 imports
370 .then(components)
371 .map(|(imports, components)| Document::new(imports, components))
372}
373
374pub fn parse_components(
376 input: &str,
377) -> Result<Vec<ComponentSpecification>, Vec<Rich<char>>> {
378 component_parser()
379 .padded_with_comments()
380 .repeated()
381 .collect()
382 .then_ignore(end())
383 .parse(input)
384 .into_result()
385}
386
387pub fn parse_component(
389 input: &str,
390) -> Result<ComponentSpecification, Vec<Rich<char>>> {
391 component_parser()
392 .padded_with_comments()
393 .then_ignore(end())
394 .parse(input)
395 .into_result()
396}
397
398pub fn parse_document(
400 input: &str,
401) -> Result<Document, Vec<Rich<char>>> {
402 document_parser()
403 .padded_with_comments()
404 .then_ignore(end())
405 .parse(input)
406 .into_result()
407}
408
409pub fn parse_import(
411 input: &str,
412) -> Result<ImportStatement, Vec<Rich<char>>> {
413 import_parser()
414 .padded_with_comments()
415 .then_ignore(end())
416 .parse(input)
417 .into_result()
418}
419
420mod id_gen {
422 use std::sync::atomic::{AtomicUsize, Ordering};
423
424 static COUNTER: AtomicUsize = AtomicUsize::new(0);
425
426 pub struct NodeId(usize);
427
428 impl NodeId {
429 pub fn next() -> Self {
431 NodeId(COUNTER.fetch_add(1, Ordering::SeqCst))
432 }
433
434 pub fn to_string(&self) -> String {
435 format!("id-{}", self.0)
436 }
437 }
438}