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 = just('-')
61 .or_not()
62 .then(text::int(10))
63 .then(just('.').then(text::digits(10)).or_not())
64 .to_slice()
65 .map(|s: &str| Value::Number(s.parse().unwrap()));
66
67 let reference = just('@')
69 .ignore_then(
70 text::ascii::ident()
71 .then(just('.').ignore_then(text::ascii::ident()).repeated())
72 .to_slice(),
73 )
74 .map(|s: &str| Value::Reference(s.to_string()));
75
76 let list = value
78 .clone()
79 .padded_with_comments()
80 .separated_by(just(','))
81 .allow_trailing()
82 .collect()
83 .delimited_by(just('['), just(']'))
84 .map(Value::List);
85
86 let map_entry = text::ascii::ident()
88 .padded_with_comments()
89 .then_ignore(just(':'))
90 .then(value.clone().padded_with_comments());
91
92 let map = map_entry
93 .separated_by(just(','))
94 .allow_trailing()
95 .collect::<Vec<_>>()
96 .delimited_by(just('{'), just('}'))
97 .map(|entries: Vec<(&str, Value)>| {
98 Value::Map(entries.into_iter().map(|(k, v)| (k.to_string(), v)).collect())
99 });
100
101 let identifier = text::ascii::ident()
103 .map(|s: &str| Value::String(s.to_string()));
104
105 choice((string, reference, boolean, number, list, map, identifier))
106 })
107}
108
109pub fn component_parser<'a>(
111) -> impl Parser<'a, &'a str, ComponentSpecification, extra::Err<Rich<'a, char>>> + Clone {
112 recursive(|component| {
113 let declaration_keyword = text::keyword("module")
115 .to(DeclarationType::Module)
116 .or(text::keyword("component").to(DeclarationType::ComponentKeyword))
117 .padded_with_comments()
118 .or_not();
119
120 let name = just('.')
122 .or_not()
123 .then(text::ascii::ident())
124 .to_slice()
125 .map(|s: &str| s.to_string())
126 .padded_with_comments();
127
128 let value = value_parser();
130
131 let arg = text::ascii::ident()
132 .then_ignore(just(':').padded_with_comments())
133 .then(value.clone())
134 .map(|(key, value)| (Some(key.to_string()), value))
135 .or(value.map(|value| (None, value)));
136
137 let arg_parser = arg
139 .clone()
140 .padded_with_comments()
141 .separated_by(just(','))
142 .allow_trailing()
143 .collect::<Vec<_>>()
144 .delimited_by(just('('), just(')'))
145 .map(|args| {
146 let arguments = args
147 .into_iter()
148 .enumerate()
149 .map(|(i, (key, value))| match key {
150 Some(k) => Argument::Named { key: k, value },
151 None => Argument::Positioned { position: i, value },
152 })
153 .collect();
154 ArgumentList::new(arguments)
155 })
156 .or(empty().to(ArgumentList::empty()));
157
158 let args = arg_parser.clone().padded_with_comments().or_not();
159
160 let children_block = just('{')
163 .padded_with_comments()
164 .ignore_then(
165 component
166 .clone()
167 .padded_with_comments()
168 .repeated()
169 .collect::<Vec<_>>()
170 )
171 .then_ignore(just('}').padded_with_comments())
172 .or_not();
173
174 let applicators = just('.')
176 .ignore_then(text::ascii::ident())
177 .then(arg_parser.clone())
178 .map(|(name, args)| ApplicatorSpecification {
179 name: name.to_string(),
180 arguments: args,
181 children: vec![],
182 internal_id: String::new(),
183 })
184 .padded_with_comments()
185 .repeated()
186 .collect::<Vec<_>>();
187
188 declaration_keyword
189 .then(name)
190 .then(args)
191 .then(children_block)
192 .then(applicators)
193 .map(|((((decl_type, name), args), children), applicators)| {
194 let base_component = ComponentSpecification::new(
196 id_gen::NodeId::next().to_string(),
197 name.clone(),
198 args.unwrap_or_else(ArgumentList::empty),
199 vec![],
200 fold_applicators(children.unwrap_or_default()),
201 MetaData {
202 internal_id: String::new(),
203 name_range: 0..0,
204 block_range: None,
205 },
206 )
207 .with_declaration_type(decl_type.unwrap_or(DeclarationType::Component));
208
209 if applicators.is_empty() {
211 base_component
212 } else {
213 ComponentSpecification {
214 applicators,
215 ..base_component
216 }
217 }
218 })
219 })
220}
221
222fn fold_applicators(components: Vec<ComponentSpecification>) -> Vec<ComponentSpecification> {
225 let mut result: Vec<ComponentSpecification> = Vec::new();
226
227 for component in components {
228 if component.name.starts_with('.') && !result.is_empty() {
229 let mut owner: ComponentSpecification = result.pop().unwrap();
231 owner.applicators.push(component.to_applicator());
232 result.push(owner);
233 } else {
234 result.push(component);
235 }
236 }
237
238 result
239}
240
241pub fn import_parser<'a>() -> impl Parser<'a, &'a str, ImportStatement, extra::Err<Rich<'a, char>>> + Clone {
245 let import_keyword = text::keyword("import").padded_with_comments();
247
248 let string_literal = just('"')
250 .ignore_then(none_of('"').repeated().to_slice())
251 .then_ignore(just('"'))
252 .map(|s: &str| s.to_string());
253
254 let named_imports = text::ascii::ident()
256 .map(|s: &str| s.to_string())
257 .padded_with_comments()
258 .separated_by(just(','))
259 .allow_trailing()
260 .collect::<Vec<String>>()
261 .delimited_by(just('{').padded_with_comments(), just('}').padded_with_comments())
262 .map(ImportClause::Named);
263
264 let default_import = text::ascii::ident()
266 .map(|s: &str| s.to_string())
267 .map(ImportClause::Default);
268
269 let import_clause = named_imports.or(default_import).padded_with_comments();
271
272 let from_keyword = text::keyword("from").padded_with_comments();
274
275 import_keyword
277 .ignore_then(import_clause)
278 .then_ignore(from_keyword)
279 .then(string_literal.padded_with_comments())
280 .map(|(clause, source_str)| {
281 let source = if source_str.starts_with("http://") || source_str.starts_with("https://") {
283 ImportSource::Url(source_str)
284 } else {
285 ImportSource::Local(source_str)
286 };
287 ImportStatement::new(clause, source)
288 })
289}
290
291pub fn document_parser<'a>() -> impl Parser<'a, &'a str, Document, extra::Err<Rich<'a, char>>> + Clone {
293 let imports = import_parser()
295 .padded_with_comments()
296 .repeated()
297 .collect::<Vec<ImportStatement>>();
298
299 let components = component_parser()
301 .padded_with_comments()
302 .repeated()
303 .collect::<Vec<ComponentSpecification>>();
304
305 imports
307 .then(components)
308 .map(|(imports, components)| Document::new(imports, components))
309}
310
311pub fn parse_components(
313 input: &str,
314) -> Result<Vec<ComponentSpecification>, Vec<Rich<char>>> {
315 component_parser()
316 .padded_with_comments()
317 .repeated()
318 .collect()
319 .then_ignore(end())
320 .parse(input)
321 .into_result()
322}
323
324pub fn parse_component(
326 input: &str,
327) -> Result<ComponentSpecification, Vec<Rich<char>>> {
328 component_parser()
329 .padded_with_comments()
330 .then_ignore(end())
331 .parse(input)
332 .into_result()
333}
334
335pub fn parse_document(
337 input: &str,
338) -> Result<Document, Vec<Rich<char>>> {
339 document_parser()
340 .padded_with_comments()
341 .then_ignore(end())
342 .parse(input)
343 .into_result()
344}
345
346pub fn parse_import(
348 input: &str,
349) -> Result<ImportStatement, Vec<Rich<char>>> {
350 import_parser()
351 .padded_with_comments()
352 .then_ignore(end())
353 .parse(input)
354 .into_result()
355}
356
357mod id_gen {
359 use std::sync::atomic::{AtomicUsize, Ordering};
360
361 static COUNTER: AtomicUsize = AtomicUsize::new(0);
362
363 pub struct NodeId(usize);
364
365 impl NodeId {
366 pub fn next() -> Self {
368 NodeId(COUNTER.fetch_add(1, Ordering::SeqCst))
369 }
370
371 pub fn to_string(&self) -> String {
372 format!("id-{}", self.0)
373 }
374 }
375}