mod error;
mod render;
mod scan;
use std::collections::HashMap;
use crate::path::lookup;
use crate::{Map, PathError, RefPath, Seg, Value};
pub use error::{Cycle, InterpError, Problem};
pub use scan::Syntax;
use render::stringify;
use scan::{Piece, Spelled, scan};
const ENV: &str = "env:";
#[derive(Debug, Clone, PartialEq)]
pub struct EnvValue {
pub raw: String,
pub typed: Value,
}
pub trait Env {
fn lookup(&self, name: &str) -> Option<EnvValue>;
}
pub fn interpolate(doc: Value, env: &dyn Env) -> Result<Value, InterpError> {
let mut resolver = Resolver {
doc: &doc,
env,
memo: HashMap::new(),
visiting: Vec::new(),
problems: Vec::new(),
};
let resolved = resolver.resolve(&[]).map_err(InterpError::Cycle)?;
if resolver.problems.is_empty() {
Ok(resolved)
} else {
Err(InterpError::Problems(resolver.problems))
}
}
struct Resolver<'a> {
doc: &'a Value,
env: &'a dyn Env,
memo: HashMap<Vec<Seg>, Value>,
visiting: Vec<Vec<Seg>>,
problems: Vec<Problem>,
}
impl<'a> Resolver<'a> {
fn resolve(&mut self, path: &[Seg]) -> Result<Value, Cycle> {
if let Some(done) = self.memo.get(path) {
return Ok(done.clone());
}
if let Some(start) = self
.visiting
.iter()
.position(|seen| seen.as_slice() == path)
{
let mut chain = self.visiting[start..].to_vec();
chain.push(path.to_vec());
return Err(Cycle::new(chain));
}
let raw = lookup(self.doc, path).expect("resolve is only called on paths that exist");
self.visiting.push(path.to_vec());
let resolved = self.resolve_value(raw, path)?;
self.visiting.pop();
if !path.is_empty() {
self.memo.insert(path.to_vec(), resolved.clone());
}
Ok(resolved)
}
fn resolve_value(&mut self, raw: &'a Value, path: &[Seg]) -> Result<Value, Cycle> {
match raw {
Value::String(text) => self.resolve_string(text, path),
Value::Array(items) => {
let mut out = Vec::with_capacity(items.len());
for index in 0..items.len() {
out.push(self.resolve(&child(path, Seg::Index(index)))?);
}
Ok(Value::Array(out))
}
Value::Object(map) => {
let mut out = Map::with_capacity(map.len());
for key in map.keys() {
let value = self.resolve(&child(path, Seg::Key(key.clone())))?;
out.insert(key.clone(), value);
}
Ok(Value::Object(out))
}
scalar => Ok(scalar.clone()),
}
}
fn resolve_string(&mut self, text: &str, path: &[Seg]) -> Result<Value, Cycle> {
let pieces = scan(text);
if pieces.is_empty() {
return Ok(Value::String(text.to_string()));
}
if let [Piece::Ref(body)] = pieces.as_slice() {
return self.substitute(body, path);
}
let mut out = String::new();
for piece in pieces {
match piece {
Piece::Literal(literal) => out.push_str(literal),
Piece::Ref(body) => out.push_str(&self.splice(body, path)?),
Piece::Malformed { spelling, error } => {
self.problems.push(Problem::Syntax {
path: path.to_vec(),
error,
});
out.push_str(spelling);
}
}
}
Ok(Value::String(out))
}
fn substitute(&mut self, body: &str, path: &[Seg]) -> Result<Value, Cycle> {
if let Some(name) = body.strip_prefix(ENV) {
return Ok(match self.env_value(name, body, path) {
Some(found) => found.typed,
None => Value::String(Spelled(body).to_string()),
});
}
match self.target(body, path) {
Some(target) => self.resolve(&target),
None => Ok(Value::String(Spelled(body).to_string())),
}
}
fn splice(&mut self, body: &str, path: &[Seg]) -> Result<String, Cycle> {
if let Some(name) = body.strip_prefix(ENV) {
return Ok(match self.env_value(name, body, path) {
Some(found) => found.raw,
None => Spelled(body).to_string(),
});
}
let Some(target) = self.target(body, path) else {
return Ok(Spelled(body).to_string());
};
let value = self.resolve(&target)?;
Ok(match stringify(&value) {
Some(text) => text,
None => {
self.problems.push(Problem::NotStringifiable {
path: path.to_vec(),
reference: body.to_string(),
kind: value.kind(),
});
Spelled(body).to_string()
}
})
}
fn env_value(&mut self, name: &str, body: &str, path: &[Seg]) -> Option<EnvValue> {
if name.is_empty() {
self.problems.push(Problem::Syntax {
path: path.to_vec(),
error: Syntax::EmptyEnvName,
});
return None;
}
let found = self.env.lookup(name);
if found.is_none() {
self.problems.push(Problem::Unresolved {
path: path.to_vec(),
reference: body.to_string(),
});
}
found
}
fn target(&mut self, body: &str, path: &[Seg]) -> Option<Vec<Seg>> {
let target: Vec<Seg> = match body.parse::<RefPath>() {
Ok(parsed) => parsed.into_segs(),
Err(PathError::BadIndex { .. }) => {
self.problems.push(Problem::Syntax {
path: path.to_vec(),
error: Syntax::BadIndex {
body: body.to_string(),
},
});
return None;
}
Err(PathError::EmptySegment { .. } | PathError::EmptyPath) => {
self.problems.push(Problem::Syntax {
path: path.to_vec(),
error: Syntax::EmptySegment {
body: body.to_string(),
},
});
return None;
}
Err(PathError::MissingEquals | PathError::IndexInKeyPath { .. }) => {
unreachable!("parsing a reference body never reports these")
}
};
if lookup(self.doc, &target).is_none() {
self.problems.push(Problem::Unresolved {
path: path.to_vec(),
reference: body.to_string(),
});
return None;
}
Some(target)
}
}
fn child(path: &[Seg], seg: Seg) -> Vec<Seg> {
let mut out = Vec::with_capacity(path.len() + 1);
out.extend_from_slice(path);
out.push(seg);
out
}
#[cfg(test)]
mod tests;