context_notation/
parser.rs

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    // use pest::Parser;
34    // use rand::Rng;
35
36    // fn generate_unicode_garbage(length: usize) -> String {
37    //     rand::thread_rng()
38    //         .sample_iter::<char, _>(rand::distributions::Standard)
39    //         .take(length)
40    //         .collect()
41    // }
42
43    // #[test]
44    // fn it_parses_nondeterministic_unicode_garbage_without_failing() {
45    //     for _ in 0..1024 {
46    //         let gibberish = generate_unicode_garbage(1024);
47
48    //         RunicParser::parse(Rule::Runic, &gibberish)
49    //             .map_err(|error| {
50    //                 println!("{error}");
51    //                 error
52    //             })
53    //             .unwrap();
54    //     }
55    // }
56
57    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 mut outer_list_items = list.into_inner();
209
210        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        // ```foo`
391        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        // `foo```
399        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        // foo`bar` `baz`foo
407        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        // Empty
416        blocks.next().unwrap();
417
418        // /foo/bar..
419        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        // foo/bar
427        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        // foo/bar /foo#baz
434        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        // /foo foo/bar
443        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        // Empty
451        blocks.next().unwrap();
452
453        // @@me
454        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        // me@example
461        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        // me @example.
468        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        // @me@example
477        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        // @me.@example
485        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        // Empty
493        blocks.next().unwrap();
494
495        // ##phonenumber
496        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        // phone#number#
503        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        // #phone #number
510        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        // #phone#number
519        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        // Empty
527        blocks.next().unwrap();
528
529        // fizz[[buzz]] [[fizz]]
530        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        // [[fizz]] [[buzz]]fizz
540        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        // [[[[fizzbuzz]]]]
550        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        // [[ [[fizzbuzz]]]]
559        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        // ([[fizzbuzz]]])
568        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}