use crate::tag;
use crate::yaml::{Alias, Mapping, Scalar, Sequence, SyntaxNode, TaggedNode};
use rowan::ast::AstNode;
use std::borrow::Cow;
use std::fmt;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum YamlKind {
Mapping,
Sequence,
Scalar,
Alias,
Document,
Tagged(Cow<'static, str>),
}
pub trait AsYaml {
fn as_node(&self) -> Option<&SyntaxNode>;
fn kind(&self) -> YamlKind;
fn build_content(
&self,
builder: &mut rowan::GreenNodeBuilder,
indent: usize,
flow_context: bool,
) -> bool;
fn is_inline(&self) -> bool;
#[doc(hidden)]
fn build_flow_content(
&self,
builder: &mut rowan::GreenNodeBuilder,
indent: usize,
_quoting: FlowQuoting,
) -> bool {
self.build_content(builder, indent, true)
}
#[doc(hidden)]
fn value_node(&self) -> Option<SyntaxNode> {
self.as_node().cloned()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FlowQuoting {
Minimal,
Double,
}
pub(crate) fn build_in<T: AsYaml + ?Sized>(
value: &T,
builder: &mut rowan::GreenNodeBuilder,
indent: usize,
flow: Option<FlowQuoting>,
) -> bool {
match flow {
Some(quoting) => value.build_flow_content(builder, indent, quoting),
None => value.build_content(builder, indent, false),
}
}
pub fn yaml_eq<A, B>(a: &A, b: &B) -> bool
where
A: AsYaml + ?Sized,
B: AsYaml + ?Sized,
{
match (to_syntax(a), to_syntax(b)) {
(Some(a), Some(b)) => node_eq(&a, &b),
_ => false,
}
}
pub fn yaml_hash<T, H>(value: &T, state: &mut H)
where
T: AsYaml + ?Sized,
H: std::hash::Hasher,
{
use std::hash::Hash;
if let Some(Ok(Some(identity))) = to_syntax(value).map(|node| node_identity(&node, None)) {
identity.hash(state);
}
}
fn to_syntax<T: AsYaml + ?Sized>(v: &T) -> Option<SyntaxNode> {
use crate::lex::SyntaxKind;
if let Some(node) = v.value_node() {
return Some(node);
}
let mut builder = rowan::GreenNodeBuilder::new();
builder.start_node(SyntaxKind::ROOT.into());
v.build_content(&mut builder, 0, true);
builder.finish_node();
SyntaxNode::new_root(builder.finish()).first_child()
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub(crate) struct NodeIdentity(Identity<NodeIdentity>);
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub(crate) enum Identity<T> {
Scalar {
tag: String,
value: String,
},
Sequence {
tag: String,
items: Vec<T>,
},
Mapping {
tag: String,
entries: Vec<(T, T)>,
},
Alias {
tag: Option<String>,
name: String,
},
}
impl NodeIdentity {
pub(crate) fn key_string(&self) -> String {
match &self.0 {
Identity::Scalar { value, .. } => value.clone(),
_ => self.flow_text(),
}
}
fn flow_text(&self) -> String {
use crate::scalar::ScalarValue;
let tagged = |tag: &str, default: &str, content: String| {
if tag == default {
content
} else {
format!("{tag} {content}")
}
};
match &self.0 {
Identity::Scalar { tag, value } if tag == tag::STR => {
ScalarValue::string(value.as_str()).to_flow_string()
}
Identity::Scalar { tag, value } => {
let plain = resolve_scalar(ScalarValue::parse(value.as_str()));
if !value.is_empty() && plain.tag == *tag && plain.value == *value {
value.clone()
} else {
format!(
"{tag} {}",
ScalarValue::string(value.as_str()).to_flow_string()
)
}
}
Identity::Sequence { tag, items } => {
let items: Vec<_> = items.iter().map(Self::flow_text).collect();
tagged(tag, tag::SEQ, format!("[{}]", items.join(", ")))
}
Identity::Mapping { tag, entries } => {
let entries: Vec<_> = entries
.iter()
.map(|(k, v)| format!("{}: {}", k.flow_text(), v.flow_text()))
.collect();
tagged(tag, tag::MAP, format!("{{{}}}", entries.join(", ")))
}
Identity::Alias { tag, name } => match tag {
Some(tag) => format!("{tag} *{name}"),
None => format!("*{name}"),
},
}
}
}
fn collection_tag(tag: Option<String>, default: &str) -> String {
match tag.as_deref() {
None | Some("!") => default.to_string(),
Some(t) => tag::normalize(t).into_owned(),
}
}
struct ResolvedScalar {
tag: String,
value: String,
}
fn resolve_scalar(scalar: crate::scalar::ScalarValue) -> ResolvedScalar {
use crate::scalar::{ScalarType, ScalarValue};
let text = scalar.value();
let canonical = |parsed: Option<String>| parsed.unwrap_or_else(|| text.to_string());
let (tag, value) = match scalar.scalar_type() {
ScalarType::Integer => (
tag::INT.to_string(),
canonical(ScalarValue::canonical_integer(text)),
),
ScalarType::Float => (
tag::FLOAT.to_string(),
canonical(
ScalarValue::parse_float(text).map(|f| ScalarValue::format_float(f.to_string())),
),
),
ScalarType::Boolean => (
tag::BOOL.to_string(),
canonical(ScalarValue::parse_bool(text).map(|b| b.to_string())),
),
ScalarType::Null => (tag::NULL.to_string(), "null".to_string()),
_ => {
let tag = match scalar.tag() {
Some(t) if t != "!" => tag::normalize(t).into_owned(),
_ => tag::STR.to_string(),
};
(tag, text.to_string())
}
};
ResolvedScalar { tag, value }
}
pub(crate) fn node_identity(
node: &SyntaxNode,
registry: Option<&crate::anchor_resolution::AnchorRegistry>,
) -> IdentityResult<NodeIdentity> {
let aliases = match registry {
Some(registry) => Aliases::Follow(registry),
None => Aliases::Undefined,
};
identity_with_tag(node, None, aliases, &mut Vec::new(), &mut Trees)
}
pub(crate) fn lookup_identity(node: &SyntaxNode) -> Option<NodeIdentity> {
identity_with_tag(node, None, Aliases::ByName, &mut Vec::new(), &mut Trees)
.expect("aliases are not followed")
}
#[derive(Clone, Copy)]
enum Aliases<'a> {
Undefined,
Follow(&'a crate::anchor_resolution::AnchorRegistry),
ByName,
}
pub(crate) type IdentityResult<T> = Result<Option<T>, crate::anchor_resolution::AliasError>;
pub(crate) fn key_identity(
key: &YamlNode,
registry: Option<&crate::anchor_resolution::AnchorRegistry>,
) -> Result<NodeIdentity, crate::anchor_resolution::AliasError> {
Ok(node_identity(key.syntax(), registry)?.expect("a YamlNode is a value node"))
}
trait IdentityBuilder {
type Id: Ord;
fn build(&mut self, identity: Identity<Self::Id>) -> Self::Id;
fn known(&self, _node: &SyntaxNode) -> Option<IdentityResult<Self::Id>> {
None
}
fn remember(&mut self, _node: &SyntaxNode, _identity: &IdentityResult<Self::Id>) {}
}
struct Trees;
impl IdentityBuilder for Trees {
type Id = NodeIdentity;
fn build(&mut self, identity: Identity<NodeIdentity>) -> NodeIdentity {
NodeIdentity(identity)
}
}
#[derive(Default)]
pub(crate) struct SharedIdentities {
numbers: std::collections::HashMap<Identity<usize>, usize>,
nodes: std::collections::HashMap<SyntaxNode, IdentityResult<usize>>,
}
impl SharedIdentities {
pub(crate) fn identity(&mut self, node: &SyntaxNode) -> IdentityResult<usize> {
identity_with_tag(node, None, Aliases::Undefined, &mut Vec::new(), self)
}
}
impl IdentityBuilder for SharedIdentities {
type Id = usize;
fn build(&mut self, identity: Identity<usize>) -> usize {
let next = self.numbers.len();
*self.numbers.entry(identity).or_insert(next)
}
fn known(&self, node: &SyntaxNode) -> Option<IdentityResult<usize>> {
self.nodes.get(node).cloned()
}
fn remember(&mut self, node: &SyntaxNode, identity: &IdentityResult<usize>) {
self.nodes.insert(node.clone(), identity.clone());
}
}
fn identity_with_tag<B: IdentityBuilder>(
node: &SyntaxNode,
tag: Option<String>,
aliases: Aliases,
expanding: &mut Vec<SyntaxNode>,
builder: &mut B,
) -> IdentityResult<B::Id> {
use crate::lex::SyntaxKind;
let holds_nodes = matches!(
node.kind(),
SyntaxKind::MAPPING | SyntaxKind::SEQUENCE | SyntaxKind::TAGGED_NODE
);
if tag.is_some() || !holds_nodes {
return build_identity(node, tag, aliases, expanding, builder);
}
if let Some(identity) = builder.known(node) {
return identity;
}
let identity = build_identity(node, None, aliases, expanding, builder);
builder.remember(node, &identity);
identity
}
fn build_identity<B: IdentityBuilder>(
node: &SyntaxNode,
tag: Option<String>,
aliases: Aliases,
expanding: &mut Vec<SyntaxNode>,
builder: &mut B,
) -> IdentityResult<B::Id> {
use crate::anchor_resolution::AliasError;
use crate::lex::SyntaxKind;
use crate::scalar::ScalarValue;
let with_tag = |scalar: ScalarValue, tag: Option<String>| match tag {
Some(tag) => scalar.with_resolved_tag(tag),
None => scalar,
};
let scalar = |builder: &mut B, scalar: ScalarValue, tag: Option<String>| {
let ResolvedScalar { tag, value } = resolve_scalar(with_tag(scalar, tag));
builder.build(Identity::Scalar { tag, value })
};
match node.kind() {
SyntaxKind::KEY | SyntaxKind::VALUE => match node.first_child() {
Some(content) => identity_with_tag(&content, tag, aliases, expanding, builder),
None => Ok(None),
},
SyntaxKind::TAGGED_NODE => {
let Some(tagged) = TaggedNode::cast(node.clone()) else {
return Ok(None);
};
let tag = tag.or_else(|| tagged.resolved_tag());
match node.first_child() {
Some(content) => identity_with_tag(&content, tag, aliases, expanding, builder),
None => Ok(ScalarValue::from_tagged(&tagged).map(|s| scalar(builder, s, tag))),
}
}
SyntaxKind::ALIAS => {
let Some(alias) = Alias::cast(node.clone()) else {
return Ok(None);
};
let registry = match aliases {
Aliases::Follow(registry) => registry,
Aliases::ByName => {
return Ok(Some(builder.build(Identity::Alias {
tag,
name: alias.name(),
})))
}
Aliases::Undefined => {
return Err(AliasError::Undefined {
alias: alias.name(),
})
}
};
let target = registry.resolve_alias_through(&alias)?;
if expanding.contains(target) {
return Err(AliasError::Recursive {
alias: alias.name(),
});
}
expanding.push(target.clone());
let identity = identity_with_tag(target, tag, aliases, expanding, builder);
expanding.pop();
identity
}
SyntaxKind::SCALAR => {
Ok(Scalar::cast(node.clone())
.map(|s| scalar(builder, ScalarValue::from_scalar(&s), tag)))
}
SyntaxKind::MAPPING => {
let Some(mapping) = Mapping::cast(node.clone()) else {
return Ok(None);
};
let mut entries = Vec::new();
for (key, value) in mapping.pairs() {
let key = identity_with_tag(&key, None, aliases, expanding, builder)?;
let value = identity_with_tag(&value, None, aliases, expanding, builder)?;
let (Some(key), Some(value)) = (key, value) else {
return Ok(None);
};
entries.push((key, value));
}
entries.sort();
Ok(Some(builder.build(Identity::Mapping {
tag: collection_tag(tag, tag::MAP),
entries,
})))
}
SyntaxKind::SEQUENCE => {
let Some(sequence) = Sequence::cast(node.clone()) else {
return Ok(None);
};
let mut items = Vec::new();
for item in sequence.items() {
let Some(item) = identity_with_tag(&item, None, aliases, expanding, builder)?
else {
return Ok(None);
};
items.push(item);
}
Ok(Some(builder.build(Identity::Sequence {
tag: collection_tag(tag, tag::SEQ),
items,
})))
}
_ => Ok(None),
}
}
pub(crate) fn identity_of<T: AsYaml + ?Sized>(
v: &T,
registry: Option<&crate::anchor_resolution::AnchorRegistry>,
) -> Result<Option<NodeIdentity>, crate::anchor_resolution::AliasError> {
match to_syntax(v) {
Some(node) => node_identity(&node, registry),
None => Ok(None),
}
}
pub(crate) fn scalar_canonical(node: &SyntaxNode) -> Option<(String, String)> {
match node_identity(node, None) {
Ok(Some(NodeIdentity(Identity::Scalar { tag, value }))) => Some((tag, value)),
_ => None,
}
}
fn node_eq(a: &SyntaxNode, b: &SyntaxNode) -> bool {
match (node_identity(a, None), node_identity(b, None)) {
(Ok(Some(a)), Ok(Some(b))) => a == b,
_ => false,
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum YamlNode {
Scalar(Scalar),
Mapping(Mapping),
Sequence(Sequence),
Alias(Alias),
TaggedNode(TaggedNode),
}
impl YamlNode {
pub fn from_syntax(node: SyntaxNode) -> Option<Self> {
use crate::lex::SyntaxKind;
match node.kind() {
SyntaxKind::SCALAR => Scalar::cast(node).map(YamlNode::Scalar),
SyntaxKind::MAPPING => Mapping::cast(node).map(YamlNode::Mapping),
SyntaxKind::SEQUENCE => Sequence::cast(node).map(YamlNode::Sequence),
SyntaxKind::ALIAS => Alias::cast(node).map(YamlNode::Alias),
SyntaxKind::TAGGED_NODE => TaggedNode::cast(node).map(YamlNode::TaggedNode),
_ => None,
}
}
pub(crate) fn from_syntax_peeled(node: SyntaxNode) -> Option<Self> {
use crate::lex::SyntaxKind;
let inner = if matches!(node.kind(), SyntaxKind::KEY | SyntaxKind::VALUE) {
node.children().next()?
} else {
node
};
Self::from_syntax(inner)
}
pub fn kind(&self) -> YamlKind {
match self {
YamlNode::Scalar(_) => YamlKind::Scalar,
YamlNode::Mapping(_) => YamlKind::Mapping,
YamlNode::Sequence(_) => YamlKind::Sequence,
YamlNode::Alias(_) => YamlKind::Alias,
YamlNode::TaggedNode(t) => t
.tag()
.map_or(YamlKind::Scalar, |tag| YamlKind::Tagged(Cow::Owned(tag))),
}
}
pub(crate) fn syntax(&self) -> &SyntaxNode {
match self {
YamlNode::Scalar(s) => s.syntax(),
YamlNode::Mapping(m) => m.syntax(),
YamlNode::Sequence(s) => s.syntax(),
YamlNode::Alias(a) => a.syntax(),
YamlNode::TaggedNode(t) => t.syntax(),
}
}
pub fn yaml_eq<O: AsYaml>(&self, other: &O) -> bool {
yaml_eq(self, other)
}
pub fn as_scalar(&self) -> Option<&Scalar> {
if let YamlNode::Scalar(s) = self {
Some(s)
} else {
None
}
}
pub fn as_mapping(&self) -> Option<&Mapping> {
if let YamlNode::Mapping(m) = self {
Some(m)
} else {
None
}
}
pub fn as_sequence(&self) -> Option<&Sequence> {
if let YamlNode::Sequence(s) = self {
Some(s)
} else {
None
}
}
pub(crate) fn mapping_content(&self) -> Option<Mapping> {
match self {
YamlNode::Mapping(m) => Some(m.clone()),
YamlNode::TaggedNode(t) => t.as_mapping(),
_ => None,
}
}
pub(crate) fn sequence_content(&self) -> Option<Sequence> {
match self {
YamlNode::Sequence(s) => Some(s.clone()),
YamlNode::TaggedNode(t) => t.as_sequence(),
_ => None,
}
}
pub fn as_tagged(&self) -> Option<&TaggedNode> {
if let YamlNode::TaggedNode(t) = self {
Some(t)
} else {
None
}
}
pub fn as_alias(&self) -> Option<&Alias> {
if let YamlNode::Alias(a) = self {
Some(a)
} else {
None
}
}
pub fn is_scalar(&self) -> bool {
matches!(self, YamlNode::Scalar(_))
}
pub fn is_mapping(&self) -> bool {
matches!(self, YamlNode::Mapping(_))
}
pub fn is_sequence(&self) -> bool {
matches!(self, YamlNode::Sequence(_))
}
pub fn is_tagged(&self) -> bool {
matches!(self, YamlNode::TaggedNode(_))
}
pub fn is_alias(&self) -> bool {
matches!(self, YamlNode::Alias(_))
}
pub fn byte_range(&self) -> crate::TextPosition {
self.syntax().text_range().into()
}
pub fn to_i64(&self) -> Option<i64> {
crate::scalar::ScalarValue::from_scalar(self.as_scalar()?).to_i64()
}
pub fn to_f64(&self) -> Option<f64> {
crate::scalar::ScalarValue::from_scalar(self.as_scalar()?).to_f64()
}
pub fn to_bool(&self) -> Option<bool> {
crate::scalar::ScalarValue::from_scalar(self.as_scalar()?).to_bool()
}
pub fn get(&self, key: impl crate::AsYaml) -> Option<YamlNode> {
self.as_mapping()?
.get_node(key)
.and_then(YamlNode::from_syntax)
}
pub fn get_item(&self, index: usize) -> Option<YamlNode> {
self.as_sequence()?
.items()
.nth(index)
.and_then(YamlNode::from_syntax)
}
}
impl AsYaml for YamlNode {
fn as_node(&self) -> Option<&SyntaxNode> {
Some(self.syntax())
}
fn kind(&self) -> YamlKind {
YamlNode::kind(self)
}
fn build_content(
&self,
builder: &mut rowan::GreenNodeBuilder,
indent: usize,
flow_context: bool,
) -> bool {
match self {
YamlNode::Scalar(s) => s.build_content(builder, indent, flow_context),
YamlNode::Mapping(m) => m.build_content(builder, indent, flow_context),
YamlNode::Sequence(s) => s.build_content(builder, indent, flow_context),
YamlNode::Alias(a) => a.build_content(builder, indent, flow_context),
YamlNode::TaggedNode(t) => t.build_content(builder, indent, flow_context),
}
}
fn is_inline(&self) -> bool {
use crate::yaml::ValueNode;
match self {
YamlNode::Scalar(s) => ValueNode::is_inline(s),
YamlNode::Mapping(m) => ValueNode::is_inline(m),
YamlNode::Sequence(s) => ValueNode::is_inline(s),
YamlNode::Alias(_) => true,
YamlNode::TaggedNode(t) => ValueNode::is_inline(t),
}
}
}
impl PartialEq<str> for YamlNode {
fn eq(&self, other: &str) -> bool {
yaml_eq(self, &other)
}
}
impl PartialEq<&str> for YamlNode {
fn eq(&self, other: &&str) -> bool {
yaml_eq(self, other)
}
}
impl PartialEq<String> for YamlNode {
fn eq(&self, other: &String) -> bool {
yaml_eq(self, &other.as_str())
}
}
impl fmt::Display for YamlNode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.syntax().text())
}
}
impl<T: AsYaml + ?Sized> AsYaml for &T {
fn as_node(&self) -> Option<&SyntaxNode> {
(*self).as_node()
}
fn kind(&self) -> YamlKind {
(*self).kind()
}
fn build_content(
&self,
builder: &mut rowan::GreenNodeBuilder,
indent: usize,
flow_context: bool,
) -> bool {
(*self).build_content(builder, indent, flow_context)
}
fn is_inline(&self) -> bool {
(*self).is_inline()
}
fn build_flow_content(
&self,
builder: &mut rowan::GreenNodeBuilder,
indent: usize,
quoting: FlowQuoting,
) -> bool {
(*self).build_flow_content(builder, indent, quoting)
}
fn value_node(&self) -> Option<SyntaxNode> {
(*self).value_node()
}
}
pub(crate) fn copy_node_content(builder: &mut rowan::GreenNodeBuilder, node: &SyntaxNode) {
for child in node.children_with_tokens() {
match child {
rowan::NodeOrToken::Node(n) => {
builder.start_node(n.kind().into());
copy_node_content(builder, &n);
builder.finish_node();
}
rowan::NodeOrToken::Token(t) => {
builder.token(t.kind().into(), t.text());
}
}
}
}
pub(crate) fn copy_node_content_reindent(
builder: &mut rowan::GreenNodeBuilder,
node: &SyntaxNode,
delta: isize,
) -> bool {
use crate::lex::SyntaxKind;
let mut after_newline = false;
let pad = |n: isize| " ".repeat(n.max(0) as usize);
for child in node.children_with_tokens() {
match child {
rowan::NodeOrToken::Node(n) => {
if n.text_range().is_empty() {
builder.start_node(n.kind().into());
copy_node_content(builder, &n);
builder.finish_node();
continue;
}
if after_newline && delta > 0 {
builder.token(SyntaxKind::INDENT.into(), &pad(delta));
}
builder.start_node(n.kind().into());
after_newline = copy_node_content_reindent(builder, &n, delta);
builder.finish_node();
}
rowan::NodeOrToken::Token(t) => match t.kind() {
SyntaxKind::NEWLINE => {
builder.token(t.kind().into(), t.text());
after_newline = true;
}
SyntaxKind::WHITESPACE | SyntaxKind::INDENT => {
if after_newline {
let shifted = shift_line_start(t.text(), delta);
if !shifted.is_empty() {
builder.token(SyntaxKind::INDENT.into(), &shifted);
}
} else {
builder.token(t.kind().into(), t.text());
}
after_newline = false;
}
_ => {
if after_newline && delta > 0 {
builder.token(SyntaxKind::INDENT.into(), &pad(delta));
}
builder.token(t.kind().into(), &shift_inner_lines(t.text(), delta));
after_newline = false;
}
},
}
}
after_newline
}
pub(crate) fn clear_parent_column(node: &SyntaxNode, parent: usize, delta: isize) -> isize {
let shallowest = node
.text()
.to_string()
.split(['\n', '\r'])
.skip(1)
.filter(|line| !line.trim().is_empty())
.map(crate::column::space_indent)
.min();
shallowest.map_or(delta, |column| {
delta.max(parent as isize + 1 - column as isize)
})
}
pub(crate) fn shift_line_start(text: &str, delta: isize) -> String {
if delta >= 0 {
return format!("{}{text}", " ".repeat(delta as usize));
}
let spaces = crate::column::space_indent(text);
text[spaces.min(delta.unsigned_abs())..].to_string()
}
pub(crate) fn shift_inner_lines(text: &str, delta: isize) -> Cow<'_, str> {
if delta == 0 || !text.contains(['\n', '\r']) {
return Cow::Borrowed(text);
}
let mut lines = text.split_inclusive(['\n', '\r']);
let mut shifted = lines.next().unwrap_or_default().to_string();
for line in lines {
if line.trim_end_matches(['\n', '\r']).is_empty() {
shifted.push_str(line);
} else {
shifted.push_str(&shift_line_start(line, delta));
}
}
Cow::Owned(shifted)
}
pub(crate) fn source_reindent_base(node: &SyntaxNode, is_flow: bool) -> usize {
if is_flow {
crate::column::line_indent(node)
} else {
crate::column::node_column(node)
}
}
macro_rules! impl_as_yaml_scalar {
($to_scalar:expr; $($ty:ty),+ $(,)?) => {
$(impl AsYaml for $ty {
fn as_node(&self) -> Option<&crate::yaml::SyntaxNode> {
None
}
fn kind(&self) -> YamlKind {
YamlKind::Scalar
}
fn build_content(&self, builder: &mut rowan::GreenNodeBuilder, indent: usize, _flow_context: bool) -> bool {
let to_scalar: fn(&$ty) -> crate::scalar::ScalarValue = $to_scalar;
to_scalar(self).build(builder, indent, None)
}
fn is_inline(&self) -> bool {
true
}
})+
};
}
impl_as_yaml_scalar!(
|v| crate::scalar::ScalarValue::plain(v.to_string()).with_type(crate::scalar::ScalarType::Integer);
i64, i32, i16, i8, isize, u64, u32, u16, u8, usize
);
impl_as_yaml_scalar!(|v| crate::scalar::ScalarValue::from(*v); f64, f32, bool);
impl AsYaml for String {
fn as_node(&self) -> Option<&crate::yaml::SyntaxNode> {
None
}
fn kind(&self) -> YamlKind {
YamlKind::Scalar
}
fn build_content(
&self,
builder: &mut rowan::GreenNodeBuilder,
indent: usize,
flow_context: bool,
) -> bool {
self.as_str().build_content(builder, indent, flow_context)
}
fn is_inline(&self) -> bool {
true
}
fn build_flow_content(
&self,
builder: &mut rowan::GreenNodeBuilder,
indent: usize,
quoting: FlowQuoting,
) -> bool {
self.as_str().build_flow_content(builder, indent, quoting)
}
}
impl AsYaml for &str {
fn as_node(&self) -> Option<&crate::yaml::SyntaxNode> {
None
}
fn kind(&self) -> YamlKind {
YamlKind::Scalar
}
fn build_content(
&self,
builder: &mut rowan::GreenNodeBuilder,
indent: usize,
flow_context: bool,
) -> bool {
crate::scalar::ScalarValue::string(*self).build_content(builder, indent, flow_context)
}
fn is_inline(&self) -> bool {
true
}
fn build_flow_content(
&self,
builder: &mut rowan::GreenNodeBuilder,
indent: usize,
quoting: FlowQuoting,
) -> bool {
crate::scalar::ScalarValue::string(*self).build_flow_content(builder, indent, quoting)
}
}
impl<T: AsYaml> AsYaml for Option<T> {
fn as_node(&self) -> Option<&crate::yaml::SyntaxNode> {
self.as_ref().and_then(AsYaml::as_node)
}
fn kind(&self) -> YamlKind {
match self {
Some(v) => v.kind(),
None => YamlKind::Scalar,
}
}
fn build_content(
&self,
builder: &mut rowan::GreenNodeBuilder,
indent: usize,
flow_context: bool,
) -> bool {
match self {
Some(v) => v.build_content(builder, indent, flow_context),
None => crate::scalar::ScalarValue::null().build_content(builder, indent, flow_context),
}
}
fn is_inline(&self) -> bool {
match self {
Some(v) => v.is_inline(),
None => true,
}
}
fn build_flow_content(
&self,
builder: &mut rowan::GreenNodeBuilder,
indent: usize,
quoting: FlowQuoting,
) -> bool {
match self {
Some(v) => v.build_flow_content(builder, indent, quoting),
None => self.build_content(builder, indent, true),
}
}
}
pub(crate) fn build_tagged(
builder: &mut rowan::GreenNodeBuilder,
tag: &str,
block: bool,
indent: usize,
content: impl FnOnce(&mut rowan::GreenNodeBuilder) -> bool,
) -> bool {
use crate::lex::SyntaxKind;
builder.start_node(SyntaxKind::TAGGED_NODE.into());
builder.token(SyntaxKind::TAG.into(), tag);
if block {
builder.token(SyntaxKind::NEWLINE.into(), "\n");
builder.token(SyntaxKind::INDENT.into(), &" ".repeat(indent + 2));
} else {
builder.token(SyntaxKind::WHITESPACE.into(), " ");
}
let ends_with_newline = content(builder);
builder.finish_node(); ends_with_newline
}
pub(crate) fn build_mapping<K: AsYaml, V: AsYaml>(
builder: &mut rowan::GreenNodeBuilder,
entries: impl ExactSizeIterator<Item = (K, V)>,
indent: usize,
flow: Option<FlowQuoting>,
) -> bool {
use crate::lex::SyntaxKind;
let len = entries.len();
let block = flow.is_none() && len > 0;
let flow = (!block).then_some(flow.unwrap_or(FlowQuoting::Minimal));
builder.start_node(SyntaxKind::MAPPING.into());
if !block {
builder.token(SyntaxKind::LEFT_BRACE.into(), "{");
}
for (i, (key, value)) in entries.enumerate() {
if i > 0 && block && indent > 0 {
builder.token(SyntaxKind::INDENT.into(), &" ".repeat(indent));
}
builder.start_node(SyntaxKind::MAPPING_ENTRY.into());
builder.start_node(SyntaxKind::KEY.into());
crate::nodes::build_key_content(builder, &key, indent, flow);
builder.finish_node(); builder.token(SyntaxKind::COLON.into(), ":");
let starts_block = block && !value.is_inline();
let ends_with_newline = if starts_block {
builder.start_node(SyntaxKind::VALUE.into());
builder.token(SyntaxKind::NEWLINE.into(), "\n");
builder.token(SyntaxKind::INDENT.into(), &" ".repeat(indent + 2));
build_in(&value, builder, indent + 2, None)
} else {
builder.token(SyntaxKind::WHITESPACE.into(), " ");
builder.start_node(SyntaxKind::VALUE.into());
build_in(&value, builder, indent, flow)
};
builder.finish_node(); if block {
if !ends_with_newline {
builder.token(SyntaxKind::NEWLINE.into(), "\n");
}
} else if i + 1 < len {
builder.token(SyntaxKind::COMMA.into(), ",");
builder.token(SyntaxKind::WHITESPACE.into(), " ");
}
builder.finish_node(); }
if !block {
builder.token(SyntaxKind::RIGHT_BRACE.into(), "}");
}
builder.finish_node(); block
}
pub(crate) fn build_sequence<T: AsYaml>(
builder: &mut rowan::GreenNodeBuilder,
items: impl ExactSizeIterator<Item = T>,
indent: usize,
flow: Option<FlowQuoting>,
) -> bool {
use crate::lex::SyntaxKind;
let len = items.len();
let block = flow.is_none() && len > 0;
let flow = (!block).then_some(flow.unwrap_or(FlowQuoting::Minimal));
builder.start_node(SyntaxKind::SEQUENCE.into());
if !block {
builder.token(SyntaxKind::LEFT_BRACKET.into(), "[");
}
for (i, item) in items.enumerate() {
if i > 0 && block && indent > 0 {
builder.token(SyntaxKind::INDENT.into(), &" ".repeat(indent));
}
builder.start_node(SyntaxKind::SEQUENCE_ENTRY.into());
if block {
builder.token(SyntaxKind::DASH.into(), "-");
builder.token(SyntaxKind::WHITESPACE.into(), " ");
let item_indent = if item.is_inline() { indent } else { indent + 2 };
if !build_in(&item, builder, item_indent, None) {
builder.token(SyntaxKind::NEWLINE.into(), "\n");
}
} else {
build_in(&item, builder, indent, flow);
if i + 1 < len {
builder.token(SyntaxKind::COMMA.into(), ",");
builder.token(SyntaxKind::WHITESPACE.into(), " ");
}
}
builder.finish_node(); }
if !block {
builder.token(SyntaxKind::RIGHT_BRACKET.into(), "]");
}
builder.finish_node(); block
}
pub(crate) fn build_set_content<T: AsYaml>(
builder: &mut rowan::GreenNodeBuilder,
members: impl ExactSizeIterator<Item = T>,
indent: usize,
flow: Option<FlowQuoting>,
) -> bool {
let block = flow.is_none() && members.len() > 0;
build_tagged(builder, tag::SET, block, indent, |builder| {
let entries = members.map(|m| (m, crate::scalar::ScalarValue::null()));
build_mapping(builder, entries, indent + 2, flow)
})
}
impl<T: AsYaml> AsYaml for std::collections::BTreeSet<T> {
fn as_node(&self) -> Option<&crate::yaml::SyntaxNode> {
None
}
fn kind(&self) -> YamlKind {
YamlKind::Tagged(Cow::Borrowed(tag::SET))
}
fn build_content(
&self,
builder: &mut rowan::GreenNodeBuilder,
indent: usize,
flow_context: bool,
) -> bool {
build_set_content(
builder,
self.iter(),
indent,
flow_context.then_some(FlowQuoting::Minimal),
)
}
fn is_inline(&self) -> bool {
true
}
fn build_flow_content(
&self,
builder: &mut rowan::GreenNodeBuilder,
indent: usize,
quoting: FlowQuoting,
) -> bool {
build_set_content(builder, self.iter(), indent, Some(quoting))
}
}