use crate::as_yaml::{key_identity, AsYaml, NodeIdentity};
use crate::lex::SyntaxKind;
use crate::value::YamlValue;
use crate::yaml::SyntaxNode;
use rowan::ast::AstNode;
use std::collections::HashMap;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ResolveError {
UndefinedAlias {
alias: String,
},
RecursiveAlias {
alias: String,
},
InvalidMergeOperand {
operand: String,
},
DuplicateKey {
first: String,
second: String,
},
KeyCollision {
first: String,
second: String,
},
InvalidSetContent {
content: String,
},
InvalidSetMember {
member: String,
},
NonNullSetValue {
member: String,
value: String,
},
InvalidPairsItem {
tag: String,
item: String,
},
InvalidPairsContent {
tag: String,
content: String,
},
UnresolvedAlias {
alias: String,
},
ExtraNode {
node: String,
},
}
impl std::fmt::Display for ResolveError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ResolveError::UndefinedAlias { alias } => {
write!(f, "alias to undefined anchor {alias:?}")
}
ResolveError::RecursiveAlias { alias } => {
write!(f, "alias {alias:?} refers to an anchor it is nested in")
}
ResolveError::InvalidMergeOperand { operand } => {
write!(f, "merge key operand {operand:?} is not a mapping")
}
ResolveError::KeyCollision { first, second } => {
write!(
f,
"distinct keys {first:?} and {second:?} have the same string form"
)
}
ResolveError::DuplicateKey { first, second } => {
write!(f, "duplicate mapping key: {second:?} equals {first:?}")
}
ResolveError::InvalidSetContent { content } => {
write!(f, "!!set content {content:?} is not a mapping")
}
ResolveError::InvalidSetMember { member } => {
write!(f, "!!set member {member:?} is not a scalar")
}
ResolveError::NonNullSetValue { member, value } => {
write!(f, "!!set member {member:?} has non-null value {value:?}")
}
ResolveError::InvalidPairsItem { tag, item } => {
write!(
f,
"{tag} item {item:?} is not a mapping with a single entry"
)
}
ResolveError::InvalidPairsContent { tag, content } => {
write!(f, "{tag} content {content:?} is not a sequence")
}
ResolveError::UnresolvedAlias { alias } => {
write!(
f,
"alias {alias:?} cannot be converted without resolving it; \
use get_resolved to resolve aliases"
)
}
ResolveError::ExtraNode { node } => {
write!(f, "node {node:?} follows another in the same place")
}
}
}
}
impl std::error::Error for ResolveError {}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum AliasError {
Undefined { alias: String },
Recursive { alias: String },
}
impl From<AliasError> for ResolveError {
fn from(err: AliasError) -> Self {
match err {
AliasError::Undefined { alias } => ResolveError::UndefinedAlias { alias },
AliasError::Recursive { alias } => ResolveError::RecursiveAlias { alias },
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum MergeError {
Alias(AliasError),
InvalidOperand {
operand: String,
},
}
impl From<AliasError> for MergeError {
fn from(err: AliasError) -> Self {
MergeError::Alias(err)
}
}
impl From<MergeError> for ResolveError {
fn from(err: MergeError) -> Self {
match err {
MergeError::Alias(err) => err.into(),
MergeError::InvalidOperand { operand } => ResolveError::InvalidMergeOperand { operand },
}
}
}
#[derive(Debug, Clone)]
pub struct AnchorRegistry {
anchors: HashMap<String, Vec<(rowan::TextSize, SyntaxNode)>>,
}
impl AnchorRegistry {
pub fn new() -> Self {
Self {
anchors: HashMap::new(),
}
}
pub fn from_document(doc: &crate::yaml::Document) -> Self {
if let Some(node) = doc.as_node() {
Self::from_tree(node)
} else {
Self::new()
}
}
pub fn from_tree(root: &SyntaxNode) -> Self {
let mut registry = Self::new();
registry.collect_anchors_from_tree(root);
registry
}
fn collect_anchors_from_tree(&mut self, node: &SyntaxNode) {
for child in node.children_with_tokens() {
if let Some(token) = child.as_token() {
if token.kind() == SyntaxKind::ANCHOR {
let text = token.text();
if let Some(name) = text.strip_prefix('&') {
if let Some(value_node) = self.find_anchored_value(node) {
self.anchors
.entry(name.to_string())
.or_default()
.push((token.text_range().start(), value_node));
}
}
}
} else if let Some(child_node) = child.as_node() {
self.collect_anchors_from_tree(child_node);
}
}
}
fn find_anchored_value(&self, node: &SyntaxNode) -> Option<SyntaxNode> {
if node.kind() == SyntaxKind::TAGGED_NODE {
return Some(node.clone());
}
for child in node.children() {
if matches!(
child.kind(),
SyntaxKind::VALUE
| SyntaxKind::SCALAR
| SyntaxKind::MAPPING
| SyntaxKind::SEQUENCE
| SyntaxKind::TAGGED_NODE
| SyntaxKind::ALIAS
) {
return Some(child);
}
}
None
}
pub fn resolve(&self, name: &str) -> Option<&SyntaxNode> {
self.anchors.get(name)?.last().map(|(_, node)| node)
}
pub(crate) fn resolve_alias(&self, name: &str, alias: &SyntaxNode) -> Option<&SyntaxNode> {
let position = alias.text_range().start();
self.anchors
.get(name)?
.iter()
.rev()
.find(|(anchor, _)| *anchor < position)
.map(|(_, node)| node)
}
pub(crate) fn resolve_alias_through(
&self,
alias: &crate::yaml::Alias,
) -> Result<&SyntaxNode, AliasError> {
let mut alias = alias.clone();
let mut seen = Vec::new();
loop {
let name = alias.name();
let target =
self.resolve_alias(&name, alias.syntax())
.ok_or_else(|| AliasError::Undefined {
alias: name.clone(),
})?;
let content = if target.kind() == SyntaxKind::VALUE {
target.children().next()
} else {
Some(target.clone())
};
let Some(next) = content.and_then(crate::yaml::Alias::cast) else {
return Ok(target);
};
if seen.contains(target) {
return Err(AliasError::Recursive { alias: name });
}
seen.push(target.clone());
alias = next;
}
}
pub fn contains(&self, name: &str) -> bool {
self.anchors.contains_key(name)
}
pub fn anchor_names(&self) -> impl Iterator<Item = &str> {
self.anchors.keys().map(|s| s.as_str())
}
}
impl Default for AnchorRegistry {
fn default() -> Self {
Self::new()
}
}
pub trait DocumentResolvedExt {
fn get_resolved(&self, key: impl crate::AsYaml) -> Result<Option<YamlValue>, ResolveError>;
fn build_anchor_registry(&self) -> AnchorRegistry;
}
impl DocumentResolvedExt for crate::yaml::Document {
fn get_resolved(&self, key: impl crate::AsYaml) -> Result<Option<YamlValue>, ResolveError> {
let Some(node) = self.as_mapping().and_then(|m| m.get(&key)) else {
return Ok(None);
};
let registry = self.build_anchor_registry();
crate::value::convert::resolved_value(node, Some(®istry), &mut Vec::new())
}
fn build_anchor_registry(&self) -> AnchorRegistry {
AnchorRegistry::from_document(self)
}
}
pub(crate) fn is_merge_key(key: &crate::as_yaml::YamlNode) -> bool {
key.as_scalar().is_some_and(|s| s.value() == "<<")
}
#[derive(Clone)]
pub struct MergedMapping<'a> {
base: crate::yaml::Mapping,
registry: &'a AnchorRegistry,
entries: Vec<MergedEntry>,
}
impl<'a> MergedMapping<'a> {
pub fn new(
base: crate::yaml::Mapping,
registry: &'a AnchorRegistry,
) -> Result<Self, ResolveError> {
Ok(Self::build(base, registry)?)
}
pub(crate) fn build(
base: crate::yaml::Mapping,
registry: &'a AnchorRegistry,
) -> Result<Self, MergeError> {
let entries = merged_entries(&base, registry)?;
Ok(Self {
base,
registry,
entries,
})
}
pub fn base(&self) -> &crate::yaml::Mapping {
&self.base
}
pub fn registry(&self) -> &AnchorRegistry {
self.registry
}
pub fn get(&self, key: impl crate::AsYaml) -> Option<crate::as_yaml::YamlNode> {
let identity = crate::as_yaml::identity_of(&key, Some(self.registry))
.ok()
.flatten()?;
self.entries
.iter()
.find(|(k, _, _)| *k == identity)
.map(|(_, _, v)| v.clone())
}
pub fn contains_key(&self, key: impl crate::AsYaml) -> bool {
self.get(key).is_some()
}
pub fn iter(
&self,
) -> impl Iterator<Item = (crate::as_yaml::YamlNode, crate::as_yaml::YamlNode)> + '_ {
self.entries.iter().map(|(_, k, v)| (k.clone(), v.clone()))
}
pub fn keys(&self) -> impl Iterator<Item = crate::as_yaml::YamlNode> + '_ {
self.iter().map(|(k, _)| k)
}
pub fn values(&self) -> impl Iterator<Item = crate::as_yaml::YamlNode> + '_ {
self.iter().map(|(_, v)| v)
}
pub fn len(&self) -> usize {
self.iter().count()
}
pub fn is_empty(&self) -> bool {
self.iter().next().is_none()
}
pub fn get_merged(
&self,
key: impl crate::AsYaml,
) -> Result<Option<MergedMapping<'a>>, ResolveError> {
let Some(mapping) = self.get(key).and_then(|node| node.as_mapping().cloned()) else {
return Ok(None);
};
MergedMapping::new(mapping, self.registry).map(Some)
}
}
impl std::fmt::Debug for MergedMapping<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("MergedMapping")
.field("len", &self.len())
.finish()
}
}
pub(crate) fn merge_sources(
base: &crate::yaml::Mapping,
registry: &AnchorRegistry,
) -> Result<Vec<crate::yaml::Mapping>, MergeError> {
let mut sources = Vec::new();
collect_merge_sources(base, registry, &mut sources)?;
Ok(sources)
}
pub(crate) fn merge_operands(
value: crate::as_yaml::YamlNode,
registry: &AnchorRegistry,
) -> Result<Vec<crate::as_yaml::YamlNode>, AliasError> {
use crate::as_yaml::YamlNode;
let sequence = match resolve_alias_node(value.clone(), registry)? {
YamlNode::Sequence(seq) => Some(seq),
YamlNode::TaggedNode(tagged) => tagged.as_sequence(),
_ => None,
};
Ok(match sequence {
Some(seq) => seq.values().collect(),
None => vec![value],
})
}
pub(crate) fn merge_source(
operand: &crate::as_yaml::YamlNode,
registry: &AnchorRegistry,
) -> Result<Option<crate::yaml::Mapping>, AliasError> {
use crate::as_yaml::YamlNode;
Ok(match resolve_alias_node(operand.clone(), registry)? {
YamlNode::Mapping(m) => Some(m),
YamlNode::TaggedNode(tagged) => tagged.as_mapping(),
_ => None,
})
}
fn collect_merge_sources(
base: &crate::yaml::Mapping,
registry: &AnchorRegistry,
sources: &mut Vec<crate::yaml::Mapping>,
) -> Result<(), MergeError> {
for (key, value) in base.iter() {
if !is_merge_key(&key) {
continue;
}
for operand in merge_operands(value, registry)? {
let source =
merge_source(&operand, registry)?.ok_or_else(|| MergeError::InvalidOperand {
operand: operand.to_string(),
})?;
if sources.iter().any(|s| s.syntax() == source.syntax()) {
continue;
}
sources.push(source.clone());
collect_merge_sources(&source, registry, sources)?;
}
}
Ok(())
}
fn merged_entries(
base: &crate::yaml::Mapping,
registry: &AnchorRegistry,
) -> Result<Vec<MergedEntry>, MergeError> {
let mut entries: Vec<MergedEntry> = Vec::new();
for (key, value) in base.iter().filter(|(k, _)| !is_merge_key(k)) {
let identity = key_identity(&key, Some(registry))?;
entries.push((identity, key, resolve_alias_node(value, registry)?));
}
for source in merge_sources(base, registry)? {
for (key, value) in source.iter() {
if is_merge_key(&key) {
continue;
}
let identity = key_identity(&key, Some(registry))?;
if entries.iter().any(|(k, _, _)| *k == identity) {
continue;
}
entries.push((identity, key, resolve_alias_node(value, registry)?));
}
}
Ok(entries)
}
type MergedEntry = (
NodeIdentity,
crate::as_yaml::YamlNode,
crate::as_yaml::YamlNode,
);
pub(crate) fn document_scope(node: &SyntaxNode) -> SyntaxNode {
node.ancestors()
.find(|n| n.kind() == SyntaxKind::DOCUMENT)
.unwrap_or_else(|| node.ancestors().last().expect("ancestors has self"))
}
pub(crate) fn resolve_alias_node(
node: crate::as_yaml::YamlNode,
registry: &AnchorRegistry,
) -> Result<crate::as_yaml::YamlNode, AliasError> {
use crate::as_yaml::YamlNode;
let YamlNode::Alias(alias) = &node else {
return Ok(node);
};
resolve_alias_with(alias, registry, SyntaxNode::clone)
}
pub(crate) fn resolve_alias_with(
alias: &crate::yaml::Alias,
registry: &AnchorRegistry,
adjust: impl FnOnce(&SyntaxNode) -> SyntaxNode,
) -> Result<crate::as_yaml::YamlNode, AliasError> {
let target = adjust(registry.resolve_alias_through(alias)?);
crate::as_yaml::YamlNode::from_syntax_peeled(target).ok_or(AliasError::Undefined {
alias: alias.name(),
})
}
pub trait MappingMergedExt {
fn merged<'a>(&self, registry: &'a AnchorRegistry) -> Result<MergedMapping<'a>, ResolveError>;
}
impl MappingMergedExt for crate::yaml::Mapping {
fn merged<'a>(&self, registry: &'a AnchorRegistry) -> Result<MergedMapping<'a>, ResolveError> {
MergedMapping::new(self.clone(), registry)
}
}
pub trait DocumentMergedExt {
fn merged(&self) -> Result<Option<MergedView>, ResolveError>;
}
impl DocumentMergedExt for crate::yaml::Document {
fn merged(&self) -> Result<Option<MergedView>, ResolveError> {
let Some(mapping) = self.as_mapping() else {
return Ok(None);
};
let registry = self.build_anchor_registry();
let entries = merged_entries(&mapping, ®istry).map_err(ResolveError::from)?;
Ok(Some(MergedView {
mapping,
registry,
entries,
}))
}
}
pub struct MergedView {
mapping: crate::yaml::Mapping,
registry: AnchorRegistry,
entries: Vec<MergedEntry>,
}
impl MergedView {
pub fn as_mapping(&self) -> MergedMapping<'_> {
MergedMapping {
base: self.mapping.clone(),
registry: &self.registry,
entries: self.entries.clone(),
}
}
pub fn get(&self, key: impl crate::AsYaml) -> Option<crate::as_yaml::YamlNode> {
self.as_mapping().get(key)
}
pub fn contains_key(&self, key: impl crate::AsYaml) -> bool {
self.as_mapping().contains_key(key)
}
pub fn registry(&self) -> &AnchorRegistry {
&self.registry
}
}
impl std::fmt::Debug for MergedView {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("MergedView").finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Document;
use std::str::FromStr;
fn doc(text: &str) -> Document {
Document::from_str(text).expect("parse")
}
#[test]
fn get_resolved_tagged_alias() {
for (yaml, expected) in [
("x: &x 1\na: !t *x\n", "a: !t 1\n"),
("x: &x '1'\na: !!int *x\n", "a: !!int '1'\n"),
("x: &x [1]\na: !t *x\n", "a: !t [1]\n"),
("x: &x {k: 1}\na: !t *x\n", "a: !t {k: 1}\n"),
("x: &x {k: }\na: !!set *x\n", "a: !!set {k: }\n"),
("x: &x [{k: 1}]\na: !!omap *x\n", "a: !!omap [{k: 1}]\n"),
("y: &y 2\nx: &x [*y]\na: !t *x\n", "a: !t [2]\n"),
("x: &x !u 1\na: !t *x\n", "a: !t 1\n"),
("x: !u &x [1]\na: !t *x\n", "a: !t [1]\n"),
("x: &x !u {k: }\na: !!set *x\n", "a: !!set {k: }\n"),
] {
assert_eq!(
doc(yaml).get_resolved("a"),
doc(expected).get_resolved("a"),
"{yaml:?}"
);
}
assert_eq!(
doc("x: &x 1\na: !t *x\n").get_resolved("a"),
Ok(Some(YamlValue::Scalar(
crate::ScalarValue::parse("1").with_resolved_tag("!t".to_string())
)))
);
}
}