use std::cell::OnceCell;
use std::rc::Rc;
use crate::detect::{Detected, detect_diagram};
use crate::errors::{Edit, Error, ErrorCode, Origin, input_offset, locating_in};
use crate::js::chars::len16;
use crate::preprocess::{cleanup_comments, encode_entities, normalize_line_breaks, preprocess};
use crate::value::Map;
pub const DEFAULT_LIMIT: usize = 50_000;
pub const DEFAULT_MAX_EDGES: usize = 5_000;
#[derive(Clone, Debug, PartialEq)]
pub struct ParseOptions {
pub limit: Option<usize>,
pub max_edges: Option<usize>,
pub config: Map,
}
impl Default for ParseOptions {
fn default() -> Self {
ParseOptions {
limit: Some(DEFAULT_LIMIT),
max_edges: Some(DEFAULT_MAX_EDGES),
config: Map::new(),
}
}
}
#[derive(Clone, Debug)]
pub(crate) struct Prepared {
pub(crate) found: Detected,
pub(crate) text: String,
pub(crate) with_comments: String,
pub(crate) config: Map,
pub(crate) title: Option<String>,
pub(crate) line_offset: usize,
pub(crate) max_edges: Option<usize>,
source: String,
}
pub(crate) fn prepare(source: &str, options: &ParseOptions) -> Result<Prepared, Error> {
if let Some(limit) = options.limit
&& len16(source) > limit
{
return Err(Error::new(
format!("Diagram source is longer than the limit of {limit} characters."),
ErrorCode::Limit,
));
}
let pre = preprocess(source, None)?;
let Some(found) = detect_diagram(&pre.text) else {
return Err(Error::new(
"No diagram type detected for the given text.",
ErrorCode::UnsupportedDiagram,
));
};
let mut config = options.config.clone();
for (k, v) in &pre.config {
config.insert(k, v.clone());
}
Ok(Prepared {
found,
text: pre.text,
with_comments: pre.with_comments,
config,
title: pre.title,
line_offset: pre.line_offset,
max_edges: options.max_edges,
source: source.to_string(),
})
}
impl Prepared {
pub(crate) fn source(&self, keep_comments: bool) -> String {
let text = if keep_comments {
&self.with_comments
} else {
&self.text
};
encode_entities(text, None) + "\n"
}
pub(crate) fn parse_with<T>(
&self,
keep_comments: bool,
parse: impl FnOnce(&str) -> Result<T, Error>,
) -> Result<T, Error> {
let parsed: Rc<str> = self.source(keep_comments).into();
let found = InSource {
source: self.source.clone(),
keep_comments,
read: OnceCell::new(),
};
let origin = Origin {
parsed: parsed.clone(),
find: Box::new(move |at| found.find(at)),
};
locating_in(origin, || parse(&parsed)).map_err(|e| e.tagged(self.found))
}
}
struct InSource {
source: String,
keep_comments: bool,
read: OnceCell<(String, Vec<Vec<Edit>>)>,
}
impl InSource {
fn find(&self, at: usize) -> (String, usize) {
let (source, steps) = self.read.get_or_init(|| {
let mut steps = Vec::new();
let pre = preprocess(&self.source, Some(&mut steps)).expect("read once already");
let text = if self.keep_comments {
pre.with_comments
} else {
cleanup_comments(&pre.with_comments, Some(&mut steps))
};
encode_entities(&text, Some(&mut steps));
(normalize_line_breaks(&self.source) + "\n", steps)
});
(source.clone(), input_offset(steps, at))
}
}