use crate::eval::*;
use crate::expression::*;
use crate::*;
use std::ops::Range;
use std::sync::Arc;
pub struct Builder<'a> {
evaluation: &'a mut Cco,
document: Arc<Document>,
}
impl<'a> Builder<'a> {
pub fn new(evaluation: &'a mut Cco, document: Arc<Document>) -> Self {
Self {
evaluation,
document,
}
}
pub fn push_expr(
&mut self,
expression: impl Into<Expression>,
span: Option<Range<usize>>,
) -> Index {
self.evaluation.push(Item {
expr: Arc::new(expression.into()),
resolution: Resolution::default(),
cloned_from: None,
context: vec![],
span,
document: Some(self.document.clone()),
})
}
pub fn push_hcl_body(&mut self, body: hcl::edit::structure::Body) -> Index {
use hcl::edit::Span;
let span = body.span();
let root: Vec<_> = body
.into_attributes()
.map(|attribute| {
(
MapKey::Literal(attribute.key.value().to_string()),
self.push_hcl_expression(attribute.value.clone()),
)
})
.collect();
self.push_expr(Expression::map(MapExpr::from(root)), span)
}
pub fn push_hcl_expression(&mut self, value: hcl::edit::expr::Expression) -> Index {
use hcl::edit::Span;
use hcl::edit::expr::Expression::*;
use hcl::edit::expr::ObjectKey;
match value {
Bool(v) => self.push_expr(*v.value(), v.span()),
Number(n) => {
if let Some(v) = n.value().as_i64() {
self.push_expr(v, n.span())
} else {
self.push_expr(n.as_f64().unwrap(), n.span())
}
}
String(v) => {
let span = v.span();
self.push_expr(v.into_value(), span)
}
Variable(variable) => {
let span = variable.span();
self.push_expr(
Expression::variable(VariableExpr {
name: variable.into_value().to_string(),
}),
span,
)
}
Array(e) => {
let span = e.span();
let mut indices = vec![];
for element in e {
indices.push(self.push_hcl_expression(element));
}
self.push_expr(Expression::list(ListExpr { items: indices }), span)
}
Object(o) => {
let span = o.span();
let mut indices = vec![];
for (key, value) in o {
let key = match key {
ObjectKey::Ident(ident) => MapKey::Literal(ident.value().to_string()),
ObjectKey::Expression(expr) => {
MapKey::Expression(self.push_hcl_expression(expr))
}
};
let value = self.push_hcl_expression(value.into_expr());
indices.push((key, value));
}
self.push_expr(Expression::map(MapExpr::from(indices)), span)
}
BinaryOp(x) => {
let span = x.span();
let left = self.push_hcl_expression(x.lhs_expr);
let right = self.push_hcl_expression(x.rhs_expr);
let op = match x.operator.value() {
hcl::edit::expr::BinaryOperator::Plus => BinaryOperator::Add,
hcl::edit::expr::BinaryOperator::Minus => BinaryOperator::Sub,
hcl::edit::expr::BinaryOperator::Eq => BinaryOperator::Eq,
hcl::edit::expr::BinaryOperator::NotEq => BinaryOperator::NotEq,
hcl::edit::expr::BinaryOperator::LessEq => BinaryOperator::LessEq,
hcl::edit::expr::BinaryOperator::GreaterEq => BinaryOperator::GreaterEq,
hcl::edit::expr::BinaryOperator::Less => BinaryOperator::Less,
hcl::edit::expr::BinaryOperator::Greater => BinaryOperator::Greater,
hcl::edit::expr::BinaryOperator::Mul => BinaryOperator::Mul,
hcl::edit::expr::BinaryOperator::Div => BinaryOperator::Div,
hcl::edit::expr::BinaryOperator::Mod => BinaryOperator::Mod,
hcl::edit::expr::BinaryOperator::And => BinaryOperator::And,
hcl::edit::expr::BinaryOperator::Or => BinaryOperator::Or,
};
self.push_expr(
Expression::binary_op(BinaryOpExpr { left, op, right }),
span,
)
}
StringTemplate(template) => {
let span = template.span();
self.push_hcl_template(template, span)
}
HeredocTemplate(template) => {
let span = template.span();
self.push_hcl_template(template.template, span)
}
Parenthesis(expr) => {
self.push_hcl_expression(expr.into_inner())
}
Conditional(cond) => {
let span = cond.span();
let condition = self.push_hcl_expression(cond.cond_expr);
let true_expr = self.push_hcl_expression(cond.true_expr);
let false_expr = self.push_hcl_expression(cond.false_expr);
self.push_expr(
Expression::conditional(ConditionalExpr {
condition,
true_expr,
false_expr,
}),
span,
)
}
Traversal(traversal) => {
let span = traversal.span();
let expr = self.push_hcl_expression(traversal.expr);
self.push_hcl_traversal(expr, &traversal.operators, span)
}
UnaryOp(op) => {
let span = op.span();
let expr = self.push_hcl_expression(op.expr);
let unary_op = match op.operator.value() {
hcl::edit::expr::UnaryOperator::Neg => UnaryOperator::Neg,
hcl::edit::expr::UnaryOperator::Not => UnaryOperator::Not,
};
self.push_expr(
Expression::unary_op(UnaryOpExpr { op: unary_op, expr }),
span,
)
}
ForExpr(for_expr) => {
let for_expr_span = for_expr.span();
let key_var_name = for_expr
.intro
.key_var
.map(|kv| self.push_expr(kv.value().to_string(), kv.span()));
let key_expr = for_expr
.key_expr
.clone()
.map(|expr| self.push_hcl_expression(expr));
let value_var_name = self.push_expr(
for_expr.intro.value_var.value().to_string(),
for_expr.intro.value_var.span(),
);
let source = self.push_hcl_expression(for_expr.intro.collection_expr);
let value_expr = self.push_hcl_expression(for_expr.value_expr);
let output = key_expr
.map(|key_expr| Output::Map {
key_expr,
value_expr,
})
.unwrap_or_else(|| Output::List { expr: value_expr });
self.push_expr(
Expression::mapper(MapperExpr {
source,
key_name: key_var_name,
value_name: value_var_name,
output,
}),
for_expr_span,
)
}
FuncCall(function) => {
let name = function.name.name.value().to_string();
let namespace = function
.name
.namespace
.iter()
.map(|ns| ns.value().to_string())
.collect();
let args = function
.args
.iter()
.map(|arg| self.push_hcl_expression(arg.clone()))
.collect();
self.push_expr(
Expression::func_call(FuncCallExpr {
name,
namespace,
args,
}),
function.span(),
)
}
Null(_) => panic!("null is not supported"),
}
}
fn push_hcl_template(
&mut self,
elements: impl IntoIterator<Item = hcl::edit::template::Element>,
template_span: Option<Range<usize>>,
) -> Index {
use hcl::edit::Span;
use hcl::edit::template::Element;
let mut parts = vec![];
for part in elements.into_iter() {
let part_index = match part {
Element::Literal(value) => {
let span = value.span();
self.push_expr(value.into_value(), span)
}
Element::Interpolation(interpolation) => {
assert_eq!(
interpolation.strip,
hcl::edit::template::Strip::None,
"strip is not implemented"
);
self.push_hcl_expression(interpolation.expr)
}
Element::Directive(directive) => match &*directive {
hcl::edit::template::Directive::If(if_directive) => {
assert_eq!(
if_directive.if_expr.strip,
hcl::edit::template::Strip::None,
"strip is not implemented"
);
let condition =
self.push_hcl_expression(if_directive.if_expr.cond_expr.clone());
let true_expr = self.push_hcl_template(
if_directive.if_expr.template.clone(),
if_directive.if_expr.template.span(),
);
let false_expr = if let Some(else_expr) = &if_directive.else_expr {
assert_eq!(
else_expr.strip,
hcl::edit::template::Strip::None,
"strip is not implemented"
);
self.push_hcl_template(
else_expr.template.clone(),
else_expr.template.span(),
)
} else {
self.push_expr(Value::String("".to_string()), None)
};
self.push_expr(
Expression::conditional(ConditionalExpr {
condition,
true_expr,
false_expr,
}),
if_directive.span(),
)
}
hcl::edit::template::Directive::For(for_directive) => {
assert_eq!(
for_directive.for_expr.strip,
hcl::edit::template::Strip::None,
"strip is not implemented"
);
assert_eq!(
for_directive.endfor_expr.strip,
hcl::edit::template::Strip::None,
"strip is not implemented"
);
let source = self
.push_hcl_expression(for_directive.for_expr.collection_expr.clone());
let key_name = for_directive.for_expr.key_var.as_ref().map(|key_name| {
self.push_expr(key_name.value().to_string(), key_name.span())
});
let value_name = &for_directive.for_expr.value_var;
let value_name =
self.push_expr(value_name.value().to_string(), value_name.span());
let template = &for_directive.for_expr.template;
let template = self.push_hcl_template(template.clone(), template.span());
let output = Output::StringConcat { expr: template };
self.push_expr(
Expression::mapper(MapperExpr {
source,
key_name,
value_name,
output,
}),
for_directive.span(),
)
}
},
};
parts.push(part_index);
}
self.push_expr(
Expression::concat_string(ConcatStringExpr { parts }),
template_span,
)
}
fn push_hcl_traversal(
&mut self,
expr: Index,
traversal_operators: &[hcl::edit::Decorated<hcl::edit::expr::TraversalOperator>],
span: Option<Range<usize>>,
) -> Index {
use hcl::edit::Span;
let splat_position = traversal_operators.iter().position(|op| {
matches!(
op.value(),
TraversalOperator::FullSplat(_) | TraversalOperator::AttrSplat(_)
)
});
if let Some(splat_position) = splat_position {
let (head, splat_and_tail) = traversal_operators.split_at(splat_position);
let tail = &splat_and_tail[1..];
let source = self.push_hcl_traversal(expr, head, None);
let name = format!("$$SPLAT${source}$$");
let value_name = self.push_expr(name.clone(), None);
let variable = self.push_expr(Expression::Variable(name.into()), None);
let tail = self.push_hcl_traversal(variable, tail, None);
let mapper = Expression::mapper(MapperExpr {
source,
key_name: None,
value_name,
output: Output::List { expr: tail },
});
return self.push_expr(mapper, span);
}
use hcl::edit::expr::TraversalOperator;
match traversal_operators.last().map(|dec| dec.value()) {
Some(TraversalOperator::GetAttr(attr)) => {
let expr = self.push_hcl_traversal(
expr,
&traversal_operators[..traversal_operators.len() - 1],
traversal_operators[0].span(),
);
self.push_expr(
Expression::traversal(TraversalExpr {
object: expr,
key: TraversalKey::String(attr.value().to_string()),
}),
span,
)
}
Some(TraversalOperator::LegacyIndex(index)) => {
let expr = self.push_hcl_traversal(
expr,
&traversal_operators[..traversal_operators.len() - 1],
traversal_operators[0].span(),
);
self.push_expr(
Expression::traversal(TraversalExpr {
object: expr,
key: TraversalKey::Number(*index.value() as usize),
}),
span,
)
}
Some(TraversalOperator::Index(index)) => {
let expr = self.push_hcl_traversal(
expr,
&traversal_operators[..traversal_operators.len() - 1],
traversal_operators[0].span(),
);
let key = self.push_hcl_expression(index.clone());
self.push_expr(
Expression::Traversal(TraversalExpr {
object: expr,
key: TraversalKey::Expression(key),
}),
span,
)
}
Some(TraversalOperator::AttrSplat(_)) | Some(TraversalOperator::FullSplat(_)) => {
unreachable!("FullSplat/AttrSplat survived?")
}
None => expr,
}
}
pub fn push_json(&mut self, value: serde_json::Value) -> Index {
use serde::Deserialize;
let value = Value::deserialize(&value).expect("Failed to import json");
self.push_expr(Expression::literal(LiteralExpr { value }), None)
}
pub fn push_yaml(&mut self, value: serde_yaml::Value) -> Index {
use serde::Deserialize;
let value = Value::deserialize(&value).expect("Failed to import yaml");
self.push_expr(Expression::literal(LiteralExpr { value }), None)
}
}