use crate::quotes::{read_reference, DelimitedReferences, Reading};
use nom::{
branch::alt,
bytes::complete::{tag, take_while, take_while1},
character::complete::{char, line_ending},
combinator::eof,
multi::{many0, many1},
sequence::{preceded, terminated},
IResult, Parser,
};
use std::cell::{OnceCell, RefCell};
use std::collections::HashMap;
#[derive(Debug, Clone, PartialEq)]
pub struct Link {
pub id: Option<String>,
pub values: Vec<Link>,
pub children: Vec<Link>,
pub is_indented_id: bool,
pub nested: Option<Vec<Link>>,
}
impl Link {
pub fn new_singlet(id: String) -> Self {
Link {
id: Some(id),
values: vec![],
children: vec![],
is_indented_id: false,
nested: None,
}
}
pub fn new_indented_id(id: String) -> Self {
Link {
id: Some(id),
values: vec![],
children: vec![],
is_indented_id: true,
nested: None,
}
}
pub fn new_value(values: Vec<Link>) -> Self {
Link {
id: None,
values,
children: vec![],
is_indented_id: false,
nested: None,
}
}
pub fn new_link(id: Option<String>, values: Vec<Link>) -> Self {
Link {
id,
values,
children: vec![],
is_indented_id: false,
nested: None,
}
}
pub fn new_nested(body: Vec<Link>) -> Self {
Link {
id: None,
values: vec![],
children: vec![],
is_indented_id: false,
nested: Some(body),
}
}
pub fn with_children(mut self, children: Vec<Link>) -> Self {
self.children = children;
self
}
}
pub struct ParserState {
indentation_stack: RefCell<Vec<usize>>,
base_indentation: RefCell<Option<usize>>,
nested_depth: RefCell<usize>,
furthest: RefCell<FurthestFailure>,
unreadable_lines: RefCell<HashMap<LineKey, LineFailure>>,
references: OnceCell<DelimitedReferences>,
}
type LineKey = (usize, bool);
type LineFailure = (usize, nom::error::ErrorKind);
#[derive(Debug, Clone, Default)]
struct FurthestFailure {
address: Option<usize>,
expected: Vec<&'static str>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ParseFailure {
pub offset: usize,
pub expected: Vec<&'static str>,
pub kind: Option<nom::error::ErrorKind>,
}
pub struct SavedContext {
indentation_stack: Vec<usize>,
base_indentation: Option<usize>,
}
impl Default for ParserState {
fn default() -> Self {
Self::new()
}
}
impl ParserState {
pub fn new() -> Self {
ParserState {
indentation_stack: RefCell::new(vec![0]),
base_indentation: RefCell::new(None),
nested_depth: RefCell::new(0),
furthest: RefCell::new(FurthestFailure::default()),
unreadable_lines: RefCell::new(HashMap::new()),
references: OnceCell::new(),
}
}
pub fn set_base_indentation(&self, indent: usize) {
let mut base = self.base_indentation.borrow_mut();
if base.is_none() {
*base = Some(indent);
}
}
pub fn get_base_indentation(&self) -> usize {
self.base_indentation.borrow().unwrap_or(0)
}
pub fn normalize_indentation(&self, indent: usize) -> usize {
let base = self.get_base_indentation();
indent.saturating_sub(base)
}
pub fn push_indentation(&self, indent: usize) {
self.indentation_stack.borrow_mut().push(indent);
}
pub fn pop_indentation(&self) {
let mut stack = self.indentation_stack.borrow_mut();
if stack.len() > 1 {
stack.pop();
}
}
pub fn current_indentation(&self) -> usize {
*self.indentation_stack.borrow().last().unwrap_or(&0)
}
pub fn check_indentation(&self, indent: usize) -> bool {
indent >= self.current_indentation()
}
pub fn enter_nested_context(&self) -> SavedContext {
let saved = SavedContext {
indentation_stack: self.indentation_stack.replace(vec![0]),
base_indentation: self.base_indentation.replace(None),
};
*self.nested_depth.borrow_mut() += 1;
saved
}
pub fn exit_nested_context(&self, saved: SavedContext) {
*self.indentation_stack.borrow_mut() = saved.indentation_stack;
*self.base_indentation.borrow_mut() = saved.base_indentation;
let mut depth = self.nested_depth.borrow_mut();
if *depth > 0 {
*depth -= 1;
}
}
pub fn is_inside_nested_context(&self) -> bool {
*self.nested_depth.borrow() > 0
}
fn expected_at(&self, at: &str, what: &'static str) {
let address = at.as_ptr() as usize;
let mut furthest = self.furthest.borrow_mut();
match furthest.address {
Some(recorded) if recorded > address => {}
Some(recorded) if recorded == address => {
if !furthest.expected.contains(&what) {
furthest.expected.push(what);
}
}
_ => {
furthest.address = Some(address);
furthest.expected = vec![what];
}
}
}
fn failure(&self, document: &str, error: &nom::Err<nom::error::Error<&str>>) -> ParseFailure {
let base = document.as_ptr() as usize;
let (nom_offset, kind) = match error {
nom::Err::Error(e) | nom::Err::Failure(e) => (
(e.input.as_ptr() as usize).saturating_sub(base),
Some(e.code),
),
nom::Err::Incomplete(_) => (document.len(), None),
};
let furthest = self.furthest.borrow();
let tracked = furthest
.address
.map(|address| address.saturating_sub(base))
.unwrap_or(0);
let offset = tracked.max(nom_offset).min(document.len());
let expected = if tracked == offset {
furthest.expected.clone()
} else {
Vec::new()
};
ParseFailure {
offset,
expected,
kind,
}
}
}
fn expected<'a, T>(
input: &'a str,
state: &ParserState,
what: &'static str,
kind: nom::error::ErrorKind,
) -> IResult<&'a str, T> {
state.expected_at(input, what);
Err(nom::Err::Error(nom::error::Error::new(input, kind)))
}
pub(crate) fn is_whitespace_char(c: char) -> bool {
c == ' ' || c == '\t' || c == '\n' || c == '\r'
}
fn is_horizontal_whitespace(c: char) -> bool {
c == ' ' || c == '\t'
}
fn is_reference_char(c: char) -> bool {
!is_whitespace_char(c) && c != '(' && c != ':' && c != ')'
}
fn horizontal_whitespace(input: &str) -> IResult<&str, &str> {
take_while(is_horizontal_whitespace)(input)
}
fn whitespace(input: &str) -> IResult<&str, &str> {
take_while(is_whitespace_char)(input)
}
fn simple_reference(input: &str) -> IResult<&str, String> {
take_while1(is_reference_char)
.map(|s: &str| s.to_string())
.parse(input)
}
pub fn quoted_reference_end(document: &str, start: usize) -> Option<usize> {
let reading = read_reference(document.get(start..)?)?;
Some(start + reading.length)
}
fn delimited_reference<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, String> {
let references = state
.references
.get_or_init(|| DelimitedReferences::new(input));
match references.read(input) {
Some(Reading { value, length }) => Ok((&input[length..], value)),
None => Err(nom::Err::Error(nom::error::Error::new(
input,
nom::error::ErrorKind::Tag,
))),
}
}
fn reference<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, String> {
let parsed = alt((|i| delimited_reference(i, state), simple_reference)).parse(input);
if parsed.is_err() {
state.expected_at(input, "a reference");
}
parsed
}
fn eol<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, &'a str> {
let parsed = alt((
preceded(horizontal_whitespace, alt((line_ending, tag("\r")))),
preceded(horizontal_whitespace, eof),
|i| nested_group_end(i, state),
))
.parse(input);
if parsed.is_err() {
state.expected_at(input, "end of line");
}
parsed
}
fn nested_group_end<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, &'a str> {
if !state.is_inside_nested_context() {
return Err(nom::Err::Error(nom::error::Error::new(
input,
nom::error::ErrorKind::Verify,
)));
}
let (rest, _) = horizontal_whitespace(input)?;
if rest.starts_with(')') {
Ok((rest, ""))
} else {
expected(rest, state, "\")\"", nom::error::ErrorKind::Char)
}
}
fn skip_empty_lines(input: &str) -> &str {
let mut rest = input;
loop {
let line_start = rest.trim_start_matches(is_horizontal_whitespace);
match strip_line_ending(line_start) {
Some(next) => rest = next,
None => return rest,
}
}
}
fn strip_line_ending(input: &str) -> Option<&str> {
input
.strip_prefix("\r\n")
.or_else(|| input.strip_prefix('\n'))
.or_else(|| input.strip_prefix('\r'))
}
fn reference_or_link<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, Link> {
alt((
|i| nested_group(i, state),
(|i| reference(i, state)).map(Link::new_singlet),
))
.parse(input)
}
fn single_line_value_and_whitespace<'a>(
input: &'a str,
state: &ParserState,
) -> IResult<&'a str, Link> {
preceded(horizontal_whitespace, |i| reference_or_link(i, state)).parse(input)
}
fn single_line_values<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, Vec<Link>> {
many1(|i| single_line_value_and_whitespace(i, state)).parse(input)
}
fn single_line_link<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, Link> {
let (input, _) = horizontal_whitespace(input)?;
let (input, id) = reference(input, state)?;
let (input, _) = horizontal_whitespace(input)?;
let (input, _) = colon(input, state)?;
let (input, values) = single_line_values(input, state)?;
Ok((input, Link::new_link(Some(id), values)))
}
fn colon<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, char> {
character(':', input, state, "\":\"")
}
fn character<'a>(
wanted: char,
input: &'a str,
state: &ParserState,
what: &'static str,
) -> IResult<&'a str, char> {
let parsed: IResult<&'a str, char> = char(wanted).parse(input);
match parsed {
Ok(parsed) => Ok(parsed),
Err(_) => expected(input, state, what, nom::error::ErrorKind::Char),
}
}
fn single_line_value_link<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, Link> {
(|i| single_line_values(i, state))
.map(Link::new_value)
.parse(input)
}
fn indented_id_link<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, Link> {
let (input, id) = reference(input, state)?;
let (input, _) = horizontal_whitespace(input)?;
let (input, _) = colon(input, state)?;
let (input, _) = eol(input, state)?;
Ok((input, Link::new_indented_id(id)))
}
fn nested_group<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, Link> {
let (body_input, _) = character('(', input, state, "\"(\"")?;
let saved = state.enter_nested_context();
let result = nested_group_body(body_input, state);
state.exit_nested_context(saved);
result
}
fn nested_group_body<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, Link> {
if let Ok((rest, body)) = links(skip_empty_lines(input), state) {
let (rest, _) = whitespace(rest)?;
let (rest, _) = closing_parenthesis(rest, state)?;
return Ok((rest, Link::new_nested(body)));
}
let (rest, _) = whitespace(input)?;
let (rest, _) = closing_parenthesis(rest, state)?;
Ok((rest, Link::new_nested(vec![])))
}
fn closing_parenthesis<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, char> {
character(')', input, state, "\")\"")
}
fn single_line_any_link<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, Link> {
alt((
terminated(|i| single_line_link(i, state), |i| eol(i, state)),
terminated(|i| single_line_value_link(i, state), |i| eol(i, state)),
))
.parse(input)
}
fn any_link<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, Link> {
let key = (input.as_ptr() as usize, state.is_inside_nested_context());
if let Some(&(offset, kind)) = state.unreadable_lines.borrow().get(&key) {
return Err(nom::Err::Error(nom::error::Error::new(
&input[offset..],
kind,
)));
}
let parsed = read_any_link(input, state);
if let Err(nom::Err::Error(error)) = &parsed {
let offset = error.input.as_ptr() as usize - input.as_ptr() as usize;
state
.unreadable_lines
.borrow_mut()
.insert(key, (offset, error.code));
}
parsed
}
fn read_any_link<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, Link> {
let (rest, group) = match nested_group(input, state) {
Ok(parsed) => parsed,
Err(_) if input.starts_with('(') => {
return reference(input, state).map(|(rest, id)| (rest, Link::new_singlet(id)))
}
Err(_) => {
return alt((
|i| indented_id_link(i, state),
|i| single_line_any_link(i, state),
))
.parse(input)
}
};
if let Ok((rest, _)) = eol(rest, state) {
return Ok((rest, group));
}
let (rest, more) = many0(|i| single_line_value_and_whitespace(i, state)).parse(rest)?;
let (rest, _) = eol(rest, state)?;
let mut values = Vec::with_capacity(more.len() + 1);
values.push(group);
values.extend(more);
Ok((rest, Link::new_value(values)))
}
fn count_indentation(input: &str) -> IResult<&str, usize> {
take_while(|c| c == ' ').map(|s: &str| s.len()).parse(input)
}
fn push_indentation<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, ()> {
let (input, spaces) = count_indentation(skip_empty_lines(input))?;
let normalized_spaces = state.normalize_indentation(spaces);
let current = state.current_indentation();
if normalized_spaces > current {
state.push_indentation(normalized_spaces);
Ok((input, ()))
} else {
Err(nom::Err::Error(nom::error::Error::new(
input,
nom::error::ErrorKind::Verify,
)))
}
}
fn check_indentation<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, ()> {
let (input, spaces) = count_indentation(input)?;
let normalized_spaces = state.normalize_indentation(spaces);
if state.check_indentation(normalized_spaces) {
Ok((input, ()))
} else {
Err(nom::Err::Error(nom::error::Error::new(
input,
nom::error::ErrorKind::Verify,
)))
}
}
fn element<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, Link> {
let (input, link) = any_link(input, state)?;
let indentation = state.indentation_stack.borrow().clone();
if let Ok((child_input, _)) = push_indentation(input, state) {
if let Ok((rest, children)) = links(child_input, state) {
return Ok((rest, link.with_children(children)));
}
state.indentation_stack.replace(indentation);
}
Ok((input, link))
}
fn first_line<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, Link> {
let (input, spaces) = count_indentation(input)?;
state.set_base_indentation(spaces);
element(input, state)
}
fn line<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, Link> {
preceded(|i| check_indentation(i, state), |i| element(i, state)).parse(skip_empty_lines(input))
}
fn links<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, Vec<Link>> {
let (input, first) = first_line(input, state)?;
let (input, rest) = many0(|i| line(i, state)).parse(input)?;
state.pop_indentation();
let mut result = vec![first];
result.extend(rest);
Ok((input, result))
}
pub fn parse_document(input: &str) -> IResult<&str, Vec<Link>> {
let state = ParserState::new();
document(input, &state)
}
pub fn parse_document_with_diagnostics(input: &str) -> Result<Vec<Link>, ParseFailure> {
let state = ParserState::new();
match document(input, &state) {
Ok((_, links)) => Ok(links),
Err(error) => Err(state.failure(input, &error)),
}
}
fn document<'a>(input: &'a str, state: &ParserState) -> IResult<&'a str, Vec<Link>> {
state
.references
.get_or_init(|| DelimitedReferences::new(input));
let document = skip_empty_lines(input);
if document.trim_matches(is_whitespace_char).is_empty() {
return Ok(("", vec![]));
}
let (rest, result) = links(document, state)?;
let (rest, _) = whitespace(rest)?;
let end: IResult<&'a str, &'a str> = eof(rest);
let (rest, _) = match end {
Ok(parsed) => parsed,
Err(_) => return expected(rest, state, "end of input", nom::error::ErrorKind::Eof),
};
Ok((rest, result))
}