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 = component
160 .clone()
161 .padded_with_comments()
162 .repeated()
163 .collect::<Vec<_>>()
164 .delimited_by(just('{'), just('}'))
165 .padded_with_comments()
166 .or_not();
167
168 let applicators = just('.')
170 .ignore_then(text::ascii::ident())
171 .then(arg_parser.clone())
172 .map(|(name, args)| ApplicatorSpecification {
173 name: name.to_string(),
174 arguments: args,
175 children: vec![],
176 internal_id: String::new(),
177 })
178 .padded_with_comments()
179 .repeated()
180 .collect::<Vec<_>>();
181
182 declaration_keyword
183 .then(name)
184 .then(args)
185 .then(children_block)
186 .then(applicators)
187 .map(|((((decl_type, name), args), children), applicators)| {
188 let base_component = ComponentSpecification::new(
190 uuid::Uuid::new_v4().to_string(),
191 name.clone(),
192 args.unwrap_or_else(ArgumentList::empty),
193 vec![],
194 fold_applicators(children.unwrap_or_default()),
195 MetaData {
196 internal_id: String::new(),
197 name_range: 0..0,
198 block_range: None,
199 },
200 )
201 .with_declaration_type(decl_type.unwrap_or(DeclarationType::Component));
202
203 if applicators.is_empty() {
205 base_component
206 } else {
207 ComponentSpecification {
208 applicators,
209 ..base_component
210 }
211 }
212 })
213 })
214}
215
216fn fold_applicators(components: Vec<ComponentSpecification>) -> Vec<ComponentSpecification> {
219 let mut result: Vec<ComponentSpecification> = Vec::new();
220
221 for component in components {
222 if component.name.starts_with('.') && !result.is_empty() {
223 let mut owner: ComponentSpecification = result.pop().unwrap();
225 owner.applicators.push(component.to_applicator());
226 result.push(owner);
227 } else {
228 result.push(component);
229 }
230 }
231
232 result
233}
234
235pub fn import_parser<'a>() -> impl Parser<'a, &'a str, ImportStatement, extra::Err<Rich<'a, char>>> + Clone {
239 let import_keyword = text::keyword("import").padded_with_comments();
241
242 let string_literal = just('"')
244 .ignore_then(none_of('"').repeated().to_slice())
245 .then_ignore(just('"'))
246 .map(|s: &str| s.to_string());
247
248 let named_imports = text::ascii::ident()
250 .map(|s: &str| s.to_string())
251 .padded_with_comments()
252 .separated_by(just(','))
253 .allow_trailing()
254 .collect::<Vec<String>>()
255 .delimited_by(just('{').padded_with_comments(), just('}').padded_with_comments())
256 .map(ImportClause::Named);
257
258 let default_import = text::ascii::ident()
260 .map(|s: &str| s.to_string())
261 .map(ImportClause::Default);
262
263 let import_clause = named_imports.or(default_import).padded_with_comments();
265
266 let from_keyword = text::keyword("from").padded_with_comments();
268
269 import_keyword
271 .ignore_then(import_clause)
272 .then_ignore(from_keyword)
273 .then(string_literal.padded_with_comments())
274 .map(|(clause, source_str)| {
275 let source = if source_str.starts_with("http://") || source_str.starts_with("https://") {
277 ImportSource::Url(source_str)
278 } else {
279 ImportSource::Local(source_str)
280 };
281 ImportStatement::new(clause, source)
282 })
283}
284
285pub fn document_parser<'a>() -> impl Parser<'a, &'a str, Document, extra::Err<Rich<'a, char>>> + Clone {
287 let imports = import_parser()
289 .padded_with_comments()
290 .repeated()
291 .collect::<Vec<ImportStatement>>();
292
293 let components = component_parser()
295 .padded_with_comments()
296 .repeated()
297 .collect::<Vec<ComponentSpecification>>();
298
299 imports
301 .then(components)
302 .map(|(imports, components)| Document::new(imports, components))
303}
304
305pub fn parse_components(
307 input: &str,
308) -> Result<Vec<ComponentSpecification>, Vec<Rich<char>>> {
309 component_parser()
310 .padded_with_comments()
311 .repeated()
312 .collect()
313 .then_ignore(end())
314 .parse(input)
315 .into_result()
316}
317
318pub fn parse_component(
320 input: &str,
321) -> Result<ComponentSpecification, Vec<Rich<char>>> {
322 component_parser()
323 .padded_with_comments()
324 .then_ignore(end())
325 .parse(input)
326 .into_result()
327}
328
329pub fn parse_document(
331 input: &str,
332) -> Result<Document, Vec<Rich<char>>> {
333 document_parser()
334 .padded_with_comments()
335 .then_ignore(end())
336 .parse(input)
337 .into_result()
338}
339
340pub fn parse_import(
342 input: &str,
343) -> Result<ImportStatement, Vec<Rich<char>>> {
344 import_parser()
345 .padded_with_comments()
346 .then_ignore(end())
347 .parse(input)
348 .into_result()
349}
350
351mod uuid {
353 use std::sync::atomic::{AtomicUsize, Ordering};
354
355 static COUNTER: AtomicUsize = AtomicUsize::new(0);
356
357 pub struct Uuid(usize);
358
359 impl Uuid {
360 pub fn new_v4() -> Self {
361 Uuid(COUNTER.fetch_add(1, Ordering::SeqCst))
362 }
363
364 pub fn to_string(&self) -> String {
365 format!("id-{}", self.0)
366 }
367 }
368}