mod builder;
mod index;
mod item;
mod stack;
pub use builder::Builder;
pub use index::Index;
use crate::{Document, LoadFormat, LoadMode, LoadOptions};
use item::{Item, Resolution};
use stack::Stack;
use crate::err::*;
use crate::expression::*;
use crate::value::*;
use std::ops::Range;
use std::sync::Arc;
#[derive(Default)]
pub struct Cco {
items: Vec<Item>,
globals: indexmap::IndexMap<String, Index>,
}
impl Cco {
pub fn new() -> Self {
Self::default()
}
pub fn builder(&mut self, document: Arc<Document>) -> Builder {
Builder::new(self, document)
}
pub fn locate(&self, index: Index) -> Option<(Arc<Document>, Range<usize>)> {
if let Some(original) = self.get_item(index).cloned_from {
return self.locate(original);
}
let item = &self.items[index.get()];
let (Some(span), Some(document)) = (item.span(), item.document.clone()) else {
return None;
};
Some((document, span))
}
pub fn load_document(
&mut self,
document: Document,
options: LoadOptions,
) -> anyhow::Result<()> {
let document = Arc::new(document);
let mut builder = self.builder(document.clone());
match options.mode {
LoadMode::Root => {
let mut globals = vec![];
match options.format {
LoadFormat::Hcl => {
let hcl_body = hcl::edit::parser::parse_body(&document.source)?;
for item in hcl_body.into_attributes() {
let key = item.key.value().to_string();
let index = builder.push_hcl_expression(item.value);
globals.push((key, index));
}
}
};
for (key, index) in globals {
if let Some(duplicate_global_key) = self.insert_global(key, index) {
anyhow::bail!("Global key already exists: {duplicate_global_key}");
}
}
}
LoadMode::SubKey(key) => {
let root = match options.format {
LoadFormat::Hcl => {
let hcl_body = hcl::edit::parser::parse_body(&document.source)?;
builder.push_hcl_body(hcl_body)
}
};
self.insert_global(key, root);
}
}
Ok(())
}
pub fn evaluate_global_context(mut self) -> Result<Value, Error> {
let mut map = vec![];
for (k, &v) in self.globals.iter() {
map.push((MapKey::Literal(k.to_string()), v));
}
let index = self.push(Item::new(Expression::map(map)));
self.evaluate_item(index)
}
pub fn evaluate_hcl_expression(mut self, hcl_expression: String) -> Result<Value, Error> {
let hcl_parsed = hcl::edit::parser::parse_expr(&hcl_expression).map_err(|e| Error {
kind: ErrorKind::Other(Arc::new(e.into())),
stack: vec![],
evaluation: Box::default(),
files: Default::default(),
})?;
let document = Arc::new(Document::new(hcl_expression.clone()));
let mut builder = self.builder(document.clone());
let target = builder.push_hcl_expression(hcl_parsed);
self.evaluate_item(target)
}
fn insert_global(&mut self, key: impl Into<String>, index: Index) -> Option<String> {
let key = key.into();
if self.globals.contains_key(&key) {
return Some(key);
};
self.globals.insert(key, index);
None
}
fn evaluate_item(&mut self, target: Index) -> Result<Value, Error> {
let mut stack = Stack::new(target);
while let Some(target) = stack.get_current() {
if let Err(e) = self.eval_target(&mut stack, target) {
if !stack.try_skip_current() {
return Err(Error {
kind: e,
stack: stack.stack(),
evaluation: Box::new(std::mem::take(self)),
files: Default::default(),
});
}
}
}
match self.get_value(target) {
Ok(value) => Ok(value),
Err(kind) => Err(Error {
kind,
stack: vec![],
evaluation: Box::new(std::mem::take(self)),
files: Default::default(),
}),
}
}
fn eval_target(&mut self, stack: &mut Stack, target: Index) -> Result<(), ErrorKind> {
if self.is_value(target) {
stack.remove(target);
return Ok(());
}
if std::collections::HashSet::<Index>::from_iter(stack.stack()).len() != stack.len() {
tracing::debug!(%target, ?stack, "circular dependency detected");
return Err(ErrorKind::CircularDependency { index: target });
}
let expr = self.get_expr(target);
match expr.as_ref().resolve(target, self) {
Ok(ValueOrReference::Value(value)) => {
self.set_value(target, value);
stack.remove(target);
}
Ok(ValueOrReference::Reference(reference)) => {
self.set_reference(target, reference);
stack.remove(target);
if stack.len() == 0 {
stack.request([reference]);
}
}
Err(ErrorKind::DependenciesNotSatisfied { indices }) => stack.request(indices),
Err(other) => return Err(other),
}
Ok(())
}
fn ensure_resolved(&self, indices: Vec<Index>) -> Result<(), ErrorKind> {
let mut to_request = std::collections::HashSet::new();
for i in indices {
let index = self.dereference(i)?;
if self.is_value(index) {
continue;
}
to_request.insert(index);
}
if to_request.is_empty() {
tracing::trace!("noop-request");
Ok(())
} else {
tracing::trace!(?to_request, "requesting");
Err(ErrorKind::DependenciesNotSatisfied {
indices: Vec::from_iter(to_request),
})
}
}
fn traverse_expression(
&self,
expression: Index,
name: &Indexer,
) -> Result<Option<ValueOrReference>, ErrorKind> {
let expression = self.dereference(expression)?;
self.get_expr(expression).traverse(self, expression, name)
}
fn get_value(&self, target: Index) -> Result<Value, ErrorKind> {
let index = self.dereference(target)?;
let Some(value) = self.get_item(index).resolution.as_value().cloned() else {
return Err(ErrorKind::DependenciesNotSatisfied {
indices: vec![index],
});
};
Ok(value)
}
fn len(&self) -> usize {
self.items.len()
}
fn get_item(&self, index: Index) -> &Item {
&self.items[index.get()]
}
fn get_item_mut(&mut self, index: Index) -> &mut Item {
&mut self.items[index.get()]
}
fn push(&mut self, item: impl Into<Item>) -> Index {
self.items.push(item.into());
Index::new(self.len() - 1)
}
fn duplicate(&mut self, index: Index) -> Index {
let mut expr = Expression::clone(&self.get_expr(index));
match &mut expr {
Expression::List(ListExpr { items }) => {
for idx in items.iter_mut() {
*idx = self.duplicate(*idx);
}
}
Expression::Map(MapExpr { items }) => {
for (key, value) in items.iter_mut() {
match key {
MapKey::Expression(key_idx) => *key_idx = self.duplicate(*key_idx),
MapKey::Literal(_) => {}
}
*value = self.duplicate(*value);
}
}
Expression::Literal(_) | Expression::Variable(_) => {}
Expression::BinaryOp(BinaryOpExpr { left, right, .. }) => {
*left = self.duplicate(*left);
*right = self.duplicate(*right);
}
Expression::UnaryOp(UnaryOpExpr { expr, .. }) => {
*expr = self.duplicate(*expr);
}
Expression::ConcatString(ConcatStringExpr { parts }) => {
for idx in parts.iter_mut() {
*idx = self.duplicate(*idx);
}
}
Expression::Conditional(ConditionalExpr {
condition,
true_expr,
false_expr,
}) => {
*condition = self.duplicate(*condition);
*true_expr = self.duplicate(*true_expr);
*false_expr = self.duplicate(*false_expr);
}
Expression::FuncCall(FuncCallExpr { args, .. }) => {
for idx in args.iter_mut() {
*idx = self.duplicate(*idx);
}
}
Expression::Traversal(TraversalExpr { object, key }) => {
*object = self.duplicate(*object);
match key {
TraversalKey::Expression(expr_index) => {
*expr_index = self.duplicate(*expr_index);
}
TraversalKey::String(_) | TraversalKey::Number(_) => {
}
}
}
Expression::Mapper(MapperExpr {
source,
key_name: key_var_name,
value_name: value_var_name,
output,
}) => {
*source = self.duplicate(*source);
if let Some(key_var_name) = key_var_name {
*key_var_name = self.duplicate(*key_var_name);
}
*value_var_name = self.duplicate(*value_var_name);
match output {
Output::List { expr } => *expr = self.duplicate(*expr),
Output::Map {
key_expr,
value_expr,
} => {
*key_expr = self.duplicate(*key_expr);
*value_expr = self.duplicate(*value_expr);
}
Output::StringConcat { expr } => {
*expr = self.duplicate(*expr);
}
}
}
Expression::TrimString(TrimStringExpr { expr }) => {
*expr = self.duplicate(*expr);
}
Expression::TypeGuard(TypeGuardExpr { expr, .. }) => {
*expr = self.duplicate(*expr);
}
}
let item = Item {
expr: Arc::new(expr),
resolution: Resolution::default(),
context: self.get_context(index),
span: self.get_span(index),
cloned_from: Some(index),
document: self.get_item(index).document.clone(),
};
self.push(item)
}
fn get_global(&self, name: String) -> Option<Index> {
self.globals.get(&name).copied()
}
fn is_value(&self, index: Index) -> bool {
matches!(self.get_item(index).resolution, Resolution::Value(_))
}
pub fn dereference(&self, index: Index) -> Result<Index, ErrorKind> {
self.dereference_internal(index, Vec::new())
}
fn dereference_internal(
&self,
index: Index,
mut visited: Vec<Index>,
) -> Result<Index, ErrorKind> {
if visited.contains(&index) {
return Err(ErrorKind::CircularDependency { index });
}
visited.push(index);
match self.get_referenced(index) {
Some(reference) => self.dereference_internal(reference, visited),
None => Ok(index),
}
}
fn set_value(&mut self, index: Index, value: Value) {
self.get_item_mut(index).resolution = Resolution::Value(value);
}
fn set_reference(&mut self, index: Index, reference: Index) {
self.get_item_mut(index).resolution = Resolution::Reference(reference);
}
fn get_context(&self, index: Index) -> Vec<Index> {
self.get_item(index).context.clone()
}
fn add_context(&mut self, index: Index, context: Index) {
let current_context = &mut self.get_item_mut(index).context;
debug_assert!(
!current_context.contains(&context),
"context already exists"
);
current_context.push(context);
match *self.get_expr(index) {
Expression::BinaryOp(BinaryOpExpr { left, right, .. }) => {
self.add_context(left, context);
self.add_context(right, context);
}
Expression::List(ListExpr { ref items }) => {
for item in items.iter().copied() {
self.add_context(item, context);
}
}
Expression::Map(MapExpr { ref items }) => {
for (key, value) in items.iter() {
match key {
MapKey::Expression(key_idx) => self.add_context(*key_idx, context),
MapKey::Literal(_) => {}
}
self.add_context(*value, context);
}
}
Expression::UnaryOp(UnaryOpExpr {
expr: inner_expr, ..
}) => {
self.add_context(inner_expr, context);
}
Expression::ConcatString(ConcatStringExpr { ref parts }) => {
for part in parts.iter().copied() {
self.add_context(part, context);
}
}
Expression::Conditional(ConditionalExpr {
condition,
true_expr,
false_expr,
}) => {
self.add_context(condition, context);
self.add_context(true_expr, context);
self.add_context(false_expr, context);
}
Expression::FuncCall(FuncCallExpr { ref args, .. }) => {
for arg in args.iter().copied() {
self.add_context(arg, context);
}
}
Expression::Traversal(TraversalExpr { object, ref key }) => {
self.add_context(object, context);
match key {
TraversalKey::Expression(expr_index) => {
self.add_context(*expr_index, context);
}
TraversalKey::String(_) | TraversalKey::Number(_) => {
}
}
}
Expression::Mapper(MapperExpr {
source,
key_name,
value_name,
output,
}) => {
self.add_context(source, context);
if let Some(key_name) = key_name {
self.add_context(key_name, context);
}
self.add_context(value_name, context);
match output {
Output::List { expr } => {
self.add_context(expr, context);
}
Output::Map {
key_expr,
value_expr,
} => {
self.add_context(key_expr, context);
self.add_context(value_expr, context);
}
Output::StringConcat { expr } => {
self.add_context(expr, context);
}
}
}
Expression::Literal(_) | Expression::Variable(_) => {}
Expression::TrimString(TrimStringExpr { expr }) => {
self.add_context(expr, context);
}
Expression::TypeGuard(TypeGuardExpr { expr, .. }) => {
self.add_context(expr, context);
}
}
}
fn get_expr(&self, index: Index) -> Arc<Expression> {
self.get_item(index).expr()
}
fn get_span(&self, index: Index) -> Option<Range<usize>> {
self.get_item(index).span.clone()
}
fn get_referenced(&self, index: Index) -> Option<Index> {
self.get_item(index).resolution.as_reference()
}
}
impl ExprEvaluator for Cco {
fn ensure_resolved(&self, indices: Vec<Index>) -> Result<(), ErrorKind> {
Cco::ensure_resolved(self, indices)
}
fn get_value(&self, target: Index) -> Result<Value, ErrorKind> {
Cco::get_value(self, target)
}
fn traverse(
&self,
expression: Index,
name: &Indexer,
) -> Result<Option<ValueOrReference>, ErrorKind> {
Cco::traverse_expression(self, expression, name)
}
fn resolve_variable(&self, index: Index, name: String) -> Result<ValueOrReference, ErrorKind> {
use crate::err::ErrorKind;
let indexer = Indexer::String(name.clone());
for context in self.get_context(index).into_iter().rev() {
if let Some(found) = self.traverse_expression(context, &indexer)? {
return Ok(found);
}
}
if let Some(global) = self.get_global(name) {
return Ok(ValueOrReference::Reference(global));
}
Err(ErrorKind::UnknownVariable {
index,
name: indexer.to_string(),
contexts: self.get_context(index).into_iter().rev().collect(),
})?
}
fn create(&mut self, expression: Expression) -> Index {
self.push(Item::new(expression))
}
fn clone_with_context(&mut self, index: Index, context: Index) -> Index {
let new_id = self.duplicate(index);
self.add_context(new_id, context);
new_id
}
}
impl std::fmt::Debug for Cco {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if f.alternate() {
for (idx, item) in self.items.iter().enumerate() {
writeln!(f, " {idx:04}: {item:?}")?;
}
Ok(())
} else {
f.debug_list().entries(self.items.iter()).finish()
}
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum Indexer {
String(String),
Number(usize),
}
impl TryFrom<Value> for Indexer {
type Error = ();
fn try_from(value: Value) -> Result<Self, ()> {
match value {
Value::String(s) => Ok(Indexer::String(s)),
Value::Integer(s) => {
if s.is_negative() {
return Err(())?;
}
Ok(Indexer::Number(s as usize))
}
_ => Err(())?,
}
}
}
impl std::fmt::Display for Indexer {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Indexer::String(s) => f.write_str(s),
Indexer::Number(n) => f.write_fmt(format_args!("{n}")),
}
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum TraversalKey {
String(String),
Number(usize),
Expression(Index),
}
#[derive(Debug)]
pub enum ValueOrReference {
Value(Value),
Reference(Index),
}
impl From<Value> for ValueOrReference {
fn from(value: Value) -> Self {
ValueOrReference::Value(value)
}
}
impl From<Index> for ValueOrReference {
fn from(index: Index) -> Self {
ValueOrReference::Reference(index)
}
}