use core::fmt;
use core::fmt::Write;
use crate::text::{LineColumn, LineIndex, TextRange};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[allow(non_camel_case_types)]
pub enum SyntaxKind {
NAME,
TYPE,
COLON,
DESCRIPTION,
OPEN_BRACKET,
CLOSE_BRACKET,
OPTIONAL,
BODY_TEXT,
SUMMARY,
EXTENDED_SUMMARY,
STRAY_LINE,
WARNING_TYPE,
UNDERLINE,
DIRECTIVE_MARKER,
KEYWORD,
DOUBLE_COLON,
VERSION,
RETURN_TYPE,
DEFAULT_KEYWORD,
DEFAULT_SEPARATOR,
DEFAULT_VALUE,
NUMBER,
CONTENT,
GOOGLE_DOCSTRING,
GOOGLE_SECTION,
GOOGLE_SECTION_HEADER,
GOOGLE_ARG,
GOOGLE_RETURNS,
GOOGLE_YIELDS,
GOOGLE_EXCEPTION,
GOOGLE_WARNING,
GOOGLE_SEE_ALSO_ITEM,
GOOGLE_ATTRIBUTE,
GOOGLE_METHOD,
NUMPY_DOCSTRING,
NUMPY_SECTION,
NUMPY_SECTION_HEADER,
NUMPY_DEPRECATION,
NUMPY_PARAMETER,
NUMPY_RETURNS,
NUMPY_YIELDS,
NUMPY_EXCEPTION,
NUMPY_WARNING,
NUMPY_SEE_ALSO_ITEM,
NUMPY_REFERENCE,
NUMPY_ATTRIBUTE,
NUMPY_METHOD,
PLAIN_DOCSTRING,
}
impl SyntaxKind {
pub const fn is_node(self) -> bool {
matches!(
self,
Self::PLAIN_DOCSTRING
| Self::GOOGLE_DOCSTRING
| Self::GOOGLE_SECTION
| Self::GOOGLE_SECTION_HEADER
| Self::GOOGLE_ARG
| Self::GOOGLE_RETURNS
| Self::GOOGLE_YIELDS
| Self::GOOGLE_EXCEPTION
| Self::GOOGLE_WARNING
| Self::GOOGLE_SEE_ALSO_ITEM
| Self::GOOGLE_ATTRIBUTE
| Self::GOOGLE_METHOD
| Self::NUMPY_DOCSTRING
| Self::NUMPY_SECTION
| Self::NUMPY_SECTION_HEADER
| Self::NUMPY_DEPRECATION
| Self::NUMPY_PARAMETER
| Self::NUMPY_RETURNS
| Self::NUMPY_YIELDS
| Self::NUMPY_EXCEPTION
| Self::NUMPY_WARNING
| Self::NUMPY_SEE_ALSO_ITEM
| Self::NUMPY_REFERENCE
| Self::NUMPY_ATTRIBUTE
| Self::NUMPY_METHOD
)
}
pub const fn is_token(self) -> bool {
!self.is_node()
}
pub const fn name(self) -> &'static str {
match self {
Self::NAME => "NAME",
Self::TYPE => "TYPE",
Self::COLON => "COLON",
Self::DESCRIPTION => "DESCRIPTION",
Self::OPEN_BRACKET => "OPEN_BRACKET",
Self::CLOSE_BRACKET => "CLOSE_BRACKET",
Self::OPTIONAL => "OPTIONAL",
Self::BODY_TEXT => "BODY_TEXT",
Self::SUMMARY => "SUMMARY",
Self::EXTENDED_SUMMARY => "EXTENDED_SUMMARY",
Self::STRAY_LINE => "STRAY_LINE",
Self::WARNING_TYPE => "WARNING_TYPE",
Self::UNDERLINE => "UNDERLINE",
Self::DIRECTIVE_MARKER => "DIRECTIVE_MARKER",
Self::KEYWORD => "KEYWORD",
Self::DOUBLE_COLON => "DOUBLE_COLON",
Self::VERSION => "VERSION",
Self::RETURN_TYPE => "RETURN_TYPE",
Self::DEFAULT_KEYWORD => "DEFAULT_KEYWORD",
Self::DEFAULT_SEPARATOR => "DEFAULT_SEPARATOR",
Self::DEFAULT_VALUE => "DEFAULT_VALUE",
Self::NUMBER => "NUMBER",
Self::CONTENT => "CONTENT",
Self::GOOGLE_DOCSTRING => "GOOGLE_DOCSTRING",
Self::GOOGLE_SECTION => "GOOGLE_SECTION",
Self::GOOGLE_SECTION_HEADER => "GOOGLE_SECTION_HEADER",
Self::GOOGLE_ARG => "GOOGLE_ARG",
Self::GOOGLE_RETURNS => "GOOGLE_RETURNS",
Self::GOOGLE_YIELDS => "GOOGLE_YIELDS",
Self::GOOGLE_EXCEPTION => "GOOGLE_EXCEPTION",
Self::GOOGLE_WARNING => "GOOGLE_WARNING",
Self::GOOGLE_SEE_ALSO_ITEM => "GOOGLE_SEE_ALSO_ITEM",
Self::GOOGLE_ATTRIBUTE => "GOOGLE_ATTRIBUTE",
Self::GOOGLE_METHOD => "GOOGLE_METHOD",
Self::PLAIN_DOCSTRING => "PLAIN_DOCSTRING",
Self::NUMPY_DOCSTRING => "NUMPY_DOCSTRING",
Self::NUMPY_SECTION => "NUMPY_SECTION",
Self::NUMPY_SECTION_HEADER => "NUMPY_SECTION_HEADER",
Self::NUMPY_DEPRECATION => "NUMPY_DEPRECATION",
Self::NUMPY_PARAMETER => "NUMPY_PARAMETER",
Self::NUMPY_RETURNS => "NUMPY_RETURNS",
Self::NUMPY_YIELDS => "NUMPY_YIELDS",
Self::NUMPY_EXCEPTION => "NUMPY_EXCEPTION",
Self::NUMPY_WARNING => "NUMPY_WARNING",
Self::NUMPY_SEE_ALSO_ITEM => "NUMPY_SEE_ALSO_ITEM",
Self::NUMPY_REFERENCE => "NUMPY_REFERENCE",
Self::NUMPY_ATTRIBUTE => "NUMPY_ATTRIBUTE",
Self::NUMPY_METHOD => "NUMPY_METHOD",
}
}
}
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 fn children_mut(&mut self) -> &mut [SyntaxElement] {
&mut self.children
}
pub fn push_child(&mut self, child: SyntaxElement) {
self.children.push(child);
}
pub 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 => 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 extend_range(&mut self, other: TextRange) {
self.range.extend(other);
}
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.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,
line_index: LineIndex,
}
impl Parsed {
pub fn new(source: String, root: SyntaxNode) -> Self {
let line_index = LineIndex::new(&source);
Self {
source,
root,
line_index,
}
}
pub fn source(&self) -> &str {
&self.source
}
pub fn root(&self) -> &SyntaxNode {
&self.root
}
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(node: &SyntaxNode, visitor: &mut dyn Visitor) {
visitor.enter(node);
for child in node.children() {
match child {
SyntaxElement::Node(n) => walk(n, visitor),
SyntaxElement::Token(t) => visitor.visit_token(t),
}
}
visitor.leave(node);
}
#[cfg(test)]
mod tests {
use super::*;
use crate::text::{TextRange, TextSize};
#[test]
fn test_syntax_kind_name() {
assert_eq!(SyntaxKind::GOOGLE_ARG.name(), "GOOGLE_ARG");
assert_eq!(SyntaxKind::NAME.name(), "NAME");
assert_eq!(SyntaxKind::NUMPY_PARAMETER.name(), "NUMPY_PARAMETER");
}
#[test]
fn test_syntax_kind_is_node_is_token() {
assert!(SyntaxKind::GOOGLE_DOCSTRING.is_node());
assert!(!SyntaxKind::GOOGLE_DOCSTRING.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::GOOGLE_ARG,
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::DESCRIPTION,
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::GOOGLE_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::GOOGLE_SECTION,
TextRange::new(TextSize::new(0), TextSize::new(20)),
vec![SyntaxElement::Node(child)],
);
assert!(parent.find_node(SyntaxKind::GOOGLE_SECTION_HEADER).is_some());
assert!(parent.find_node(SyntaxKind::GOOGLE_ARG).is_none());
assert_eq!(parent.nodes(SyntaxKind::GOOGLE_SECTION_HEADER).count(), 1);
}
#[test]
fn test_pretty_print() {
let source = "Args:\n x: int";
let root = SyntaxNode::new(
SyntaxKind::GOOGLE_DOCSTRING,
TextRange::new(TextSize::new(0), TextSize::new(source.len() as u32)),
vec![SyntaxElement::Node(SyntaxNode::new(
SyntaxKind::GOOGLE_SECTION,
TextRange::new(TextSize::new(0), TextSize::new(source.len() as u32)),
vec![
SyntaxElement::Node(SyntaxNode::new(
SyntaxKind::GOOGLE_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::GOOGLE_ARG,
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::DESCRIPTION,
TextRange::new(TextSize::new(13), TextSize::new(source.len() as u32)),
)),
],
)),
],
))],
);
let parsed = Parsed::new(source.to_string(), root);
let output = parsed.pretty_print();
assert!(output.contains("GOOGLE_DOCSTRING@"));
assert!(output.contains("GOOGLE_SECTION@"));
assert!(output.contains("GOOGLE_SECTION_HEADER@"));
assert!(output.contains("GOOGLE_ARG@"));
assert!(output.contains("NAME: \"Args\"@"));
assert!(output.contains("COLON: \":\"@"));
assert!(output.contains("NAME: \"x\"@"));
assert!(output.contains("DESCRIPTION: \"int\"@"));
}
#[test]
fn test_visitor_walk() {
let source = "hello";
let root = SyntaxNode::new(
SyntaxKind::GOOGLE_DOCSTRING,
TextRange::new(TextSize::new(0), TextSize::new(5)),
vec![SyntaxElement::Token(SyntaxToken::new(
SyntaxKind::SUMMARY,
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(&root, &mut counter);
assert_eq!(counter.nodes, 1);
assert_eq!(counter.tokens, 1);
let tok = root.required_token(SyntaxKind::SUMMARY);
assert_eq!(tok.text(source), "hello");
}
}