use std::{hash::Hash, ops::Range};
use nom::{
branch::alt,
bytes::complete::{tag, take_until},
character::complete::{anychar, char, none_of},
combinator::map,
multi::{many0, separated_list1},
sequence::{preceded, separated_pair, tuple},
IResult,
};
use nom_locate::LocatedSpan;
use crate::annotations::AquascopeAnnotations;
#[derive(PartialEq, Hash, Debug, Clone)]
pub struct AquascopeBlock {
pub operations: Vec<String>,
pub config: Vec<(String, String)>,
pub code: String,
pub annotations: AquascopeAnnotations,
}
impl AquascopeBlock {
fn parse(i: LocatedSpan<&str>) -> IResult<LocatedSpan<&str>, Self> {
fn parse_sym(i: LocatedSpan<&str>) -> IResult<LocatedSpan<&str>, String> {
let (i, v) = many0(none_of(",=\n+"))(i)?;
Ok((i, v.into_iter().collect::<String>()))
}
let mut parser = tuple((
tag("```aquascope"),
preceded(char(','), separated_list1(char('+'), parse_sym)),
many0(preceded(
char(','),
alt((
separated_pair(parse_sym, char('='), parse_sym),
map(parse_sym, |s| (s, String::from("true"))),
)),
)),
take_until("```"),
tag("```"),
));
let (i, (_, operations, config, code, _)) = parser(i)?;
let code = code.fragment().trim();
let (code, annotations) =
crate::annotations::parse_annotations(code).unwrap();
Ok((i, AquascopeBlock {
operations,
config,
code,
annotations,
}))
}
pub fn parse_all(content: &str) -> Vec<(Range<usize>, Self)> {
let mut content = LocatedSpan::new(content);
let mut to_process = Vec::new();
loop {
if let Ok((next, block)) = AquascopeBlock::parse(content) {
let range = content.location_offset() .. next.location_offset();
to_process.push((range, block));
content = next;
} else {
match anychar::<_, nom::error::Error<LocatedSpan<&str>>>(content) {
Ok((next, _)) => {
content = next;
}
Err(_) => break,
}
}
}
to_process
}
}
#[test]
fn test_parse_block() {
let inp = r#"```aquascope,interpreter,foo=bar,baz
content!
```"#;
let s = |s: &str| s.to_string();
let blocks = AquascopeBlock::parse_all(inp);
assert_eq!(blocks, vec![(0 .. inp.len(), AquascopeBlock {
operations: vec![s("interpreter")],
config: vec![(s("foo"), s("bar")), (s("baz"), s("true"))],
code: s("content!"),
annotations: Default::default()
})]);
}