use pest::iterators::Pairs;
use pest_derive::Parser;
use thiserror::Error;
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_namespace = console)]
fn log(s: &str);
}
#[derive(Error, Debug)]
pub enum ContextParserError {
#[error("Failed to parse input: {0}")]
ParseError(String),
}
#[derive(Parser)]
#[grammar = "context.pest"]
pub struct ContextParser {}
impl ContextParser {
pub fn parse<'a>(rule: Rule, value: &'a str) -> Result<Pairs<'a, Rule>, ContextParserError> {
<Self as pest::Parser<Rule>>::parse(rule, value)
.map_err(|error| ContextParserError::ParseError(format!("{error}")))
}
}
#[cfg(test)]
mod tests {
use super::{ContextParser, Rule};
macro_rules! assert_pair {
($pair:expr, $rule:expr, $str:expr) => {
let pair = $pair;
assert_eq!(pair.as_rule(), $rule);
assert_eq!(pair.as_str(), $str);
};
}
#[test]
fn it_parses_a_basic_paragraph() {
let mut parsed = ContextParser::parse(Rule::Context, "Greetings, citizen!").unwrap();
let context = parsed.nth(0).unwrap();
assert_eq!(context.as_rule(), Rule::Context);
let mut blocks = context.into_inner();
let paragraph = blocks.next().unwrap();
assert_eq!(paragraph.as_rule(), Rule::Paragraph);
let mut spans = paragraph.into_inner();
let text = spans.next().unwrap();
assert_eq!(text.as_rule(), Rule::Text);
}
#[test]
fn it_parses_a_basic_paragraph_with_a_hashtag() {
let mut parsed =
ContextParser::parse(Rule::Context, "Greetings, citizen! #copaganda").unwrap();
let context = parsed.nth(0).unwrap();
assert_eq!(context.as_rule(), Rule::Context);
let mut blocks = context.into_inner();
let paragraph = blocks.next().unwrap();
assert_eq!(paragraph.as_rule(), Rule::Paragraph);
let mut spans = paragraph.into_inner();
let text = spans.next().unwrap();
assert_eq!(text.as_rule(), Rule::Text);
assert_eq!(text.as_span().as_str(), "Greetings, citizen! ");
let hashtag = spans.next().unwrap();
assert_eq!(hashtag.as_rule(), Rule::HashTag);
assert_eq!(hashtag.as_span().as_str(), "#copaganda");
}
#[test]
fn it_preserves_boundary_whitespace_in_text_spans() {
let mut parsed =
ContextParser::parse(Rule::Context, "Greetings, #citizen! #copaganda").unwrap();
let context = parsed.nth(0).unwrap();
assert_eq!(context.as_rule(), Rule::Context);
let mut blocks = context.into_inner();
let paragraph = blocks.next().unwrap();
assert_eq!(paragraph.as_rule(), Rule::Paragraph);
let mut spans = paragraph.into_inner();
let text = spans.next().unwrap();
assert_eq!(text.as_rule(), Rule::Text);
assert_eq!(text.as_span().as_str(), "Greetings, ");
let first_hashtag = spans.next().unwrap();
assert_eq!(first_hashtag.as_rule(), Rule::HashTag);
assert_eq!(first_hashtag.as_span().as_str(), "#citizen");
let bridge_text = spans.next().unwrap();
assert_eq!(bridge_text.as_rule(), Rule::Text);
assert_eq!(bridge_text.as_span().as_str(), "! ");
let second_hashtag = spans.next().unwrap();
assert_eq!(second_hashtag.as_rule(), Rule::HashTag);
assert_eq!(second_hashtag.as_span().as_str(), "#copaganda");
}
#[test]
fn it_parses_a_basic_flat_list() {
let mut parsed = ContextParser::parse(
Rule::Context,
r#"- foo
- bar
- baz"#,
)
.unwrap();
let context = parsed.nth(0).unwrap();
assert_eq!(context.as_rule(), Rule::Context);
let mut blocks = context.into_inner();
let list = blocks.next().unwrap();
assert_eq!(list.as_rule(), Rule::List,);
let mut list_inner = list.into_inner();
let list_indentation = list_inner.next().unwrap();
assert_eq!(list_indentation.as_rule(), Rule::ListIndentation);
let mut list_item_count = 0;
for item in list_inner {
list_item_count += 1;
assert_eq!(item.as_rule(), Rule::ListItem);
}
assert_eq!(list_item_count, 3);
}
#[test]
fn it_parses_a_basic_nested_list() {
let mut parsed = ContextParser::parse(
Rule::Context,
r#"- foo
- bar
- baz"#,
)
.unwrap();
let context = parsed.nth(0).unwrap();
assert_eq!(context.as_rule(), Rule::Context);
let mut blocks = context.into_inner();
let list = blocks.next().unwrap();
assert_eq!(list.as_rule(), Rule::List);
let mut outer_list_inner = list.into_inner();
let list_indentation = outer_list_inner.next().unwrap();
assert_eq!(list_indentation.as_rule(), Rule::ListIndentation);
let first_item = outer_list_inner.next().unwrap();
assert_eq!(first_item.as_rule(), Rule::ListItem);
let mut first_item_inner = first_item.into_inner();
let mut item_children = first_item_inner.next().unwrap().into_inner();
let item_content = item_children.next().unwrap();
assert_eq!(item_content.as_rule(), Rule::Text);
let inner_list = first_item_inner.next().unwrap();
assert_eq!(inner_list.as_rule(), Rule::NestedList);
let mut inner_list_inner = inner_list.into_inner();
let list_indentation = inner_list_inner.next().unwrap();
assert_eq!(list_indentation.as_rule(), Rule::ListIndentation);
assert_eq!(list_indentation.as_str(), " ");
let inner_list_item = inner_list_inner.next().unwrap();
assert_eq!(inner_list_item.as_rule(), Rule::ListItem);
let last_item = outer_list_inner.next().unwrap();
assert_eq!(last_item.as_rule(), Rule::ListItem);
}
#[test]
fn it_parses_a_basic_multiline_quote() {
let mut parsed = ContextParser::parse(
Rule::Context,
r#"> foo
> bar
> baz"#,
)
.unwrap();
let context = parsed.nth(0).unwrap();
assert_eq!(context.as_rule(), Rule::Context);
let mut blocks = context.into_inner();
let quote = blocks.next().unwrap();
assert_eq!(quote.as_rule(), Rule::Quote);
let quote_lines = quote.into_inner();
let mut quote_line_count = 0;
for quote in quote_lines {
if quote.as_rule() == Rule::EOI {
break;
}
assert_eq!(quote.as_rule(), Rule::QuoteLine);
quote_line_count += 1;
}
assert_eq!(quote_line_count, 3);
}
#[test]
fn it_parses_a_basic_slashlink() {
let mut parsed = ContextParser::parse(Rule::Context, "/foo/bar").unwrap();
let context = parsed.next().unwrap();
assert_eq!(context.as_rule(), Rule::Context);
let mut blocks = context.into_inner();
let paragraph = blocks.next().unwrap();
assert_eq!(paragraph.as_rule(), Rule::Paragraph);
let slashlink = paragraph.into_inner().next().unwrap();
assert_eq!(slashlink.as_rule(), Rule::SlashLink);
assert_eq!(slashlink.as_span().as_str(), "/foo/bar");
}
#[test]
fn it_supports_extensions_in_slashlinks() {
let mut parsed = ContextParser::parse(Rule::Context, "/foo/bar.txt").unwrap();
let context = parsed.next().unwrap();
assert_eq!(context.as_rule(), Rule::Context);
let mut blocks = context.into_inner();
let paragraph = blocks.next().unwrap();
assert_eq!(paragraph.as_rule(), Rule::Paragraph);
let slashlink = paragraph.into_inner().next().unwrap();
assert_eq!(slashlink.as_rule(), Rule::SlashLink);
assert_eq!(slashlink.as_span().as_str(), "/foo/bar.txt");
let mut parsed = ContextParser::parse(Rule::Context, "/foo/bar. ").unwrap();
let context = parsed.next().unwrap();
assert_eq!(context.as_rule(), Rule::Context);
let mut blocks = context.into_inner();
let paragraph = blocks.next().unwrap();
assert_eq!(paragraph.as_rule(), Rule::Paragraph);
let slashlink = paragraph.into_inner().next().unwrap();
assert_eq!(slashlink.as_rule(), Rule::SlashLink);
assert_eq!(slashlink.as_span().as_str(), "/foo/bar");
}
#[test]
fn it_parses_slashes_preceded_by_printed_characters_as_text() {
let mut parsed = ContextParser::parse(Rule::Context, "red/black").unwrap();
let context = parsed.next().unwrap();
assert_eq!(context.as_rule(), Rule::Context);
let mut blocks = context.into_inner();
let paragraph = blocks.next().unwrap();
assert_eq!(paragraph.as_rule(), Rule::Paragraph);
let mut spans = paragraph.into_inner();
let text = spans.next().unwrap();
assert_eq!(text.as_rule(), Rule::Text);
assert_eq!(text.as_span().as_str(), "red/black");
assert!(spans.next().is_none());
}
#[test]
fn it_parses_various_inline_boundary_edge_cases() {
let mut parsed = ContextParser::parse(
Rule::Context,
r#"```foo`
`foo```
foo`bar` `baz`foo
/foo/bar..
foo/bar
foo/bar /foo#baz
/foo foo/bar
@@me
me@example
me @example.
@me@example
@me.@example
##phonenumber
phone#number#
#phone #number
#phone#number
fizz[[buzz]] [[fizz]]
[[fizz]] [[buzz]]fizz
[[[[fizzbuzz]]]]
[[ [[fizzbuzz]]]]
"#,
)
.unwrap();
let context = parsed.next().unwrap();
assert_eq!(context.as_rule(), Rule::Context);
let mut blocks = context.into_inner();
let paragraph = blocks.next().unwrap();
assert_eq!(paragraph.as_rule(), Rule::Paragraph);
let mut spans = paragraph.into_inner();
assert_pair!(spans.next().unwrap(), Rule::Text, "``");
assert_pair!(spans.next().unwrap(), Rule::InlineCode, "`foo`");
let paragraph = blocks.next().unwrap();
assert_eq!(paragraph.as_rule(), Rule::Paragraph);
let mut spans = paragraph.into_inner();
assert_pair!(spans.next().unwrap(), Rule::InlineCode, "`foo`");
assert_pair!(spans.next().unwrap(), Rule::Text, "``");
let paragraph = blocks.next().unwrap();
assert_eq!(paragraph.as_rule(), Rule::Paragraph);
let mut spans = paragraph.into_inner();
assert_pair!(spans.next().unwrap(), Rule::Text, "foo`bar` ");
assert_pair!(spans.next().unwrap(), Rule::InlineCode, "`baz`");
assert_pair!(spans.next().unwrap(), Rule::Text, "foo");
blocks.next().unwrap();
let paragraph = blocks.next().unwrap();
assert_eq!(paragraph.as_rule(), Rule::Paragraph);
let mut spans = paragraph.into_inner();
assert_pair!(spans.next().unwrap(), Rule::SlashLink, "/foo/bar");
assert_pair!(spans.next().unwrap(), Rule::Text, "..");
let paragraph = blocks.next().unwrap();
assert_eq!(paragraph.as_rule(), Rule::Paragraph);
let mut spans = paragraph.into_inner();
assert_pair!(spans.next().unwrap(), Rule::Text, "foo/bar");
let paragraph = blocks.next().unwrap();
assert_eq!(paragraph.as_rule(), Rule::Paragraph);
let mut spans = paragraph.into_inner();
assert_pair!(spans.next().unwrap(), Rule::Text, "foo/bar ");
assert_pair!(spans.next().unwrap(), Rule::SlashLink, "/foo");
assert_pair!(spans.next().unwrap(), Rule::HashTag, "#baz");
let paragraph = blocks.next().unwrap();
assert_eq!(paragraph.as_rule(), Rule::Paragraph);
let mut spans = paragraph.into_inner();
assert_pair!(spans.next().unwrap(), Rule::SlashLink, "/foo");
assert_pair!(spans.next().unwrap(), Rule::Text, " foo/bar");
blocks.next().unwrap();
let paragraph = blocks.next().unwrap();
assert_eq!(paragraph.as_rule(), Rule::Paragraph);
let mut spans = paragraph.into_inner();
assert_pair!(spans.next().unwrap(), Rule::Text, "@@me");
let paragraph = blocks.next().unwrap();
assert_eq!(paragraph.as_rule(), Rule::Paragraph);
let mut spans = paragraph.into_inner();
assert_pair!(spans.next().unwrap(), Rule::Text, "me@example");
let paragraph = blocks.next().unwrap();
assert_eq!(paragraph.as_rule(), Rule::Paragraph);
let mut spans = paragraph.into_inner();
assert_pair!(spans.next().unwrap(), Rule::Text, "me ");
assert_pair!(spans.next().unwrap(), Rule::Mention, "@example");
assert_pair!(spans.next().unwrap(), Rule::Text, ".");
let paragraph = blocks.next().unwrap();
assert_eq!(paragraph.as_rule(), Rule::Paragraph);
let mut spans = paragraph.into_inner();
assert_pair!(spans.next().unwrap(), Rule::Mention, "@me");
assert_pair!(spans.next().unwrap(), Rule::Mention, "@example");
let paragraph = blocks.next().unwrap();
assert_eq!(paragraph.as_rule(), Rule::Paragraph);
let mut spans = paragraph.into_inner();
assert_pair!(spans.next().unwrap(), Rule::Mention, "@me");
assert_pair!(spans.next().unwrap(), Rule::Text, ".@example");
blocks.next().unwrap();
let paragraph = blocks.next().unwrap();
assert_eq!(paragraph.as_rule(), Rule::Paragraph);
let mut spans = paragraph.into_inner();
assert_pair!(spans.next().unwrap(), Rule::Text, "##phonenumber");
let paragraph = blocks.next().unwrap();
assert_eq!(paragraph.as_rule(), Rule::Paragraph);
let mut spans = paragraph.into_inner();
assert_pair!(spans.next().unwrap(), Rule::Text, "phone#number#");
let paragraph = blocks.next().unwrap();
assert_eq!(paragraph.as_rule(), Rule::Paragraph);
let mut spans = paragraph.into_inner();
assert_pair!(spans.next().unwrap(), Rule::HashTag, "#phone");
assert_pair!(spans.next().unwrap(), Rule::Text, " ");
assert_pair!(spans.next().unwrap(), Rule::HashTag, "#number");
let paragraph = blocks.next().unwrap();
assert_eq!(paragraph.as_rule(), Rule::Paragraph);
let mut spans = paragraph.into_inner();
assert_pair!(spans.next().unwrap(), Rule::HashTag, "#phone");
assert_pair!(spans.next().unwrap(), Rule::HashTag, "#number");
blocks.next().unwrap();
let paragraph = blocks.next().unwrap();
assert_eq!(paragraph.as_rule(), Rule::Paragraph);
let mut spans = paragraph.into_inner();
assert_pair!(spans.next().unwrap(), Rule::Text, "fizz[[buzz]] ");
assert_pair!(spans.next().unwrap(), Rule::WikiLink, "[[fizz]]");
let paragraph = blocks.next().unwrap();
assert_eq!(paragraph.as_rule(), Rule::Paragraph);
let mut spans = paragraph.into_inner();
assert_pair!(spans.next().unwrap(), Rule::WikiLink, "[[fizz]]");
assert_pair!(spans.next().unwrap(), Rule::Text, " ");
assert_pair!(spans.next().unwrap(), Rule::WikiLink, "[[buzz]]");
assert_pair!(spans.next().unwrap(), Rule::Text, "fizz");
let paragraph = blocks.next().unwrap();
assert_eq!(paragraph.as_rule(), Rule::Paragraph);
let mut spans = paragraph.into_inner();
assert_pair!(spans.next().unwrap(), Rule::Text, "[[[[fizzbuzz]]]]");
let paragraph = blocks.next().unwrap();
assert_eq!(paragraph.as_rule(), Rule::Paragraph);
let mut spans = paragraph.into_inner();
assert_pair!(spans.next().unwrap(), Rule::WikiLink, "[[ [[fizzbuzz]]]]");
}
}