1pub use pest::iterators::{Pair, Pairs};
2use pest_derive::Parser;
3use thiserror::Error;
4use wasm_bindgen::prelude::*;
5
6#[wasm_bindgen]
7extern "C" {
8 #[wasm_bindgen(js_namespace = console)]
9 fn log(s: &str);
10}
11
12#[derive(Error, Debug)]
13pub enum ContextParserError {
14 #[error("Failed to parse input: {0}")]
15 ParseError(String),
16}
17
18#[derive(Parser)]
19#[grammar = "context.pest"]
20pub struct ContextParser {}
21
22impl ContextParser {
23 pub fn parse<'a>(rule: Rule, value: &'a str) -> Result<Pairs<'a, Rule>, ContextParserError> {
24 <Self as pest::Parser<Rule>>::parse(rule, value)
25 .map_err(|error| ContextParserError::ParseError(format!("{error}")))
26 }
27}
28
29#[cfg(test)]
30mod tests {
31
32 use super::{ContextParser, Rule};
33 macro_rules! assert_pair {
58 ($pair:expr, $rule:expr, $str:expr) => {
59 let pair = $pair;
60 assert_eq!(pair.as_rule(), $rule);
61 assert_eq!(pair.as_str(), $str);
62 };
63 }
64
65 #[test]
66 fn it_parses_a_basic_paragraph() {
67 let mut parsed = ContextParser::parse(Rule::Context, "Greetings, citizen!").unwrap();
68
69 let context = parsed.nth(0).unwrap();
70
71 assert_eq!(context.as_rule(), Rule::Context);
72
73 let mut blocks = context.into_inner();
74
75 let paragraph = blocks.next().unwrap();
76
77 assert_eq!(paragraph.as_rule(), Rule::Paragraph);
78
79 let mut spans = paragraph.into_inner();
80 let text = spans.next().unwrap();
81
82 assert_eq!(text.as_rule(), Rule::Text);
83 }
84
85 #[test]
86 fn it_parses_a_basic_paragraph_with_a_hashtag() {
87 let mut parsed =
88 ContextParser::parse(Rule::Context, "Greetings, citizen! #copaganda").unwrap();
89
90 let context = parsed.nth(0).unwrap();
91
92 assert_eq!(context.as_rule(), Rule::Context);
93
94 let mut blocks = context.into_inner();
95
96 let paragraph = blocks.next().unwrap();
97
98 assert_eq!(paragraph.as_rule(), Rule::Paragraph);
99
100 let mut spans = paragraph.into_inner();
101 let text = spans.next().unwrap();
102
103 assert_eq!(text.as_rule(), Rule::Text);
104 assert_eq!(text.as_span().as_str(), "Greetings, citizen! ");
105
106 let hashtag = spans.next().unwrap();
107
108 assert_eq!(hashtag.as_rule(), Rule::HashTag);
109 assert_eq!(hashtag.as_span().as_str(), "#copaganda");
110 }
111
112 #[test]
113 fn it_preserves_boundary_whitespace_in_text_spans() {
114 let mut parsed =
115 ContextParser::parse(Rule::Context, "Greetings, #citizen! #copaganda").unwrap();
116
117 let context = parsed.nth(0).unwrap();
118
119 assert_eq!(context.as_rule(), Rule::Context);
120
121 let mut blocks = context.into_inner();
122
123 let paragraph = blocks.next().unwrap();
124
125 assert_eq!(paragraph.as_rule(), Rule::Paragraph);
126
127 let mut spans = paragraph.into_inner();
128 let text = spans.next().unwrap();
129
130 assert_eq!(text.as_rule(), Rule::Text);
131 assert_eq!(text.as_span().as_str(), "Greetings, ");
132
133 let first_hashtag = spans.next().unwrap();
134
135 assert_eq!(first_hashtag.as_rule(), Rule::HashTag);
136 assert_eq!(first_hashtag.as_span().as_str(), "#citizen");
137
138 let bridge_text = spans.next().unwrap();
139
140 assert_eq!(bridge_text.as_rule(), Rule::Text);
141 assert_eq!(bridge_text.as_span().as_str(), "! ");
142
143 let second_hashtag = spans.next().unwrap();
144
145 assert_eq!(second_hashtag.as_rule(), Rule::HashTag);
146 assert_eq!(second_hashtag.as_span().as_str(), "#copaganda");
147 }
148
149 #[test]
150 fn it_parses_a_basic_flat_list() {
151 let mut parsed = ContextParser::parse(
152 Rule::Context,
153 r#"- foo
154- bar
155- baz"#,
156 )
157 .unwrap();
158
159 let context = parsed.nth(0).unwrap();
160
161 assert_eq!(context.as_rule(), Rule::Context);
162
163 let mut blocks = context.into_inner();
164
165 let list = blocks.next().unwrap();
166
167 assert_eq!(list.as_rule(), Rule::List,);
168
169 let mut list_inner = list.into_inner();
170 let list_indentation = list_inner.next().unwrap();
171
172 assert_eq!(list_indentation.as_rule(), Rule::ListIndentation);
173
174 let mut list_item_count = 0;
175
176 for item in list_inner {
177 list_item_count += 1;
178 assert_eq!(item.as_rule(), Rule::ListItem);
179 }
180
181 assert_eq!(list_item_count, 3);
182 }
183
184 #[test]
185 fn it_parses_a_basic_nested_list() {
186 let mut parsed = ContextParser::parse(
187 Rule::Context,
188 r#"- foo
189 - bar
190- baz"#,
191 )
192 .unwrap();
193
194 let context = parsed.nth(0).unwrap();
195
196 assert_eq!(context.as_rule(), Rule::Context);
197
198 let mut blocks = context.into_inner();
199
200 let list = blocks.next().unwrap();
201
202 assert_eq!(list.as_rule(), Rule::List);
203
204 let mut outer_list_inner = list.into_inner();
205
206 let list_indentation = outer_list_inner.next().unwrap();
207 assert_eq!(list_indentation.as_rule(), Rule::ListIndentation);
208 let first_item = outer_list_inner.next().unwrap();
211
212 assert_eq!(first_item.as_rule(), Rule::ListItem);
213
214 let mut first_item_inner = first_item.into_inner();
215
216 let mut item_children = first_item_inner.next().unwrap().into_inner();
217
218 let item_content = item_children.next().unwrap();
219
220 assert_eq!(item_content.as_rule(), Rule::Text);
221
222 let inner_list = first_item_inner.next().unwrap();
223
224 assert_eq!(inner_list.as_rule(), Rule::NestedList);
225
226 let mut inner_list_inner = inner_list.into_inner();
227
228 let list_indentation = inner_list_inner.next().unwrap();
229 assert_eq!(list_indentation.as_rule(), Rule::ListIndentation);
230 assert_eq!(list_indentation.as_str(), " ");
231
232 let inner_list_item = inner_list_inner.next().unwrap();
233
234 assert_eq!(inner_list_item.as_rule(), Rule::ListItem);
235
236 let last_item = outer_list_inner.next().unwrap();
237
238 assert_eq!(last_item.as_rule(), Rule::ListItem);
239 }
240
241 #[test]
242 fn it_parses_a_basic_multiline_quote() {
243 let mut parsed = ContextParser::parse(
244 Rule::Context,
245 r#"> foo
246> bar
247> baz"#,
248 )
249 .unwrap();
250
251 let context = parsed.nth(0).unwrap();
252
253 assert_eq!(context.as_rule(), Rule::Context);
254
255 let mut blocks = context.into_inner();
256 let quote = blocks.next().unwrap();
257
258 assert_eq!(quote.as_rule(), Rule::Quote);
259
260 let quote_lines = quote.into_inner();
261 let mut quote_line_count = 0;
262
263 for quote in quote_lines {
264 if quote.as_rule() == Rule::EOI {
265 break;
266 }
267
268 assert_eq!(quote.as_rule(), Rule::QuoteLine);
269 quote_line_count += 1;
270 }
271
272 assert_eq!(quote_line_count, 3);
273 }
274
275 #[test]
276 fn it_parses_a_basic_slashlink() {
277 let mut parsed = ContextParser::parse(Rule::Context, "/foo/bar").unwrap();
278
279 let context = parsed.next().unwrap();
280
281 assert_eq!(context.as_rule(), Rule::Context);
282
283 let mut blocks = context.into_inner();
284 let paragraph = blocks.next().unwrap();
285
286 assert_eq!(paragraph.as_rule(), Rule::Paragraph);
287
288 let slashlink = paragraph.into_inner().next().unwrap();
289
290 assert_eq!(slashlink.as_rule(), Rule::SlashLink);
291 assert_eq!(slashlink.as_span().as_str(), "/foo/bar");
292 }
293
294 #[test]
295 fn it_supports_extensions_in_slashlinks() {
296 let mut parsed = ContextParser::parse(Rule::Context, "/foo/bar.txt").unwrap();
297
298 let context = parsed.next().unwrap();
299
300 assert_eq!(context.as_rule(), Rule::Context);
301
302 let mut blocks = context.into_inner();
303 let paragraph = blocks.next().unwrap();
304
305 assert_eq!(paragraph.as_rule(), Rule::Paragraph);
306
307 let slashlink = paragraph.into_inner().next().unwrap();
308
309 assert_eq!(slashlink.as_rule(), Rule::SlashLink);
310 assert_eq!(slashlink.as_span().as_str(), "/foo/bar.txt");
311
312 let mut parsed = ContextParser::parse(Rule::Context, "/foo/bar. ").unwrap();
313
314 let context = parsed.next().unwrap();
315
316 assert_eq!(context.as_rule(), Rule::Context);
317
318 let mut blocks = context.into_inner();
319 let paragraph = blocks.next().unwrap();
320
321 assert_eq!(paragraph.as_rule(), Rule::Paragraph);
322
323 let slashlink = paragraph.into_inner().next().unwrap();
324
325 assert_eq!(slashlink.as_rule(), Rule::SlashLink);
326 assert_eq!(slashlink.as_span().as_str(), "/foo/bar");
327 }
328
329 #[test]
330 fn it_parses_slashes_preceded_by_printed_characters_as_text() {
331 let mut parsed = ContextParser::parse(Rule::Context, "red/black").unwrap();
332
333 let context = parsed.next().unwrap();
334
335 assert_eq!(context.as_rule(), Rule::Context);
336
337 let mut blocks = context.into_inner();
338 let paragraph = blocks.next().unwrap();
339
340 assert_eq!(paragraph.as_rule(), Rule::Paragraph);
341
342 let mut spans = paragraph.into_inner();
343 let text = spans.next().unwrap();
344
345 assert_eq!(text.as_rule(), Rule::Text);
346 assert_eq!(text.as_span().as_str(), "red/black");
347
348 assert!(spans.next().is_none());
349 }
350
351 #[test]
352 fn it_parses_various_inline_boundary_edge_cases() {
353 let mut parsed = ContextParser::parse(
354 Rule::Context,
355 r#"```foo`
356`foo```
357foo`bar` `baz`foo
358
359/foo/bar..
360foo/bar
361foo/bar /foo#baz
362/foo foo/bar
363
364@@me
365me@example
366me @example.
367@me@example
368@me.@example
369
370##phonenumber
371phone#number#
372#phone #number
373#phone#number
374
375fizz[[buzz]] [[fizz]]
376[[fizz]] [[buzz]]fizz
377[[[[fizzbuzz]]]]
378[[ [[fizzbuzz]]]]
379([[fizzbuzz]])
380 "#,
381 )
382 .unwrap();
383
384 let context = parsed.next().unwrap();
385
386 assert_eq!(context.as_rule(), Rule::Context);
387
388 let mut blocks = context.into_inner();
389
390 let paragraph = blocks.next().unwrap();
392 assert_eq!(paragraph.as_rule(), Rule::Paragraph);
393 let mut spans = paragraph.into_inner();
394
395 assert_pair!(spans.next().unwrap(), Rule::Text, "``");
396 assert_pair!(spans.next().unwrap(), Rule::InlineCode, "`foo`");
397
398 let paragraph = blocks.next().unwrap();
400 assert_eq!(paragraph.as_rule(), Rule::Paragraph);
401 let mut spans = paragraph.into_inner();
402
403 assert_pair!(spans.next().unwrap(), Rule::InlineCode, "`foo`");
404 assert_pair!(spans.next().unwrap(), Rule::Text, "``");
405
406 let paragraph = blocks.next().unwrap();
408 assert_eq!(paragraph.as_rule(), Rule::Paragraph);
409 let mut spans = paragraph.into_inner();
410
411 assert_pair!(spans.next().unwrap(), Rule::Text, "foo`bar` ");
412 assert_pair!(spans.next().unwrap(), Rule::InlineCode, "`baz`");
413 assert_pair!(spans.next().unwrap(), Rule::Text, "foo");
414
415 blocks.next().unwrap();
417
418 let paragraph = blocks.next().unwrap();
420 assert_eq!(paragraph.as_rule(), Rule::Paragraph);
421 let mut spans = paragraph.into_inner();
422
423 assert_pair!(spans.next().unwrap(), Rule::SlashLink, "/foo/bar");
424 assert_pair!(spans.next().unwrap(), Rule::Text, "..");
425
426 let paragraph = blocks.next().unwrap();
428 assert_eq!(paragraph.as_rule(), Rule::Paragraph);
429 let mut spans = paragraph.into_inner();
430
431 assert_pair!(spans.next().unwrap(), Rule::Text, "foo/bar");
432
433 let paragraph = blocks.next().unwrap();
435 assert_eq!(paragraph.as_rule(), Rule::Paragraph);
436 let mut spans = paragraph.into_inner();
437
438 assert_pair!(spans.next().unwrap(), Rule::Text, "foo/bar ");
439 assert_pair!(spans.next().unwrap(), Rule::SlashLink, "/foo");
440 assert_pair!(spans.next().unwrap(), Rule::HashTag, "#baz");
441
442 let paragraph = blocks.next().unwrap();
444 assert_eq!(paragraph.as_rule(), Rule::Paragraph);
445 let mut spans = paragraph.into_inner();
446
447 assert_pair!(spans.next().unwrap(), Rule::SlashLink, "/foo");
448 assert_pair!(spans.next().unwrap(), Rule::Text, " foo/bar");
449
450 blocks.next().unwrap();
452
453 let paragraph = blocks.next().unwrap();
455 assert_eq!(paragraph.as_rule(), Rule::Paragraph);
456 let mut spans = paragraph.into_inner();
457
458 assert_pair!(spans.next().unwrap(), Rule::Text, "@@me");
459
460 let paragraph = blocks.next().unwrap();
462 assert_eq!(paragraph.as_rule(), Rule::Paragraph);
463 let mut spans = paragraph.into_inner();
464
465 assert_pair!(spans.next().unwrap(), Rule::Text, "me@example");
466
467 let paragraph = blocks.next().unwrap();
469 assert_eq!(paragraph.as_rule(), Rule::Paragraph);
470 let mut spans = paragraph.into_inner();
471
472 assert_pair!(spans.next().unwrap(), Rule::Text, "me ");
473 assert_pair!(spans.next().unwrap(), Rule::Mention, "@example");
474 assert_pair!(spans.next().unwrap(), Rule::Text, ".");
475
476 let paragraph = blocks.next().unwrap();
478 assert_eq!(paragraph.as_rule(), Rule::Paragraph);
479 let mut spans = paragraph.into_inner();
480
481 assert_pair!(spans.next().unwrap(), Rule::Mention, "@me");
482 assert_pair!(spans.next().unwrap(), Rule::Mention, "@example");
483
484 let paragraph = blocks.next().unwrap();
486 assert_eq!(paragraph.as_rule(), Rule::Paragraph);
487 let mut spans = paragraph.into_inner();
488
489 assert_pair!(spans.next().unwrap(), Rule::Mention, "@me");
490 assert_pair!(spans.next().unwrap(), Rule::Text, ".@example");
491
492 blocks.next().unwrap();
494
495 let paragraph = blocks.next().unwrap();
497 assert_eq!(paragraph.as_rule(), Rule::Paragraph);
498 let mut spans = paragraph.into_inner();
499
500 assert_pair!(spans.next().unwrap(), Rule::Text, "##phonenumber");
501
502 let paragraph = blocks.next().unwrap();
504 assert_eq!(paragraph.as_rule(), Rule::Paragraph);
505 let mut spans = paragraph.into_inner();
506
507 assert_pair!(spans.next().unwrap(), Rule::Text, "phone#number#");
508
509 let paragraph = blocks.next().unwrap();
511 assert_eq!(paragraph.as_rule(), Rule::Paragraph);
512 let mut spans = paragraph.into_inner();
513
514 assert_pair!(spans.next().unwrap(), Rule::HashTag, "#phone");
515 assert_pair!(spans.next().unwrap(), Rule::Text, " ");
516 assert_pair!(spans.next().unwrap(), Rule::HashTag, "#number");
517
518 let paragraph = blocks.next().unwrap();
520 assert_eq!(paragraph.as_rule(), Rule::Paragraph);
521 let mut spans = paragraph.into_inner();
522
523 assert_pair!(spans.next().unwrap(), Rule::HashTag, "#phone");
524 assert_pair!(spans.next().unwrap(), Rule::HashTag, "#number");
525
526 blocks.next().unwrap();
528
529 let paragraph = blocks.next().unwrap();
531 assert_eq!(paragraph.as_rule(), Rule::Paragraph);
532 let mut spans = paragraph.into_inner();
533
534 assert_pair!(spans.next().unwrap(), Rule::Text, "fizz");
535 assert_pair!(spans.next().unwrap(), Rule::WikiLink, "[[buzz]]");
536 assert_pair!(spans.next().unwrap(), Rule::Text, " ");
537 assert_pair!(spans.next().unwrap(), Rule::WikiLink, "[[fizz]]");
538
539 let paragraph = blocks.next().unwrap();
541 assert_eq!(paragraph.as_rule(), Rule::Paragraph);
542 let mut spans = paragraph.into_inner();
543
544 assert_pair!(spans.next().unwrap(), Rule::WikiLink, "[[fizz]]");
545 assert_pair!(spans.next().unwrap(), Rule::Text, " ");
546 assert_pair!(spans.next().unwrap(), Rule::WikiLink, "[[buzz]]");
547 assert_pair!(spans.next().unwrap(), Rule::Text, "fizz");
548
549 let paragraph = blocks.next().unwrap();
551 assert_eq!(paragraph.as_rule(), Rule::Paragraph);
552 let mut spans = paragraph.into_inner();
553
554 assert_pair!(spans.next().unwrap(), Rule::Text, "[[");
555 assert_pair!(spans.next().unwrap(), Rule::WikiLink, "[[fizzbuzz]]");
556 assert_pair!(spans.next().unwrap(), Rule::Text, "]]");
557
558 let paragraph = blocks.next().unwrap();
560 assert_eq!(paragraph.as_rule(), Rule::Paragraph);
561 let mut spans = paragraph.into_inner();
562
563 assert_pair!(spans.next().unwrap(), Rule::Text, "[[ ");
564 assert_pair!(spans.next().unwrap(), Rule::WikiLink, "[[fizzbuzz]]");
565 assert_pair!(spans.next().unwrap(), Rule::Text, "]]");
566
567 let paragraph = blocks.next().unwrap();
569 assert_eq!(paragraph.as_rule(), Rule::Paragraph);
570 let mut spans = paragraph.into_inner();
571
572 assert_pair!(spans.next().unwrap(), Rule::Text, "(");
573 assert_pair!(spans.next().unwrap(), Rule::WikiLink, "[[fizzbuzz]]");
574 assert_pair!(spans.next().unwrap(), Rule::Text, ")");
575 }
576}