2023-04-11 19:16:04 -04:00
|
|
|
use std::collections::HashMap;
|
|
|
|
|
2023-04-11 14:50:37 -04:00
|
|
|
use nom::branch::alt;
|
2023-04-11 15:23:16 -04:00
|
|
|
use nom::bytes::complete::escaped;
|
2023-04-11 14:50:37 -04:00
|
|
|
use nom::bytes::complete::tag;
|
|
|
|
use nom::bytes::complete::take_till1;
|
|
|
|
use nom::character::complete::multispace0;
|
|
|
|
use nom::character::complete::multispace1;
|
2023-04-11 15:23:16 -04:00
|
|
|
use nom::character::complete::one_of;
|
2023-04-11 16:02:57 -04:00
|
|
|
use nom::combinator::map;
|
2023-04-11 15:08:46 -04:00
|
|
|
use nom::combinator::not;
|
2023-04-11 16:02:57 -04:00
|
|
|
use nom::combinator::opt;
|
2023-04-11 15:08:46 -04:00
|
|
|
use nom::combinator::peek;
|
2023-04-11 14:50:37 -04:00
|
|
|
use nom::multi::separated_list1;
|
2023-04-11 15:08:46 -04:00
|
|
|
use nom::sequence::delimited;
|
2023-04-11 16:02:57 -04:00
|
|
|
use nom::sequence::preceded;
|
|
|
|
use nom::sequence::tuple;
|
2023-04-11 14:50:37 -04:00
|
|
|
|
2023-08-23 00:30:26 -04:00
|
|
|
use super::org_source::convert_error;
|
|
|
|
use super::org_source::OrgSource;
|
|
|
|
use super::util::get_consumed;
|
2023-04-22 21:45:18 -04:00
|
|
|
use crate::error::Res;
|
|
|
|
|
2023-04-11 14:50:37 -04:00
|
|
|
#[derive(Debug)]
|
2023-09-11 13:13:28 -04:00
|
|
|
pub enum Token<'s> {
|
2023-04-11 14:50:37 -04:00
|
|
|
Atom(&'s str),
|
|
|
|
List(Vec<Token<'s>>),
|
2023-04-11 16:02:57 -04:00
|
|
|
TextWithProperties(TextWithProperties<'s>),
|
2023-08-13 00:41:55 -04:00
|
|
|
Vector(Vec<Token<'s>>),
|
2023-04-11 16:02:57 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
2023-09-11 13:13:28 -04:00
|
|
|
pub struct TextWithProperties<'s> {
|
|
|
|
pub(crate) text: &'s str,
|
|
|
|
pub(crate) properties: Vec<Token<'s>>,
|
2023-04-11 14:50:37 -04:00
|
|
|
}
|
|
|
|
|
2023-04-22 18:18:13 -04:00
|
|
|
enum ParseState {
|
|
|
|
Normal,
|
|
|
|
Escape,
|
|
|
|
}
|
|
|
|
|
2023-04-11 17:35:09 -04:00
|
|
|
impl<'s> Token<'s> {
|
2023-09-11 13:13:28 -04:00
|
|
|
pub(crate) fn as_vector<'p>(
|
|
|
|
&'p self,
|
|
|
|
) -> Result<&'p Vec<Token<'s>>, Box<dyn std::error::Error>> {
|
2023-08-13 01:06:55 -04:00
|
|
|
Ok(match self {
|
|
|
|
Token::Vector(children) => Ok(children),
|
2023-08-28 01:04:51 -04:00
|
|
|
_ => Err(format!("wrong token type, expected vector: {:?}", self)),
|
2023-08-13 01:06:55 -04:00
|
|
|
}?)
|
|
|
|
}
|
|
|
|
|
2023-09-11 13:13:28 -04:00
|
|
|
pub(crate) fn as_list<'p>(&'p self) -> Result<&'p Vec<Token<'s>>, Box<dyn std::error::Error>> {
|
2023-04-11 17:35:09 -04:00
|
|
|
Ok(match self {
|
|
|
|
Token::List(children) => Ok(children),
|
2023-08-28 01:04:51 -04:00
|
|
|
_ => Err(format!("wrong token type, expected list: {:?}", self)),
|
2023-04-11 17:35:09 -04:00
|
|
|
}?)
|
|
|
|
}
|
|
|
|
|
2023-09-11 13:13:28 -04:00
|
|
|
pub(crate) fn as_atom<'p>(&'p self) -> Result<&'s str, Box<dyn std::error::Error>> {
|
2023-04-11 17:35:09 -04:00
|
|
|
Ok(match self {
|
|
|
|
Token::Atom(body) => Ok(*body),
|
2023-08-28 01:04:51 -04:00
|
|
|
_ => Err(format!("wrong token type, expected atom: {:?}", self)),
|
2023-04-11 17:35:09 -04:00
|
|
|
}?)
|
|
|
|
}
|
2023-04-11 19:16:04 -04:00
|
|
|
|
2023-09-11 13:13:28 -04:00
|
|
|
pub(crate) fn as_text<'p>(
|
|
|
|
&'p self,
|
|
|
|
) -> Result<&'p TextWithProperties<'s>, Box<dyn std::error::Error>> {
|
2023-04-22 17:58:16 -04:00
|
|
|
Ok(match self {
|
|
|
|
Token::TextWithProperties(body) => Ok(body),
|
2023-08-28 01:04:51 -04:00
|
|
|
_ => Err(format!("wrong token type, expected text: {:?}", self)),
|
2023-04-22 17:58:16 -04:00
|
|
|
}?)
|
|
|
|
}
|
|
|
|
|
2023-09-11 13:13:28 -04:00
|
|
|
pub(crate) fn as_map<'p>(
|
2023-04-11 19:16:04 -04:00
|
|
|
&'p self,
|
|
|
|
) -> Result<HashMap<&'s str, &'p Token<'s>>, Box<dyn std::error::Error>> {
|
|
|
|
let mut hashmap = HashMap::new();
|
|
|
|
|
|
|
|
let children = self.as_list()?;
|
|
|
|
if children.len() % 2 != 0 {
|
|
|
|
return Err("Expecting an even number of children".into());
|
|
|
|
}
|
|
|
|
let mut key: Option<&str> = None;
|
|
|
|
for child in children.iter() {
|
|
|
|
match key {
|
|
|
|
None => {
|
|
|
|
key = Some(child.as_atom()?);
|
|
|
|
}
|
|
|
|
Some(key_val) => {
|
|
|
|
key = None;
|
|
|
|
hashmap.insert(key_val, child);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(hashmap)
|
|
|
|
}
|
2023-04-11 17:35:09 -04:00
|
|
|
}
|
|
|
|
|
2023-09-11 13:13:28 -04:00
|
|
|
pub(crate) fn unquote(text: &str) -> Result<String, Box<dyn std::error::Error>> {
|
2023-08-25 06:46:00 -04:00
|
|
|
let mut out = String::with_capacity(text.len());
|
|
|
|
if !text.starts_with(r#"""#) {
|
|
|
|
return Err("Quoted text does not start with quote.".into());
|
|
|
|
}
|
|
|
|
if !text.ends_with(r#"""#) {
|
|
|
|
return Err("Quoted text does not end with quote.".into());
|
|
|
|
}
|
|
|
|
let interior_text = &text[1..(text.len() - 1)];
|
|
|
|
let mut state = ParseState::Normal;
|
|
|
|
for current_char in interior_text.chars().into_iter() {
|
|
|
|
state = match (state, current_char) {
|
|
|
|
(ParseState::Normal, '\\') => ParseState::Escape,
|
|
|
|
(ParseState::Normal, _) => {
|
|
|
|
out.push(current_char);
|
|
|
|
ParseState::Normal
|
|
|
|
}
|
|
|
|
(ParseState::Escape, 'n') => {
|
|
|
|
out.push('\n');
|
|
|
|
ParseState::Normal
|
|
|
|
}
|
|
|
|
(ParseState::Escape, '\\') => {
|
|
|
|
out.push('\\');
|
|
|
|
ParseState::Normal
|
|
|
|
}
|
|
|
|
(ParseState::Escape, '"') => {
|
|
|
|
out.push('"');
|
|
|
|
ParseState::Normal
|
|
|
|
}
|
|
|
|
_ => todo!(),
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(out)
|
|
|
|
}
|
|
|
|
|
2023-08-10 20:04:59 -04:00
|
|
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
2023-09-11 13:13:28 -04:00
|
|
|
pub fn sexp_with_padding<'s>(input: &'s str) -> Res<&'s str, Token<'s>> {
|
2023-04-11 14:50:37 -04:00
|
|
|
let (remaining, _) = multispace0(input)?;
|
2023-08-23 00:30:26 -04:00
|
|
|
let remaining = OrgSource::new(remaining);
|
2023-08-24 23:43:41 +00:00
|
|
|
let (remaining, tkn) = token(remaining)
|
|
|
|
.map(|(rem, out)| (Into::<&str>::into(rem), out))
|
|
|
|
.map_err(convert_error)?;
|
2023-04-11 14:50:37 -04:00
|
|
|
let (remaining, _) = multispace0(remaining)?;
|
|
|
|
Ok((remaining, tkn))
|
|
|
|
}
|
|
|
|
|
2023-08-10 20:04:59 -04:00
|
|
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
2023-09-11 13:13:28 -04:00
|
|
|
fn sexp<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, Token<'s>> {
|
2023-04-21 20:53:55 -04:00
|
|
|
let (remaining, tkn) = token(input)?;
|
|
|
|
Ok((remaining, tkn))
|
|
|
|
}
|
|
|
|
|
2023-08-10 20:04:59 -04:00
|
|
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
2023-08-23 00:30:26 -04:00
|
|
|
fn token<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, Token<'s>> {
|
2023-08-13 00:41:55 -04:00
|
|
|
alt((list, vector, atom))(input)
|
2023-04-11 14:50:37 -04:00
|
|
|
}
|
|
|
|
|
2023-08-10 20:04:59 -04:00
|
|
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
2023-08-23 00:30:26 -04:00
|
|
|
fn list<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, Token<'s>> {
|
2023-04-11 16:02:57 -04:00
|
|
|
let (remaining, _) = tag("(")(input)?;
|
2023-04-11 15:23:16 -04:00
|
|
|
let (remaining, children) = delimited(
|
|
|
|
multispace0,
|
|
|
|
separated_list1(multispace1, token),
|
|
|
|
multispace0,
|
|
|
|
)(remaining)?;
|
2023-04-11 16:02:57 -04:00
|
|
|
let (remaining, _) = tag(")")(remaining)?;
|
2023-04-11 14:50:37 -04:00
|
|
|
Ok((remaining, Token::List(children)))
|
|
|
|
}
|
|
|
|
|
2023-08-13 00:41:55 -04:00
|
|
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
2023-08-23 00:30:26 -04:00
|
|
|
fn vector<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, Token<'s>> {
|
2023-08-13 00:41:55 -04:00
|
|
|
let (remaining, _) = tag("[")(input)?;
|
|
|
|
let (remaining, children) = delimited(
|
|
|
|
multispace0,
|
|
|
|
separated_list1(multispace1, token),
|
|
|
|
multispace0,
|
|
|
|
)(remaining)?;
|
|
|
|
let (remaining, _) = tag("]")(remaining)?;
|
|
|
|
Ok((remaining, Token::Vector(children)))
|
|
|
|
}
|
|
|
|
|
2023-08-10 20:04:59 -04:00
|
|
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
2023-08-23 00:30:26 -04:00
|
|
|
fn atom<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, Token<'s>> {
|
2023-08-13 00:41:55 -04:00
|
|
|
not(peek(one_of(")]")))(input)?;
|
2023-08-13 00:46:15 -04:00
|
|
|
alt((
|
|
|
|
text_with_properties,
|
|
|
|
hash_notation,
|
|
|
|
quoted_atom,
|
|
|
|
unquoted_atom,
|
|
|
|
))(input)
|
2023-04-11 14:50:37 -04:00
|
|
|
}
|
|
|
|
|
2023-08-10 20:04:59 -04:00
|
|
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
2023-08-23 00:30:26 -04:00
|
|
|
fn unquoted_atom<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, Token<'s>> {
|
2023-04-11 14:50:37 -04:00
|
|
|
let (remaining, body) = take_till1(|c| match c {
|
2023-08-13 00:41:55 -04:00
|
|
|
' ' | '\t' | '\r' | '\n' | ')' | ']' => true,
|
2023-04-11 14:50:37 -04:00
|
|
|
_ => false,
|
|
|
|
})(input)?;
|
2023-08-23 00:30:26 -04:00
|
|
|
Ok((remaining, Token::Atom(body.into())))
|
2023-04-11 14:50:37 -04:00
|
|
|
}
|
|
|
|
|
2023-08-10 20:04:59 -04:00
|
|
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
2023-08-23 00:30:26 -04:00
|
|
|
fn quoted_atom<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, Token<'s>> {
|
2023-04-11 15:23:16 -04:00
|
|
|
let (remaining, _) = tag(r#"""#)(input)?;
|
|
|
|
let (remaining, _) = escaped(
|
|
|
|
take_till1(|c| match c {
|
2023-04-21 21:15:18 -04:00
|
|
|
'\\' | '"' => true,
|
2023-04-11 15:23:16 -04:00
|
|
|
_ => false,
|
|
|
|
}),
|
|
|
|
'\\',
|
2023-07-18 23:08:58 -04:00
|
|
|
one_of(r#""n\\"#),
|
2023-04-11 15:23:16 -04:00
|
|
|
)(remaining)?;
|
|
|
|
let (remaining, _) = tag(r#"""#)(remaining)?;
|
|
|
|
let source = get_consumed(input, remaining);
|
2023-08-23 00:30:26 -04:00
|
|
|
Ok((remaining, Token::Atom(source.into())))
|
2023-04-11 15:23:16 -04:00
|
|
|
}
|
|
|
|
|
2023-08-13 00:46:15 -04:00
|
|
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
2023-08-23 00:30:26 -04:00
|
|
|
fn hash_notation<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, Token<'s>> {
|
2023-08-13 00:46:15 -04:00
|
|
|
let (remaining, _) = tag("#<")(input)?;
|
|
|
|
let (remaining, _body) = take_till1(|c| match c {
|
|
|
|
'>' => true,
|
|
|
|
_ => false,
|
|
|
|
})(remaining)?;
|
|
|
|
let (remaining, _) = tag(">")(remaining)?;
|
|
|
|
let source = get_consumed(input, remaining);
|
2023-08-23 00:30:26 -04:00
|
|
|
Ok((remaining, Token::Atom(source.into())))
|
2023-08-13 00:46:15 -04:00
|
|
|
}
|
|
|
|
|
2023-08-23 00:30:26 -04:00
|
|
|
fn text_with_properties<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, Token<'s>> {
|
2023-04-11 16:02:57 -04:00
|
|
|
let (remaining, _) = tag("#(")(input)?;
|
|
|
|
let (remaining, (text, props)) = delimited(
|
|
|
|
multispace0,
|
|
|
|
tuple((
|
|
|
|
map(quoted_atom, |atom| match atom {
|
|
|
|
Token::Atom(body) => body,
|
|
|
|
_ => unreachable!(),
|
|
|
|
}),
|
|
|
|
preceded(multispace1, opt(separated_list1(multispace1, token))),
|
|
|
|
)),
|
|
|
|
multispace0,
|
|
|
|
)(remaining)?;
|
|
|
|
let (remaining, _) = tag(")")(remaining)?;
|
|
|
|
Ok((
|
|
|
|
remaining,
|
|
|
|
Token::TextWithProperties(TextWithProperties {
|
|
|
|
text,
|
|
|
|
properties: props.unwrap_or(Vec::new()),
|
|
|
|
}),
|
|
|
|
))
|
|
|
|
}
|
|
|
|
|
2023-04-11 14:50:37 -04:00
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn simple() {
|
|
|
|
let input = " (foo bar baz ) ";
|
2023-04-21 20:53:55 -04:00
|
|
|
let (remaining, parsed) = sexp_with_padding(input).expect("Parse the input");
|
2023-04-11 14:50:37 -04:00
|
|
|
assert_eq!(remaining, "");
|
|
|
|
assert!(match parsed {
|
|
|
|
Token::Atom(_) => false,
|
|
|
|
Token::List(_) => true,
|
2023-04-11 16:02:57 -04:00
|
|
|
Token::TextWithProperties(_) => false,
|
2023-08-13 00:41:55 -04:00
|
|
|
Token::Vector(_) => false,
|
2023-04-11 14:50:37 -04:00
|
|
|
});
|
|
|
|
}
|
2023-04-11 15:23:16 -04:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn quoted() {
|
|
|
|
let input = r#" ("foo" bar baz ) "#;
|
2023-04-21 20:53:55 -04:00
|
|
|
let (remaining, parsed) = sexp_with_padding(input).expect("Parse the input");
|
2023-04-11 15:23:16 -04:00
|
|
|
assert_eq!(remaining, "");
|
|
|
|
assert!(match parsed {
|
|
|
|
Token::Atom(_) => false,
|
|
|
|
Token::List(_) => true,
|
2023-04-11 16:02:57 -04:00
|
|
|
Token::TextWithProperties(_) => false,
|
2023-08-13 00:41:55 -04:00
|
|
|
Token::Vector(_) => false,
|
2023-04-11 15:23:16 -04:00
|
|
|
});
|
2023-04-11 16:02:57 -04:00
|
|
|
let children = match parsed {
|
|
|
|
Token::List(children) => children,
|
|
|
|
_ => panic!("Should be a list."),
|
|
|
|
};
|
|
|
|
assert_eq!(
|
|
|
|
match children.first() {
|
|
|
|
Some(Token::Atom(body)) => *body,
|
|
|
|
_ => panic!("First child should be an atom."),
|
|
|
|
},
|
|
|
|
r#""foo""#
|
|
|
|
)
|
2023-04-11 15:23:16 -04:00
|
|
|
}
|
2023-04-21 21:15:18 -04:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn quoted_containing_paren() {
|
|
|
|
let input = r#" (foo "b(a)r" baz ) "#;
|
|
|
|
let (remaining, parsed) = sexp_with_padding(input).expect("Parse the input");
|
|
|
|
assert_eq!(remaining, "");
|
|
|
|
assert!(match parsed {
|
|
|
|
Token::List(_) => true,
|
|
|
|
_ => false,
|
|
|
|
});
|
|
|
|
let children = match parsed {
|
|
|
|
Token::List(children) => children,
|
|
|
|
_ => panic!("Should be a list."),
|
|
|
|
};
|
|
|
|
assert_eq!(
|
|
|
|
match children.first() {
|
|
|
|
Some(Token::Atom(body)) => *body,
|
|
|
|
_ => panic!("First child should be an atom."),
|
|
|
|
},
|
|
|
|
r#"foo"#
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
match children.iter().nth(1) {
|
|
|
|
Some(Token::Atom(body)) => *body,
|
|
|
|
_ => panic!("Second child should be an atom."),
|
|
|
|
},
|
|
|
|
r#""b(a)r""#
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
match children.iter().nth(2) {
|
|
|
|
Some(Token::Atom(body)) => *body,
|
|
|
|
_ => panic!("Third child should be an atom."),
|
|
|
|
},
|
|
|
|
r#"baz"#
|
|
|
|
);
|
|
|
|
}
|
2023-07-18 23:08:58 -04:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn string_containing_escaped_characters() {
|
|
|
|
let input = r#" (foo "\\( x=2 \\)" bar) "#;
|
|
|
|
let (remaining, parsed) = sexp_with_padding(input).expect("Parse the input");
|
|
|
|
assert_eq!(remaining, "");
|
|
|
|
assert!(match parsed {
|
|
|
|
Token::Atom(_) => false,
|
|
|
|
Token::List(_) => true,
|
|
|
|
Token::TextWithProperties(_) => false,
|
2023-08-13 00:41:55 -04:00
|
|
|
Token::Vector(_) => false,
|
2023-07-18 23:08:58 -04:00
|
|
|
});
|
|
|
|
let children = match parsed {
|
|
|
|
Token::List(children) => children,
|
|
|
|
_ => panic!("Should be a list."),
|
|
|
|
};
|
|
|
|
assert_eq!(
|
|
|
|
match children.get(1) {
|
|
|
|
Some(Token::Atom(body)) => *body,
|
|
|
|
_ => panic!("First child should be an atom."),
|
|
|
|
},
|
|
|
|
r#""\\( x=2 \\)""#
|
|
|
|
)
|
|
|
|
}
|
2023-04-11 14:50:37 -04:00
|
|
|
}
|