use crate::context::RenderContext;
use crate::diagnostic::Diagnostic;
use crate::expanded::{self, Inputs};
use crate::host::{Data, Fetch, Files, Needed, Packs};
use crate::load;
use crate::parse::{self, Change, ParseOptions, TextEdit};
pub use crate::resolve::HostComponent;
use crate::resolve::{self, Ambient, Store};
use crate::tree::Move;
use crate::{expand, tree, validate};
use std::rc::Rc;
pub struct Processor<S> {
diagnostics: Vec<Diagnostic>,
state: S,
}
pub struct New {
path: String,
text: String,
}
pub struct Parsed {
path: String,
text: String,
document: parse::Parsed,
options: ParseOptions,
}
#[allow(
clippy::struct_field_names,
reason = "`resolved` is stage 2's result, named as in the later states"
)]
pub struct Resolved {
store: HostFiles,
resolved: resolve::Resolved,
edit_context: EditContext,
}
struct HostFiles {
files: Rc<dyn Files>,
packs: Rc<dyn Packs>,
}
impl HostFiles {
fn store(&self) -> Store<'_> {
Store {
files: &*self.files,
packs: &*self.packs,
}
}
}
struct EditContext {
path: String,
options: ParseOptions,
parsed: parse::Parsed,
ambient: Ambient,
parses: resolve::Parses,
}
pub struct Validated {
store: HostFiles,
resolved: resolve::Resolved,
checks: validate::Validated,
kept: usize,
edit_context: EditContext,
allowed_origins: Vec<String>,
}
struct RenderInputs {
context: RenderContext,
fetch: Rc<dyn Fetch>,
data: Rc<dyn Data>,
}
impl RenderInputs {
fn new(context: RenderContext, fetch: impl Fetch + 'static, data: impl Data + 'static) -> Self {
Self {
context,
fetch: Rc::new(fetch),
data: Rc::new(data),
}
}
}
pub struct Loaded {
validated: Validated,
render: RenderInputs,
data: load::Loaded,
}
pub struct Expanded {
validated: Validated,
render: RenderInputs,
data: load::Loaded,
options: expand::Options,
result: expand::Expanded,
replaced: expand::Replaced,
}
#[derive(Clone, Debug, PartialEq)]
pub struct Splice {
pub from: usize,
pub to: usize,
pub nodes: Vec<expanded::Node>,
pub lines: isize,
pub offset: isize,
}
impl<S> Processor<S> {
#[must_use]
pub fn diagnostics(&self) -> &[Diagnostic] {
&self.diagnostics
}
fn then<T>(self, step: impl FnOnce(S, &mut Vec<Diagnostic>) -> T) -> Processor<T> {
let Processor {
mut diagnostics,
state,
} = self;
let state = step(state, &mut diagnostics);
Processor { diagnostics, state }
}
}
impl Processor<New> {
#[must_use]
pub fn new(path: impl Into<String>, text: impl Into<String>) -> Self {
Self {
diagnostics: Vec::new(),
state: New {
path: path.into(),
text: text.into(),
},
}
}
#[must_use]
pub fn parse(self, options: &ParseOptions) -> Processor<Parsed> {
self.then(|New { path, text }, diagnostics| {
let document = parse::parse(&text, options);
Parsed::new(path, text, document, options.clone(), diagnostics)
})
}
}
impl Parsed {
fn new(
path: String,
text: String,
document: parse::Parsed,
options: ParseOptions,
diagnostics: &mut Vec<Diagnostic>,
) -> Self {
diagnostics.extend_from_slice(&document.diagnostics);
Self {
path,
text,
document,
options,
}
}
}
impl Processor<Parsed> {
#[must_use]
pub fn tree(&self) -> &tree::Root {
&self.state.document.root
}
#[must_use]
pub fn resolve(
self,
files: impl Files + 'static,
packs: impl Packs + 'static,
ambient: &Ambient,
) -> Processor<Resolved> {
let store = HostFiles {
files: Rc::new(files),
packs: Rc::new(packs),
};
self.resolve_in(store, ambient.clone(), resolve::Parses::default())
}
fn resolve_in(
self,
store: HostFiles,
ambient: Ambient,
mut parses: resolve::Parses,
) -> Processor<Resolved> {
self.then(|parsed, diagnostics| {
let Parsed {
path,
text,
document,
options,
} = parsed;
let resolved = resolve::resolve(
&path,
text,
document.for_resolving(),
store.store(),
&ambient,
&mut parses,
);
let edit_context = EditContext {
path,
options,
parsed: document,
ambient,
parses,
};
diagnostics.extend(resolved.diagnostics().cloned());
Resolved {
store,
resolved,
edit_context,
}
})
}
}
impl Processor<Resolved> {
#[must_use]
pub fn validate(self, allowed_origins: &[String]) -> Processor<Validated> {
self.then(
|Resolved {
store,
resolved,
edit_context,
},
diagnostics| {
let checks = validate::validate(&resolved, allowed_origins);
diagnostics.extend(checks.diagnostics().cloned());
Validated {
store,
resolved,
checks,
kept: diagnostics.len(),
edit_context,
allowed_origins: allowed_origins.to_vec(),
}
},
)
}
}
impl Processor<Validated> {
#[must_use]
pub fn sources(&self, context: &RenderContext) -> Needed {
load::sources(&self.state.resolved, &self.state.checks, context)
}
#[must_use]
pub fn load(
self,
context: RenderContext,
fetch: impl Fetch + 'static,
data: impl Data + 'static,
) -> Processor<Loaded> {
self.load_with(RenderInputs::new(context, fetch, data))
}
fn load_with(self, render: RenderInputs) -> Processor<Loaded> {
self.then(|validated, diagnostics| {
let host = load::Host {
store: validated.store.store(),
fetch: &*render.fetch,
data: &*render.data,
};
let data = load::load(
&validated.resolved,
&validated.checks,
&render.context,
&host,
);
diagnostics.extend_from_slice(&data.diagnostics);
Loaded {
validated,
render,
data,
}
})
}
}
impl Processor<Loaded> {
#[must_use]
pub fn inputs(&self) -> &Inputs {
&self.state.data.inputs
}
#[must_use]
pub fn expand(self, options: &expand::Options) -> Processor<Expanded> {
self.then(
|Loaded {
validated,
render,
data,
},
diagnostics| {
let result = expand::expand(
&validated.resolved,
&validated.checks,
&data,
&render.context,
options,
);
diagnostics.extend_from_slice(&result.diagnostics);
let replaced = expand::Replaced {
nodes: 0..0,
inserted: result.root.children.len(),
by: Move::default(),
};
Expanded {
validated,
render,
data,
options: options.clone(),
result,
replaced,
}
},
)
}
#[must_use]
pub fn reload(
self,
context: RenderContext,
fetch: impl Fetch + 'static,
data: impl Data + 'static,
) -> Processor<Loaded> {
self.then(|loaded, diagnostics| back(loaded.validated, diagnostics))
.load(context, fetch, data)
}
}
impl Processor<Expanded> {
#[must_use]
pub fn frontmatter(&self) -> crate::frontmatter::Frontmatter {
let resolved = &self.state.validated.resolved;
crate::frontmatter::of(resolved, resolved.main, &self.state.data)
}
#[must_use]
pub fn components(&self) -> crate::components::Components {
crate::components::of(&self.state.validated.resolved)
}
#[must_use]
pub fn hint_at(&self, offset: usize) -> Option<crate::hints::PositionalHint> {
crate::hints::at(&self.state.validated.resolved, &self.diagnostics, offset)
}
#[must_use]
pub fn tree(&self) -> &tree::Root {
let resolved = &self.state.validated.resolved;
&resolved.document(resolved.main).root
}
#[must_use]
pub fn expanded(&self) -> &expanded::Root {
&self.state.result.root
}
#[must_use]
pub fn splice(&self) -> Splice {
let replaced = &self.state.replaced;
let from = replaced.nodes.start;
Splice {
from,
to: replaced.nodes.end,
nodes: self.state.result.root.children[from..from + replaced.inserted].to_vec(),
lines: replaced.by.lines,
offset: replaced.by.offset,
}
}
#[must_use]
pub fn reload(
self,
context: RenderContext,
fetch: impl Fetch + 'static,
data: impl Data + 'static,
) -> Processor<Expanded> {
let options = self.state.options.clone();
let before = self.state.result.root.children.len();
let mut processor = self
.then(|expanded, diagnostics| back(expanded.validated, diagnostics))
.load(context, fetch, data)
.expand(&options);
processor.state.replaced.nodes = 0..before;
processor
}
#[must_use]
pub fn host_components(&self) -> Vec<HostComponent> {
let validated = &self.state.validated;
validated.resolved.host_components(validated.store.store())
}
#[must_use]
pub fn sources(&self) -> Needed {
let validated = &self.state.validated;
load::sources(
&validated.resolved,
&validated.checks,
&self.state.render.context,
)
}
#[must_use]
pub fn edit(self, edit: &TextEdit) -> Self {
let Expanded {
validated,
render,
data,
options,
result,
..
} = self.state;
let Validated {
store,
mut resolved,
mut checks,
edit_context: context,
allowed_origins,
..
} = validated;
let before = result.root.children.len();
let (text, mut lines) = resolved.take_text();
let (text, document, change) =
parse::reparse(context.parsed, &text, &mut lines, edit, &context.options);
let Change::Blocks(spliced) = change else {
drop(resolved);
let mut diagnostics = Vec::new();
let parsed = Parsed::new(
context.path,
text,
document,
context.options,
&mut diagnostics,
);
let mut processor = Processor {
diagnostics,
state: parsed,
}
.resolve_in(store, context.ambient, context.parses)
.validate(&allowed_origins)
.load_with(render)
.expand(&options);
processor.state.replaced.nodes = 0..before;
return processor;
};
let report = context.ambient.report;
resolved.splice((text, lines), Rc::clone(&document.root), &spliced, report);
let reload = checks.splice(&resolved, &spliced, &allowed_origins);
let mut diagnostics = document.diagnostics.clone();
diagnostics.extend(resolved.diagnostics().cloned());
diagnostics.extend(checks.diagnostics().cloned());
let validated = Validated {
store,
resolved,
checks,
kept: diagnostics.len(),
edit_context: EditContext {
parsed: document,
..context
},
allowed_origins,
};
if reload {
let mut processor = Processor {
diagnostics,
state: validated,
}
.load_with(render)
.expand(&options);
processor.state.replaced.nodes = 0..before;
return processor;
}
let result = expand::splice(
result,
&validated.resolved,
&validated.checks,
&data,
&render.context,
&options,
&spliced,
);
diagnostics.extend_from_slice(&data.diagnostics);
diagnostics.extend_from_slice(&result.diagnostics);
let replaced = result.replaced.clone().unwrap_or(expand::Replaced {
nodes: 0..before,
inserted: result.root.children.len(),
by: Move::default(),
});
Processor {
diagnostics,
state: Expanded {
validated,
render,
data,
options,
result,
replaced,
},
}
}
}
fn back(validated: Validated, diagnostics: &mut Vec<Diagnostic>) -> Validated {
diagnostics.truncate(validated.kept);
validated
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::{Map, json};
use std::collections::HashMap;
fn context(inputs: serde_json::Value) -> RenderContext {
let inputs: Map<String, serde_json::Value> =
serde_json::from_value(inputs).expect("an object");
RenderContext {
now: 0,
tz: "UTC".into(),
time: Rc::new(()),
inputs,
}
}
#[test]
fn each_state_gives_what_its_stage_made_and_reloads_keep_stages_1_to_3() {
let text = "---\nmd:\n data:\n - from: ./missing.json\n as: m\n inputs:\n n: number = 1\n---\n\n{{md.input.n}} :x[y]\n";
let files: HashMap<String, String> = HashMap::new();
let parsed = Processor::new("a.comp.md", text).parse(&ParseOptions::default());
assert_eq!(parsed.tree().children.len(), 2);
let resolved = parsed.resolve(files, (), &Ambient::default());
assert_eq!(
resolved.state.resolved.documents.len(),
2,
"with @compmd/core"
);
let validated = resolved.validate(&[]);
assert_eq!(validated.diagnostics().len(), 1, "F002 for `x`");
let one = context(json!({}));
let loaded = validated.load(one, (), ());
let values = |loaded: &Processor<Loaded>| -> Vec<serde_json::Value> {
loaded
.inputs()
.iter()
.map(|input| input.value.clone())
.collect()
};
assert_eq!(values(&loaded), [json!(1)]);
assert_eq!(loaded.diagnostics().len(), 2, "and F026 for the data");
let two = context(json!({ "n": 2 }));
let loaded = loaded.reload(two, (), ());
assert_eq!(values(&loaded), [json!(2)]);
assert_eq!(
loaded.diagnostics().len(),
2,
"a reload drops stage 4's own"
);
let expanded = loaded.expand(&expand::Options::default());
let written = |processor: &Processor<Expanded>| {
serde_json::to_value(processor.expanded()).expect("a tree")["children"][0]["children"]
[0]["value"]
.clone()
};
assert_eq!(written(&expanded), json!("2 y"));
assert_eq!(expanded.sources(), Needed::default());
let three = context(json!({ "n": 3 }));
let expanded = expanded.reload(three, (), ());
assert_eq!(written(&expanded), json!("3 y"));
let splice = expanded.splice();
assert_eq!((splice.from, splice.to, splice.nodes.len()), (0, 1, 1));
assert_eq!(expanded.diagnostics().len(), 2);
assert_eq!(expanded.tree().children.len(), 2);
}
#[test]
fn an_edit_after_text_of_two_blocks_was_joined_expands_the_whole_document() {
let files: HashMap<String, String> = HashMap::new();
let context = context(json!({}));
let mut processor = Processor::new("a.comp.md", "One.\n\nTwo.\n")
.parse(&ParseOptions::default())
.resolve(files, (), &Ambient::default())
.validate(&[])
.load(context, (), ())
.expand(&expand::Options::default());
processor.state.result.blocks = None;
let processor = processor.edit(&TextEdit {
start: 9,
end: 9,
text: "!".into(),
});
let splice = processor.splice();
assert_eq!((splice.from, splice.to, splice.nodes.len()), (0, 2, 2));
let json = serde_json::to_value(processor.expanded()).expect("a tree");
assert_eq!(json["children"][1]["children"][0]["value"], "Two!.");
}
}