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 text::whitespace()
28 .then(comment().then(text::whitespace()).repeated())
29 .ignored()
30}
31
32trait PaddedWithComments<'a, O>: Parser<'a, &'a str, O, extra::Err<Rich<'a, char>>> + Sized {
34 fn padded_with_comments(self) -> impl Parser<'a, &'a str, O, extra::Err<Rich<'a, char>>> + Clone
35 where
36 Self: Clone,
37 {
38 ws().ignore_then(self).then_ignore(ws())
39 }
40}
41
42impl<'a, O, P> PaddedWithComments<'a, O> for P where P: Parser<'a, &'a str, O, extra::Err<Rich<'a, char>>> {}
43
44fn value_parser<'a>() -> impl Parser<'a, &'a str, Value, extra::Err<Rich<'a, char>>> + Clone {
46 recursive(|value| {
47 let string = just('"')
49 .ignore_then(none_of('"').repeated())
50 .then_ignore(just('"'))
51 .to_slice()
52 .map(|s: &str| Value::String(s.to_string()));
53
54 let boolean = text::keyword("true")
56 .to(Value::Boolean(true))
57 .or(text::keyword("false").to(Value::Boolean(false)));
58
59 let number = text::int(10)
61 .then(just('.').then(text::digits(10)).or_not())
62 .to_slice()
63 .map(|s: &str| Value::Number(s.parse().unwrap()));
64
65 let reference = just('@')
67 .ignore_then(
68 text::ascii::ident()
69 .then(just('.').ignore_then(text::ascii::ident()).repeated())
70 .to_slice(),
71 )
72 .map(|s: &str| Value::Reference(s.to_string()));
73
74 let list = value
76 .clone()
77 .padded_with_comments()
78 .separated_by(just(','))
79 .allow_trailing()
80 .collect()
81 .delimited_by(just('['), just(']'))
82 .map(Value::List);
83
84 let map_entry = text::ascii::ident()
86 .padded_with_comments()
87 .then_ignore(just(':'))
88 .then(value.clone().padded_with_comments());
89
90 let map = map_entry
91 .separated_by(just(','))
92 .allow_trailing()
93 .collect::<Vec<_>>()
94 .delimited_by(just('{'), just('}'))
95 .map(|entries: Vec<(&str, Value)>| {
96 Value::Map(entries.into_iter().map(|(k, v)| (k.to_string(), v)).collect())
97 });
98
99 let identifier = text::ascii::ident()
101 .map(|s: &str| Value::String(s.to_string()));
102
103 choice((string, reference, boolean, number, list, map, identifier))
104 })
105}
106
107pub fn component_parser<'a>(
109) -> impl Parser<'a, &'a str, ComponentSpecification, extra::Err<Rich<'a, char>>> + Clone {
110 recursive(|component| {
111 let declaration_keyword = text::keyword("module")
113 .to(DeclarationType::Module)
114 .or(text::keyword("component").to(DeclarationType::ComponentKeyword))
115 .padded_with_comments()
116 .or_not();
117
118 let name = just('.')
120 .or_not()
121 .then(text::ascii::ident())
122 .to_slice()
123 .map(|s: &str| s.to_string())
124 .padded_with_comments();
125
126 let value = value_parser();
128
129 let arg = text::ascii::ident()
130 .then_ignore(just(':').padded_with_comments())
131 .then(value.clone())
132 .map(|(key, value)| (Some(key.to_string()), value))
133 .or(value.map(|value| (None, value)));
134
135 let arg_parser = arg
137 .clone()
138 .padded_with_comments()
139 .separated_by(just(','))
140 .allow_trailing()
141 .collect::<Vec<_>>()
142 .delimited_by(just('('), just(')'))
143 .map(|args| {
144 let arguments = args
145 .into_iter()
146 .enumerate()
147 .map(|(i, (key, value))| match key {
148 Some(k) => Argument::Named { key: k, value },
149 None => Argument::Positioned { position: i, value },
150 })
151 .collect();
152 ArgumentList::new(arguments)
153 })
154 .or(empty().to(ArgumentList::empty()));
155
156 let args = arg_parser.clone().padded_with_comments().or_not();
157
158 let children_block = just('{')
161 .padded_with_comments()
162 .ignore_then(
163 component
164 .clone()
165 .padded_with_comments()
166 .repeated()
167 .collect::<Vec<_>>()
168 )
169 .then_ignore(just('}').padded_with_comments())
170 .or_not();
171
172 let applicators = just('.')
174 .ignore_then(text::ascii::ident())
175 .then(arg_parser.clone())
176 .map(|(name, args)| ApplicatorSpecification {
177 name: name.to_string(),
178 arguments: args,
179 children: vec![],
180 internal_id: String::new(),
181 })
182 .padded_with_comments()
183 .repeated()
184 .collect::<Vec<_>>();
185
186 declaration_keyword
187 .then(name)
188 .then(args)
189 .then(children_block)
190 .then(applicators)
191 .map(|((((decl_type, name), args), children), applicators)| {
192 let base_component = ComponentSpecification::new(
194 uuid::Uuid::new_v4().to_string(),
195 name.clone(),
196 args.unwrap_or_else(ArgumentList::empty),
197 vec![],
198 fold_applicators(children.unwrap_or_default()),
199 MetaData {
200 internal_id: String::new(),
201 name_range: 0..0,
202 block_range: None,
203 },
204 )
205 .with_declaration_type(decl_type.unwrap_or(DeclarationType::Component));
206
207 if applicators.is_empty() {
209 base_component
210 } else {
211 ComponentSpecification {
212 applicators,
213 ..base_component
214 }
215 }
216 })
217 })
218}
219
220fn fold_applicators(components: Vec<ComponentSpecification>) -> Vec<ComponentSpecification> {
223 let mut result: Vec<ComponentSpecification> = Vec::new();
224
225 for component in components {
226 if component.name.starts_with('.') && !result.is_empty() {
227 let mut owner: ComponentSpecification = result.pop().unwrap();
229 owner.applicators.push(component.to_applicator());
230 result.push(owner);
231 } else {
232 result.push(component);
233 }
234 }
235
236 result
237}
238
239pub fn import_parser<'a>() -> impl Parser<'a, &'a str, ImportStatement, extra::Err<Rich<'a, char>>> + Clone {
243 let import_keyword = text::keyword("import").padded_with_comments();
245
246 let string_literal = just('"')
248 .ignore_then(none_of('"').repeated().to_slice())
249 .then_ignore(just('"'))
250 .map(|s: &str| s.to_string());
251
252 let named_imports = text::ascii::ident()
254 .map(|s: &str| s.to_string())
255 .padded_with_comments()
256 .separated_by(just(','))
257 .allow_trailing()
258 .collect::<Vec<String>>()
259 .delimited_by(just('{').padded_with_comments(), just('}').padded_with_comments())
260 .map(ImportClause::Named);
261
262 let default_import = text::ascii::ident()
264 .map(|s: &str| s.to_string())
265 .map(ImportClause::Default);
266
267 let import_clause = named_imports.or(default_import).padded_with_comments();
269
270 let from_keyword = text::keyword("from").padded_with_comments();
272
273 import_keyword
275 .ignore_then(import_clause)
276 .then_ignore(from_keyword)
277 .then(string_literal.padded_with_comments())
278 .map(|(clause, source_str)| {
279 let source = if source_str.starts_with("http://") || source_str.starts_with("https://") {
281 ImportSource::Url(source_str)
282 } else {
283 ImportSource::Local(source_str)
284 };
285 ImportStatement::new(clause, source)
286 })
287}
288
289pub fn document_parser<'a>() -> impl Parser<'a, &'a str, Document, extra::Err<Rich<'a, char>>> + Clone {
291 let imports = import_parser()
293 .padded_with_comments()
294 .repeated()
295 .collect::<Vec<ImportStatement>>();
296
297 let components = component_parser()
299 .padded_with_comments()
300 .repeated()
301 .collect::<Vec<ComponentSpecification>>();
302
303 imports
305 .then(components)
306 .map(|(imports, components)| Document::new(imports, components))
307}
308
309pub fn parse_components(
311 input: &str,
312) -> Result<Vec<ComponentSpecification>, Vec<Rich<char>>> {
313 component_parser()
314 .padded_with_comments()
315 .repeated()
316 .collect()
317 .then_ignore(end())
318 .parse(input)
319 .into_result()
320}
321
322pub fn parse_component(
324 input: &str,
325) -> Result<ComponentSpecification, Vec<Rich<char>>> {
326 component_parser()
327 .padded_with_comments()
328 .then_ignore(end())
329 .parse(input)
330 .into_result()
331}
332
333pub fn parse_document(
335 input: &str,
336) -> Result<Document, Vec<Rich<char>>> {
337 document_parser()
338 .padded_with_comments()
339 .then_ignore(end())
340 .parse(input)
341 .into_result()
342}
343
344pub fn parse_import(
346 input: &str,
347) -> Result<ImportStatement, Vec<Rich<char>>> {
348 import_parser()
349 .padded_with_comments()
350 .then_ignore(end())
351 .parse(input)
352 .into_result()
353}
354
355mod uuid {
357 use std::sync::atomic::{AtomicUsize, Ordering};
358
359 static COUNTER: AtomicUsize = AtomicUsize::new(0);
360
361 pub struct Uuid(usize);
362
363 impl Uuid {
364 pub fn new_v4() -> Self {
365 Uuid(COUNTER.fetch_add(1, Ordering::SeqCst))
366 }
367
368 pub fn to_string(&self) -> String {
369 format!("id-{}", self.0)
370 }
371 }
372}