use core::fmt;
use core::fmt::Write;
use crate::parse::Style;
use crate::text::LineColumn;
use crate::text::LineIndex;
use crate::text::TextRange;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[allow(non_camel_case_types)]
#[non_exhaustive]
pub enum SyntaxKind {
NAME,
TYPE,
COLON,
COMMA,
DESCRIPTION,
OPEN_BRACKET,
CLOSE_BRACKET,
OPTIONAL,
SUMMARY,
EXTENDED_SUMMARY,
TEXT_LINE,
WHITESPACE,
NEWLINE,
BLANK_LINE,
UNDERLINE,
DIRECTIVE_MARKER,
DIRECTIVE_NAME,
DOUBLE_COLON,
ARGUMENT,
DEFAULT_KEYWORD,
DEFAULT_SEPARATOR,
DEFAULT_VALUE,
LABEL,
DOCUMENT,
SECTION,
SECTION_HEADER,
ENTRY,
DIRECTIVE,
CITATION,
DEFAULT,
PARAGRAPH,
}
impl SyntaxKind {
pub const fn is_node(self) -> bool {
matches!(
self,
Self::SUMMARY
| Self::EXTENDED_SUMMARY
| Self::DESCRIPTION
| Self::DOCUMENT
| Self::SECTION
| Self::SECTION_HEADER
| Self::ENTRY
| Self::DIRECTIVE
| Self::CITATION
| Self::DEFAULT
| Self::PARAGRAPH
)
}
pub const fn is_token(self) -> bool {
!self.is_node()
}
pub const fn is_trivia(self) -> bool {
matches!(self, Self::WHITESPACE | Self::NEWLINE | Self::BLANK_LINE)
}
pub const fn name(self) -> &'static str {
match self {
Self::NAME => "NAME",
Self::TYPE => "TYPE",
Self::COLON => "COLON",
Self::COMMA => "COMMA",
Self::DESCRIPTION => "DESCRIPTION",
Self::OPEN_BRACKET => "OPEN_BRACKET",
Self::CLOSE_BRACKET => "CLOSE_BRACKET",
Self::OPTIONAL => "OPTIONAL",
Self::SUMMARY => "SUMMARY",
Self::EXTENDED_SUMMARY => "EXTENDED_SUMMARY",
Self::TEXT_LINE => "TEXT_LINE",
Self::WHITESPACE => "WHITESPACE",
Self::NEWLINE => "NEWLINE",
Self::BLANK_LINE => "BLANK_LINE",
Self::UNDERLINE => "UNDERLINE",
Self::DIRECTIVE_MARKER => "DIRECTIVE_MARKER",
Self::DIRECTIVE_NAME => "DIRECTIVE_NAME",
Self::DOUBLE_COLON => "DOUBLE_COLON",
Self::ARGUMENT => "ARGUMENT",
Self::DEFAULT_KEYWORD => "DEFAULT_KEYWORD",
Self::DEFAULT_SEPARATOR => "DEFAULT_SEPARATOR",
Self::DEFAULT_VALUE => "DEFAULT_VALUE",
Self::LABEL => "LABEL",
Self::DOCUMENT => "DOCUMENT",
Self::SECTION => "SECTION",
Self::SECTION_HEADER => "SECTION_HEADER",
Self::ENTRY => "ENTRY",
Self::DIRECTIVE => "DIRECTIVE",
Self::CITATION => "CITATION",
Self::DEFAULT => "DEFAULT",
Self::PARAGRAPH => "PARAGRAPH",
}
}
}
impl fmt::Display for SyntaxKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.name())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SyntaxNode {
kind: SyntaxKind,
range: TextRange,
children: Vec<SyntaxElement>,
}
impl SyntaxNode {
pub fn new(kind: SyntaxKind, range: TextRange, children: Vec<SyntaxElement>) -> Self {
Self { kind, range, children }
}
pub fn kind(&self) -> SyntaxKind {
self.kind
}
pub fn range(&self) -> TextRange {
self.range
}
pub fn children(&self) -> &[SyntaxElement] {
&self.children
}
pub(crate) fn children_mut(&mut self) -> &mut [SyntaxElement] {
&mut self.children
}
pub(crate) fn push_child(&mut self, child: SyntaxElement) {
self.children.push(child);
}
pub(crate) fn take_children(&mut self) -> Vec<SyntaxElement> {
core::mem::take(&mut self.children)
}
pub(crate) fn set_children(&mut self, children: Vec<SyntaxElement>) {
self.children = children;
}
pub(crate) fn extend_range_to(&mut self, end: crate::text::TextSize) {
self.range = TextRange::new(self.range.start(), end);
}
pub fn find_token(&self, kind: SyntaxKind) -> Option<&SyntaxToken> {
self.children.iter().find_map(|c| match c {
SyntaxElement::Token(t) if t.kind() == kind && !t.is_missing() => Some(t),
_ => None,
})
}
pub fn find_missing(&self, kind: SyntaxKind) -> Option<&SyntaxToken> {
self.children.iter().find_map(|c| match c {
SyntaxElement::Token(t) if t.kind() == kind && t.is_missing() => Some(t),
_ => None,
})
}
pub fn required_token(&self, kind: SyntaxKind) -> &SyntaxToken {
self.children
.iter()
.find_map(|c| match c {
SyntaxElement::Token(t) if t.kind() == kind => Some(t),
_ => None,
})
.unwrap_or_else(|| panic!("required token {:?} not found in {:?}", kind, self.kind))
}
pub fn tokens(&self, kind: SyntaxKind) -> impl Iterator<Item = &SyntaxToken> {
self.children.iter().filter_map(move |c| match c {
SyntaxElement::Token(t) if t.kind() == kind && !t.is_missing() => Some(t),
_ => None,
})
}
pub fn find_node(&self, kind: SyntaxKind) -> Option<&SyntaxNode> {
self.children.iter().find_map(|c| match c {
SyntaxElement::Node(n) if n.kind() == kind => Some(n),
_ => None,
})
}
pub fn nodes(&self, kind: SyntaxKind) -> impl Iterator<Item = &SyntaxNode> {
self.children.iter().filter_map(move |c| match c {
SyntaxElement::Node(n) if n.kind() == kind => Some(n),
_ => None,
})
}
pub fn pretty_fmt(&self, src: &str, indent: usize, out: &mut String) {
for _ in 0..indent {
out.push_str(" ");
}
let _ = writeln!(out, "{}@{} {{", self.kind.name(), self.range);
for child in &self.children {
match child {
SyntaxElement::Node(n) => n.pretty_fmt(src, indent + 1, out),
SyntaxElement::Token(t) => t.pretty_fmt(src, indent + 1, out),
}
}
for _ in 0..indent {
out.push_str(" ");
}
out.push_str("}\n");
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SyntaxToken {
kind: SyntaxKind,
range: TextRange,
}
impl SyntaxToken {
pub fn new(kind: SyntaxKind, range: TextRange) -> Self {
Self { kind, range }
}
pub fn kind(&self) -> SyntaxKind {
self.kind
}
pub fn range(&self) -> TextRange {
self.range
}
pub fn is_missing(&self) -> bool {
self.range.is_empty()
}
pub fn text<'a>(&self, source: &'a str) -> &'a str {
self.range.source_text(source)
}
pub fn pretty_fmt(&self, src: &str, indent: usize, out: &mut String) {
for _ in 0..indent {
out.push_str(" ");
}
if self.is_missing() {
let _ = writeln!(out, "{}: <missing>@{}", self.kind.name(), self.range);
} else {
let _ = writeln!(out, "{}: {:?}@{}", self.kind.name(), self.text(src), self.range);
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SyntaxElement {
Node(SyntaxNode),
Token(SyntaxToken),
}
impl SyntaxElement {
pub fn range(&self) -> TextRange {
match self {
Self::Node(n) => n.range(),
Self::Token(t) => t.range(),
}
}
pub fn kind(&self) -> SyntaxKind {
match self {
Self::Node(n) => n.kind(),
Self::Token(t) => t.kind(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Parsed {
source: String,
root: SyntaxNode,
style: Style,
line_index: LineIndex,
}
impl Parsed {
pub fn new(source: String, root: SyntaxNode, style: Style) -> Self {
let line_index = LineIndex::new(&source);
Self {
source,
root,
style,
line_index,
}
}
pub fn source(&self) -> &str {
&self.source
}
pub fn root(&self) -> &SyntaxNode {
&self.root
}
pub fn style(&self) -> Style {
self.style
}
pub fn line_col(&self, offset: crate::text::TextSize) -> LineColumn {
self.line_index.line_col(offset)
}
pub fn pretty_print(&self) -> String {
let mut out = String::new();
self.root.pretty_fmt(&self.source, 0, &mut out);
out
}
}
pub trait Visitor {
fn enter(&mut self, _node: &SyntaxNode) {}
fn leave(&mut self, _node: &SyntaxNode) {}
fn visit_token(&mut self, _token: &SyntaxToken) {}
}
pub fn walk_tree(node: &SyntaxNode, visitor: &mut dyn Visitor) {
visitor.enter(node);
for child in node.children() {
match child {
SyntaxElement::Node(n) => walk_tree(n, visitor),
SyntaxElement::Token(t) => visitor.visit_token(t),
}
}
visitor.leave(node);
}
#[cfg(test)]
mod tests {
use super::*;
use crate::text::TextRange;
use crate::text::TextSize;
#[test]
fn test_syntax_kind_name() {
assert_eq!(SyntaxKind::ENTRY.name(), "ENTRY");
assert_eq!(SyntaxKind::NAME.name(), "NAME");
assert_eq!(SyntaxKind::SECTION.name(), "SECTION");
}
#[test]
fn test_syntax_kind_is_node_is_token() {
assert!(SyntaxKind::DOCUMENT.is_node());
assert!(!SyntaxKind::DOCUMENT.is_token());
assert!(SyntaxKind::NAME.is_token());
assert!(!SyntaxKind::NAME.is_node());
}
#[test]
fn test_syntax_token_text() {
let source = "hello world";
let token = SyntaxToken::new(SyntaxKind::NAME, TextRange::new(TextSize::new(0), TextSize::new(5)));
assert_eq!(token.text(source), "hello");
}
#[test]
fn test_syntax_node_find_token() {
let node = SyntaxNode::new(
SyntaxKind::ENTRY,
TextRange::new(TextSize::new(0), TextSize::new(10)),
vec![
SyntaxElement::Token(SyntaxToken::new(
SyntaxKind::NAME,
TextRange::new(TextSize::new(0), TextSize::new(3)),
)),
SyntaxElement::Token(SyntaxToken::new(
SyntaxKind::COLON,
TextRange::new(TextSize::new(3), TextSize::new(4)),
)),
SyntaxElement::Token(SyntaxToken::new(
SyntaxKind::TEXT_LINE,
TextRange::new(TextSize::new(5), TextSize::new(10)),
)),
],
);
assert!(node.find_token(SyntaxKind::NAME).is_some());
assert!(node.find_token(SyntaxKind::COLON).is_some());
assert!(node.find_token(SyntaxKind::TYPE).is_none());
assert_eq!(node.tokens(SyntaxKind::NAME).count(), 1);
}
#[test]
fn test_syntax_node_find_node() {
let child = SyntaxNode::new(
SyntaxKind::SECTION_HEADER,
TextRange::new(TextSize::new(0), TextSize::new(5)),
vec![SyntaxElement::Token(SyntaxToken::new(
SyntaxKind::NAME,
TextRange::new(TextSize::new(0), TextSize::new(4)),
))],
);
let parent = SyntaxNode::new(
SyntaxKind::SECTION,
TextRange::new(TextSize::new(0), TextSize::new(20)),
vec![SyntaxElement::Node(child)],
);
assert!(parent.find_node(SyntaxKind::SECTION_HEADER).is_some());
assert!(parent.find_node(SyntaxKind::ENTRY).is_none());
assert_eq!(parent.nodes(SyntaxKind::SECTION_HEADER).count(), 1);
}
#[test]
fn test_pretty_print() {
let source = "Args:\n x: int";
let root = SyntaxNode::new(
SyntaxKind::DOCUMENT,
TextRange::new(TextSize::new(0), TextSize::new(source.len() as u32)),
vec![SyntaxElement::Node(SyntaxNode::new(
SyntaxKind::SECTION,
TextRange::new(TextSize::new(0), TextSize::new(source.len() as u32)),
vec![
SyntaxElement::Node(SyntaxNode::new(
SyntaxKind::SECTION_HEADER,
TextRange::new(TextSize::new(0), TextSize::new(5)),
vec![
SyntaxElement::Token(SyntaxToken::new(
SyntaxKind::NAME,
TextRange::new(TextSize::new(0), TextSize::new(4)),
)),
SyntaxElement::Token(SyntaxToken::new(
SyntaxKind::COLON,
TextRange::new(TextSize::new(4), TextSize::new(5)),
)),
],
)),
SyntaxElement::Node(SyntaxNode::new(
SyntaxKind::ENTRY,
TextRange::new(TextSize::new(10), TextSize::new(source.len() as u32)),
vec![
SyntaxElement::Token(SyntaxToken::new(
SyntaxKind::NAME,
TextRange::new(TextSize::new(10), TextSize::new(11)),
)),
SyntaxElement::Token(SyntaxToken::new(
SyntaxKind::COLON,
TextRange::new(TextSize::new(11), TextSize::new(12)),
)),
SyntaxElement::Token(SyntaxToken::new(
SyntaxKind::TEXT_LINE,
TextRange::new(TextSize::new(13), TextSize::new(source.len() as u32)),
)),
],
)),
],
))],
);
let parsed = Parsed::new(source.to_string(), root, crate::parse::Style::Google);
let output = parsed.pretty_print();
assert!(output.contains("DOCUMENT@"));
assert!(output.contains("SECTION@"));
assert!(output.contains("SECTION_HEADER@"));
assert!(output.contains("ENTRY@"));
assert!(output.contains("NAME: \"Args\"@"));
assert!(output.contains("COLON: \":\"@"));
assert!(output.contains("NAME: \"x\"@"));
assert!(output.contains("TEXT_LINE: \"int\"@"));
}
#[test]
fn test_pretty_print_missing_placeholder() {
let source = "a ()";
let mut out = String::new();
SyntaxToken::new(SyntaxKind::TYPE, TextRange::new(TextSize::new(3), TextSize::new(3)))
.pretty_fmt(source, 0, &mut out);
assert_eq!(out, "TYPE: <missing>@3..3\n");
}
#[test]
fn test_token_accessors_partition_present_from_missing() {
let present = SyntaxToken::new(SyntaxKind::NAME, TextRange::new(TextSize::new(0), TextSize::new(1)));
let missing = SyntaxToken::new(SyntaxKind::TYPE, TextRange::new(TextSize::new(3), TextSize::new(3)));
let node = SyntaxNode::new(
SyntaxKind::ENTRY,
TextRange::new(TextSize::new(0), TextSize::new(4)),
vec![SyntaxElement::Token(present), SyntaxElement::Token(missing)],
);
assert!(node.find_token(SyntaxKind::TYPE).is_none());
assert_eq!(node.tokens(SyntaxKind::TYPE).count(), 0);
assert!(node.find_missing(SyntaxKind::TYPE).is_some());
assert!(node.find_token(SyntaxKind::NAME).is_some());
assert_eq!(node.tokens(SyntaxKind::NAME).count(), 1);
assert!(node.find_missing(SyntaxKind::NAME).is_none());
}
#[test]
fn test_visitor_walk() {
let source = "hello";
let root = SyntaxNode::new(
SyntaxKind::DOCUMENT,
TextRange::new(TextSize::new(0), TextSize::new(5)),
vec![SyntaxElement::Token(SyntaxToken::new(
SyntaxKind::TEXT_LINE,
TextRange::new(TextSize::new(0), TextSize::new(5)),
))],
);
struct Counter {
nodes: usize,
tokens: usize,
}
impl Visitor for Counter {
fn enter(&mut self, _node: &SyntaxNode) {
self.nodes += 1;
}
fn visit_token(&mut self, _token: &SyntaxToken) {
self.tokens += 1;
}
}
let mut counter = Counter { nodes: 0, tokens: 0 };
walk_tree(&root, &mut counter);
assert_eq!(counter.nodes, 1);
assert_eq!(counter.tokens, 1);
let tok = root.required_token(SyntaxKind::TEXT_LINE);
assert_eq!(tok.text(source), "hello");
}
}