use std::sync::Arc;
pub trait GrammarCassette: Send + Sync {
fn external_token_default(&self, token_name: &str) -> Option<&str>;
fn separator_is_line_break(&self, separator_text: &str) -> bool {
let _ = separator_text;
false
}
fn kind_is_tight_content(&self, kind: &str) -> bool {
let _ = kind;
false
}
fn operator_is_tight(&self, rule: &str, token: &str) -> bool {
let _ = (rule, token);
false
}
fn external_is_newline(&self, token_name: &str) -> bool {
let _ = token_name;
false
}
fn external_leads_no_space(&self, token_name: &str) -> bool {
let _ = token_name;
false
}
}
#[must_use]
pub fn resolve_external_token<'a>(
cassette: &'a dyn GrammarCassette,
token_name: &'a str,
) -> Option<&'a str> {
if let Some(v) = cassette.external_token_default(token_name) {
return Some(v);
}
common_external_default(token_name)
}
#[must_use]
#[allow(clippy::too_many_lines)]
pub fn common_external_default(token_name: &str) -> Option<&'static str> {
if matches!(
token_name,
"_newline"
| "_line_break"
| "_newline_before_do"
| "_newline_before_binary_operator"
| "_newline_before_comment"
| "_newline_inline"
| "_newline_not_aligned"
) {
return Some("\n");
}
if matches!(
token_name,
"_concat"
| "_brace_concat"
| "_concat_list"
| "_no_space"
| "_begin_brace"
| "_brace_start"
| "_bare_dollar"
| "_no_line_break"
| "_empty_value"
| "_eof"
| "_eof_or_newline"
| "_after_eof"
| "_end_of_file"
| "_ignored"
| "_non_whitespace_check"
| "_in_fallback"
| "_error"
| "_error_sentinel"
| "_error_recovery"
| "__error_recovery"
| "error_sentinel"
| "_failure"
| "_automatic_semicolon"
| "_function_signature_automatic_semicolon"
| "_optional_semi"
| "_string_content"
| "string_content"
| "_template_chars"
| "raw_string_content"
| "_quoted_content"
| "_raw_str_content"
| "_multi_str_content"
| "_multi_raw_str_content"
| "_block_comment_content"
| "_documentation_block_comment"
| "_block_comment"
| "block_comment"
| "multiline_comment"
| "comment"
| "html_comment"
| "heredoc_start"
| "heredoc_end"
| "heredoc_content"
| "heredoc_nl"
| "heredoc_line"
| "heredoc_marker"
| "simple_heredoc_body"
| "_heredoc_body_beginning"
| "_heredoc_body_start"
| "raw_string_delimiter"
| "raw_string_start"
| "raw_string_end"
| "escape_interpolation"
| "escape_sequence"
| "regex_pattern"
| "regex_modifier"
| "raw_text"
| "jsx_text"
) {
return Some("");
}
match token_name {
"string_start" | "string_end" | "_string_start" | "_string_end" => Some("\""),
"not_in" => Some("not in"),
"not_is" => Some("not is"),
"_ternary_qmark" => Some("?"),
"_descendant_operator" => Some(" "),
"_regex_start" => Some("/"),
_ => {
if token_name.starts_with("_immediate_") {
return Some("");
}
if token_name.starts_with("_quoted_content_") {
return Some("");
}
if token_name.starts_with("_external_expansion_sym_") {
return Some("");
}
if token_name.starts_with("_virtual_") {
return Some("");
}
if token_name.starts_with("_layout_") {
return Some("");
}
if token_name.starts_with("_multi_") {
return Some("");
}
if token_name.starts_with("_tq_") {
return Some("");
}
None
}
}
}
struct DefaultCassette;
impl GrammarCassette for DefaultCassette {
fn external_token_default(&self, _token_name: &str) -> Option<&str> {
None
}
}
struct PythonCassette;
impl GrammarCassette for PythonCassette {
fn external_token_default(&self, _token_name: &str) -> Option<&str> {
None
}
fn separator_is_line_break(&self, separator_text: &str) -> bool {
separator_text == ";"
}
}
struct JuliaCassette;
impl GrammarCassette for JuliaCassette {
fn external_token_default(&self, token_name: &str) -> Option<&str> {
match token_name {
"_end_str" | "_immediate_string_start" => Some("\""),
"_end_cmd" | "_immediate_command_start" => Some("`"),
"_immediate_paren" | "_immediate_bracket" | "_immediate_brace" => Some(""),
_ => None,
}
}
}
struct RubyCassette;
impl GrammarCassette for RubyCassette {
fn external_token_default(&self, token_name: &str) -> Option<&str> {
match token_name {
"_line_break" => Some("\n"),
"_no_line_break" => Some(""),
_ => None,
}
}
}
struct OcamlCassette;
impl GrammarCassette for OcamlCassette {
fn external_token_default(&self, token_name: &str) -> Option<&str> {
match token_name {
"_quoted_string_start" => Some("{|"),
"_quoted_string_end" => Some("|}"),
_ => None,
}
}
}
struct HtmlFamilyCassette;
impl GrammarCassette for HtmlFamilyCassette {
fn external_token_default(&self, token_name: &str) -> Option<&str> {
match token_name {
"_start_tag_name"
| "_end_tag_name"
| "_script_start_tag_name"
| "_style_start_tag_name"
| "erroneous_end_tag_name"
| "_implicit_end_tag" => Some(""),
"_interpolation_start" | "_html_interpolation_start" => Some("{{"),
"_interpolation_end" | "_html_interpolation_end" => Some("}}"),
_ => None,
}
}
fn kind_is_tight_content(&self, kind: &str) -> bool {
matches!(kind, "text" | "raw_text")
}
}
struct RCassette;
impl GrammarCassette for RCassette {
fn external_token_default(&self, _token_name: &str) -> Option<&str> {
None
}
fn operator_is_tight(&self, rule: &str, token: &str) -> bool {
match rule {
"namespace_operator" => matches!(token, "::" | ":::"),
"extract_operator" => matches!(token, "$" | "@"),
"binary_operator" => token == ":",
"unary_operator" => matches!(token, "!" | "-" | "+" | "~" | "?"),
_ => false,
}
}
}
struct ShellFamilyCassette;
impl GrammarCassette for ShellFamilyCassette {
fn external_token_default(&self, token_name: &str) -> Option<&str> {
match token_name {
"file_descriptor" | "variable_name" | "test_operator" | "regex" | "_regex_no_slash"
| "_regex_no_space" | "_expansion_word" | "extglob_pattern" => Some(""),
"_immediate_double_hash" => Some("##"),
_ => None,
}
}
fn operator_is_tight(&self, rule: &str, token: &str) -> bool {
rule == "variable_assignment" && matches!(token, "=" | "+=")
}
}
struct CFamilyCassette;
impl GrammarCassette for CFamilyCassette {
fn external_token_default(&self, _token_name: &str) -> Option<&str> {
None }
}
struct CSharpCassette;
impl GrammarCassette for CSharpCassette {
fn external_token_default(&self, _token_name: &str) -> Option<&str> {
None }
fn external_leads_no_space(&self, token_name: &str) -> bool {
matches!(
token_name,
"interpolation_start_quote"
| "interpolation_open_brace"
| "interpolation_close_brace"
| "interpolation_end_quote"
)
}
fn kind_is_tight_content(&self, kind: &str) -> bool {
matches!(
kind,
"string_content" | "interpolation_brace" | "interpolation_quote" | "raw_string_content"
)
}
}
struct JsFamilyCassette;
impl GrammarCassette for JsFamilyCassette {
fn external_token_default(&self, _token_name: &str) -> Option<&str> {
None }
}
struct IndentBasedCassette;
impl GrammarCassette for IndentBasedCassette {
fn external_token_default(&self, token_name: &str) -> Option<&str> {
match token_name {
"_dot" => Some("."),
_ => None,
}
}
}
struct ElmCassette;
impl GrammarCassette for ElmCassette {
fn external_token_default(&self, _token_name: &str) -> Option<&str> {
None
}
fn external_is_newline(&self, token_name: &str) -> bool {
matches!(token_name, "_virtual_end_decl" | "_virtual_end_section")
}
}
struct KotlinCassette;
impl GrammarCassette for KotlinCassette {
fn external_token_default(&self, token_name: &str) -> Option<&str> {
match token_name {
"_primary_constructor_keyword" => Some("constructor"),
_ => None,
}
}
}
#[must_use]
pub fn cassette_for(protocol: &str) -> Arc<dyn GrammarCassette> {
match protocol {
"python" | "starlark" | "bitbake" => Arc::new(PythonCassette),
"julia" => Arc::new(JuliaCassette),
"ruby" | "crystal" => Arc::new(RubyCassette),
"ocaml" | "ocaml_interface" => Arc::new(OcamlCassette),
"html" | "vue" | "svelte" | "astro" | "blade" | "angular" | "templ" | "heex" => {
Arc::new(HtmlFamilyCassette)
}
"bash" | "zsh" | "fish" => Arc::new(ShellFamilyCassette),
"r" => Arc::new(RCassette),
"csharp" => Arc::new(CSharpCassette),
"cpp" | "cuda" | "hlsl" | "arduino" | "c" => Arc::new(CFamilyCassette),
"javascript" | "typescript" | "tsx" | "qml" | "rescript" => Arc::new(JsFamilyCassette),
"elm" => Arc::new(ElmCassette),
"kotlin" => Arc::new(KotlinCassette),
"agda" | "fsharp" | "fsharp_signature" | "earthfile" | "firrtl" | "cooklang" | "djot"
| "idris" | "nim" | "purescript" | "haskell" => Arc::new(IndentBasedCassette),
_ => Arc::new(DefaultCassette),
}
}