use super::red::{SyntaxElement, SyntaxNode, SyntaxToken};
use crate::types::SyntaxKind;
#[derive(Clone, Debug, Default)]
pub struct SexpOptions {
pub include_trivia: bool,
pub kind_to_name: Option<fn(SyntaxKind) -> Option<&'static str>>,
pub max_token_len: Option<usize>,
}
impl SexpOptions {
#[must_use]
pub fn semantic_only() -> Self {
Self {
include_trivia: false,
..Self::default()
}
}
#[must_use]
pub fn full() -> Self {
Self {
include_trivia: true,
..Self::default()
}
}
}
fn escape_str(s: &str, max_len: Option<usize>) -> String {
let s = max_len.map_or_else(
|| s.to_string(),
|max| {
if s.len() > max {
format!("{}...", &s[..max])
} else {
s.to_string()
}
},
);
let mut out = String::with_capacity(s.len() + 2);
out.push('"');
for c in s.chars() {
match c {
'\\' => out.push_str("\\\\"),
'"' => out.push_str("\\\""),
'\n' => out.push_str("\\n"),
'\r' => out.push_str("\\r"),
'\t' => out.push_str("\\t"),
_ => out.push(c),
}
}
out.push('"');
out
}
fn kind_str(kind: SyntaxKind, opts: &SexpOptions) -> String {
if let Some(f) = opts.kind_to_name {
if let Some(name) = f(kind) {
return name.to_string();
}
}
kind.to_string()
}
#[must_use]
pub fn syntax_node_to_sexp(node: &SyntaxNode, opts: &SexpOptions) -> String {
let mut out = String::new();
append_node_to_sexp(node, opts, &mut out);
out
}
fn append_node_to_sexp(node: &SyntaxNode, opts: &SexpOptions, out: &mut String) {
let k = kind_str(node.kind(), opts);
out.push('(');
out.push_str(&k);
for child in node.children() {
match child {
SyntaxElement::Node(n) => {
out.push(' ');
append_node_to_sexp(&n, opts, out);
}
SyntaxElement::Token(t) => {
if t.is_trivia() && !opts.include_trivia {
continue;
}
out.push(' ');
append_token_to_sexp(&t, opts, out);
}
}
}
out.push(')');
}
fn append_token_to_sexp(tok: &SyntaxToken, opts: &SexpOptions, out: &mut String) {
let k = kind_str(tok.kind(), opts);
let text = escape_str(tok.text(), opts.max_token_len);
out.push('(');
out.push_str(&k);
out.push(' ');
out.push_str(&text);
out.push(')');
}