use crate::lex::SyntaxKind;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Lang {}
impl rowan::Language for Lang {
type Kind = SyntaxKind;
fn kind_from_raw(raw: rowan::SyntaxKind) -> Self::Kind {
assert!(
raw.0 <= SyntaxKind::EOF as u16,
"raw SyntaxKind value {} is out of range (max {})",
raw.0,
SyntaxKind::EOF as u16,
);
unsafe { std::mem::transmute::<u16, SyntaxKind>(raw.0) }
}
fn kind_to_raw(kind: Self::Kind) -> rowan::SyntaxKind {
kind.into()
}
}
pub type SyntaxNode = rowan::SyntaxNode<Lang>;
pub type SyntaxToken = rowan::SyntaxToken<Lang>;
pub(crate) fn fresh_token(kind: SyntaxKind, text: &str) -> SyntaxToken {
let mut builder = rowan::GreenNodeBuilder::new();
builder.start_node(SyntaxKind::ROOT.into());
builder.token(kind.into(), text);
builder.finish_node();
SyntaxNode::new_root_mut(builder.finish())
.first_token()
.expect("just built a token")
}
pub(crate) fn child_of_kind(parent: &SyntaxNode, kind: SyntaxKind) -> Option<SyntaxNode> {
parent.children().find(|n| n.kind() == kind)
}
pub(crate) fn has_child_token(parent: &SyntaxNode, pred: impl Fn(SyntaxKind) -> bool) -> bool {
parent
.children_with_tokens()
.any(|el| el.as_token().is_some_and(|t| pred(t.kind())))
}
pub(crate) fn append_children(
parent: &SyntaxNode,
children: Vec<rowan::NodeOrToken<SyntaxNode, SyntaxToken>>,
) {
let end = parent.children_with_tokens().count();
parent.splice_children(end..end, children);
}
pub(crate) fn trailing_newline_reachable(node: &SyntaxNode) -> bool {
let mut last_non_empty = None;
for el in node.descendants_with_tokens() {
if let Some(t) = el.into_token() {
if !t.text().is_empty() {
last_non_empty = Some(t);
}
}
}
last_non_empty.is_some_and(|t| t.kind() == SyntaxKind::NEWLINE)
}
pub(crate) fn entry_line_terminated(entry: &SyntaxNode) -> bool {
trailing_newline_reachable(entry)
|| entry
.next_sibling_or_token()
.is_some_and(|s| s.kind() == SyntaxKind::NEWLINE)
}
pub(crate) fn ensure_trailing_newline(entry: &SyntaxNode) {
if entry_line_terminated(entry) {
return;
}
append_children(entry, vec![fresh_token(SyntaxKind::NEWLINE, "\n").into()]);
}
pub(crate) fn ensure_own_trailing_newline(entry: &SyntaxNode) {
if trailing_newline_reachable(entry) {
return;
}
append_children(entry, vec![fresh_token(SyntaxKind::NEWLINE, "\n").into()]);
}
pub(crate) fn entry_indices(parent: &SyntaxNode, kind: SyntaxKind) -> Vec<usize> {
parent
.children_with_tokens()
.enumerate()
.filter(|(_, c)| c.as_node().is_some_and(|n| n.kind() == kind))
.map(|(i, _)| i)
.collect()
}
pub(crate) fn nth_entry_index(
parent: &SyntaxNode,
kind: SyntaxKind,
index: usize,
) -> Option<usize> {
parent
.children_with_tokens()
.enumerate()
.filter(|(_, c)| c.as_node().is_some_and(|n| n.kind() == kind))
.map(|(i, _)| i)
.nth(index)
}
pub(crate) fn build_key_content(builder: &mut rowan::GreenNodeBuilder, key: &impl crate::AsYaml) {
if key.as_node().is_none() {
let mut probe = rowan::GreenNodeBuilder::new();
probe.start_node(SyntaxKind::ROOT.into());
key.build_content(&mut probe, 0, false);
probe.finish_node();
let rendered = SyntaxNode::new_root(probe.finish()).text().to_string();
if rendered.starts_with('|') || rendered.starts_with('>') {
let mut flow = rowan::GreenNodeBuilder::new();
flow.start_node(SyntaxKind::ROOT.into());
key.build_content(&mut flow, 0, true);
flow.finish_node();
let quoted = SyntaxNode::new_root(flow.finish()).text().to_string();
builder.start_node(SyntaxKind::SCALAR.into());
builder.token(SyntaxKind::STRING.into(), "ed);
builder.finish_node();
return;
}
}
key.build_content(builder, 0, false);
}
pub(crate) fn entry_key(entry: &SyntaxNode) -> Option<SyntaxNode> {
child_of_kind(entry, SyntaxKind::KEY)
}
pub(crate) fn entry_value(entry: &SyntaxNode) -> Option<SyntaxNode> {
child_of_kind(entry, SyntaxKind::VALUE)
}
macro_rules! ast_node {
($ast:ident, $kind:ident, $doc:expr) => {
#[doc = $doc]
#[doc = ""]
#[doc = "**Note:** This type uses interior mutability through the rowan library."]
#[doc = "Mutation methods work even when called through `&self`. See the crate-level"]
#[doc = "documentation for details on the mutability model."]
#[derive(Clone, PartialEq, Eq, Hash)]
pub struct $ast(pub(crate) SyntaxNode);
impl std::fmt::Debug for $ast {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct(stringify!($ast))
.field("syntax", &self.0)
.finish()
}
}
impl AstNode for $ast {
type Language = Lang;
fn can_cast(kind: SyntaxKind) -> bool {
kind == SyntaxKind::$kind
}
fn cast(syntax: SyntaxNode) -> Option<Self> {
if Self::can_cast(syntax.kind()) {
Some(Self(syntax))
} else {
None
}
}
fn syntax(&self) -> &SyntaxNode {
&self.0
}
}
impl std::fmt::Display for $ast {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0.text())
}
}
};
}
pub(crate) use ast_node;
macro_rules! ast_node_spans {
($what:expr) => {
#[doc = concat!("Get the byte offset range of this ", $what, " in the source text.")]
#[doc = ""]
#[doc = "Returns the start and end byte offsets as a `TextPosition`."]
pub fn byte_range(&self) -> crate::TextPosition {
self.0.text_range().into()
}
#[doc = concat!("Get the line and column where this ", $what, " starts.")]
#[doc = ""]
#[doc = "Requires the original source text to calculate line/column from byte"]
#[doc = "offsets. Line and column numbers are 1-indexed."]
#[doc = ""]
#[doc = "# Arguments"]
#[doc = ""]
#[doc = "* `source_text` - The original YAML source text"]
pub fn start_position(&self, source_text: &str) -> crate::LineColumn {
crate::byte_offset_to_line_column(source_text, self.byte_range().start as usize)
}
#[doc = concat!("Get the line and column where this ", $what, " ends.")]
#[doc = ""]
#[doc = "Requires the original source text to calculate line/column from byte"]
#[doc = "offsets. Line and column numbers are 1-indexed."]
#[doc = ""]
#[doc = "# Arguments"]
#[doc = ""]
#[doc = "* `source_text` - The original YAML source text"]
pub fn end_position(&self, source_text: &str) -> crate::LineColumn {
crate::byte_offset_to_line_column(source_text, self.byte_range().end as usize)
}
};
}
pub(crate) use ast_node_spans;
pub mod alias_node;
pub mod comment;
pub mod directive;
pub mod document;
pub mod entry;
pub mod mapping;
pub mod scalar_node;
pub mod sequence;
pub mod tagged_node;
pub use alias_node::Alias;
pub use comment::Comment;
pub use directive::Directive;
pub use document::Document;
pub use entry::{Entry, OccupiedEntry, VacantEntry};
pub use mapping::{Mapping, MappingEntry};
pub use scalar_node::{Scalar, ScalarConversionError};
pub use sequence::Sequence;
pub use tagged_node::TaggedNode;