feat(bindings): add Java bindings

This commit is contained in:
ObserverOfTime 2025-08-05 22:40:48 +03:00 committed by Will Lillis
parent 8ca17d1bb1
commit b9c2d1dc89
9 changed files with 369 additions and 22 deletions

View file

@ -36,6 +36,8 @@ const PARSER_CLASS_NAME_PLACEHOLDER: &str = "PARSER_CLASS_NAME";
const PARSER_DESCRIPTION_PLACEHOLDER: &str = "PARSER_DESCRIPTION";
const PARSER_LICENSE_PLACEHOLDER: &str = "PARSER_LICENSE";
const PARSER_NS_PLACEHOLDER: &str = "PARSER_NS";
const PARSER_NS_CLEANED_PLACEHOLDER: &str = "PARSER_NS_CLEANED";
const PARSER_URL_PLACEHOLDER: &str = "PARSER_URL";
const PARSER_URL_STRIPPED_PLACEHOLDER: &str = "PARSER_URL_STRIPPED";
const PARSER_VERSION_PLACEHOLDER: &str = "PARSER_VERSION";
@ -58,6 +60,11 @@ const AUTHOR_BLOCK_RS: &str = "\nauthors = [";
const AUTHOR_NAME_PLACEHOLDER_RS: &str = "PARSER_AUTHOR_NAME";
const AUTHOR_EMAIL_PLACEHOLDER_RS: &str = " PARSER_AUTHOR_EMAIL";
const AUTHOR_BLOCK_JAVA: &str = "\n <developer>";
const AUTHOR_NAME_PLACEHOLDER_JAVA: &str = "\n <name>PARSER_AUTHOR_NAME</name>";
const AUTHOR_EMAIL_PLACEHOLDER_JAVA: &str = "\n <email>PARSER_AUTHOR_EMAIL</email>";
const AUTHOR_URL_PLACEHOLDER_JAVA: &str = "\n <url>PARSER_AUTHOR_URL</url>";
const AUTHOR_BLOCK_GRAMMAR: &str = "\n * @author ";
const AUTHOR_NAME_PLACEHOLDER_GRAMMAR: &str = "PARSER_AUTHOR_NAME";
const AUTHOR_EMAIL_PLACEHOLDER_GRAMMAR: &str = " PARSER_AUTHOR_EMAIL";
@ -107,6 +114,10 @@ const TEST_BINDING_PY_TEMPLATE: &str = include_str!("./templates/test_binding.py
const PACKAGE_SWIFT_TEMPLATE: &str = include_str!("./templates/package.swift");
const TESTS_SWIFT_TEMPLATE: &str = include_str!("./templates/tests.swift");
const POM_XML_TEMPLATE: &str = include_str!("./templates/pom.xml");
const BINDING_JAVA_TEMPLATE: &str = include_str!("./templates/binding.java");
const TEST_JAVA_TEMPLATE: &str = include_str!("./templates/test.java");
const BUILD_ZIG_TEMPLATE: &str = include_str!("./templates/build.zig");
const BUILD_ZIG_ZON_TEMPLATE: &str = include_str!("./templates/build.zig.zon");
const ROOT_ZIG_TEMPLATE: &str = include_str!("./templates/root.zig");
@ -134,6 +145,7 @@ pub struct JsonConfigOpts {
pub email: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub url: Option<String>,
pub namespace: Option<String>,
pub bindings: Bindings,
}
@ -174,7 +186,7 @@ impl JsonConfigOpts {
}),
funding: self.funding,
}),
namespace: None,
namespace: self.namespace,
},
bindings: self.bindings,
}
@ -197,6 +209,7 @@ impl Default for JsonConfigOpts {
author: String::new(),
email: None,
url: None,
namespace: None,
bindings: Bindings::default(),
}
}
@ -218,6 +231,7 @@ struct GenerateOpts<'a> {
injections_query_path: &'a str,
locals_query_path: &'a str,
tags_query_path: &'a str,
namespace: Option<&'a str>,
}
pub fn generate_grammar_files(
@ -311,6 +325,7 @@ pub fn generate_grammar_files(
tags_query_path: tree_sitter_config.grammars[0]
.tags
.to_variable_value(&default_tags_path),
namespace: tree_sitter_config.metadata.namespace.as_deref(),
};
// Create package.json
@ -1048,6 +1063,45 @@ pub fn generate_grammar_files(
})?;
}
// Generate Java bindings
if tree_sitter_config.bindings.java {
missing_path(repo_path.join("pom.xml"), |path| {
generate_file(path, POM_XML_TEMPLATE, language_name, &generate_opts)
})?;
missing_path(bindings_dir.join("java"), create_dir)?.apply(|path| {
missing_path(path.join("main"), create_dir)?.apply(|path| {
let package_path = generate_opts
.namespace
.unwrap_or("io.github.treesitter")
.replace(['-', '_'], "")
.split('.')
.fold(path.to_path_buf(), |path, dir| path.join(dir))
.join("jtreesitter")
.join(language_name.to_lowercase().replace('_', ""));
missing_path(package_path, create_dir)?.apply(|path| {
missing_path(path.join(format!("{class_name}.java")), |path| {
generate_file(path, BINDING_JAVA_TEMPLATE, language_name, &generate_opts)
})?;
Ok(())
})?;
Ok(())
})?;
missing_path(path.join("test"), create_dir)?.apply(|path| {
missing_path(path.join(format!("{class_name}Test.java")), |path| {
generate_file(path, TEST_JAVA_TEMPLATE, language_name, &generate_opts)
})?;
Ok(())
})?;
Ok(())
})?;
}
Ok(())
}
@ -1097,6 +1151,15 @@ fn generate_file(
) -> Result<()> {
let filename = path.file_name().unwrap().to_str().unwrap();
let lower_parser_name = if path
.extension()
.is_some_and(|e| e.eq_ignore_ascii_case("java"))
{
language_name.to_snake_case().replace('_', "")
} else {
language_name.to_snake_case()
};
let mut replacement = template
.replace(
CAMEL_PARSER_NAME_PLACEHOLDER,
@ -1110,14 +1173,11 @@ fn generate_file(
UPPER_PARSER_NAME_PLACEHOLDER,
&language_name.to_shouty_snake_case(),
)
.replace(
LOWER_PARSER_NAME_PLACEHOLDER,
&language_name.to_snake_case(),
)
.replace(
KEBAB_PARSER_NAME_PLACEHOLDER,
&language_name.to_kebab_case(),
)
.replace(LOWER_PARSER_NAME_PLACEHOLDER, &lower_parser_name)
.replace(PARSER_NAME_PLACEHOLDER, language_name)
.replace(CLI_VERSION_PLACEHOLDER, CLI_VERSION)
.replace(RUST_BINDING_VERSION_PLACEHOLDER, RUST_BINDING_VERSION)
@ -1157,6 +1217,9 @@ fn generate_file(
"Cargo.toml" => {
replacement = replacement.replace(AUTHOR_NAME_PLACEHOLDER_RS, "");
}
"pom.xml" => {
replacement = replacement.replace(AUTHOR_NAME_PLACEHOLDER_JAVA, "");
}
_ => {}
}
}
@ -1182,30 +1245,52 @@ fn generate_file(
"Cargo.toml" => {
replacement = replacement.replace(AUTHOR_EMAIL_PLACEHOLDER_RS, "");
}
"pom.xml" => {
replacement = replacement.replace(AUTHOR_EMAIL_PLACEHOLDER_JAVA, "");
}
_ => {}
}
}
if filename == "package.json" {
if let Some(url) = generate_opts.author_url {
match (generate_opts.author_url, filename) {
(Some(url), "package.json" | "pom.xml") => {
replacement = replacement.replace(AUTHOR_URL_PLACEHOLDER, url);
} else {
}
(None, "package.json") => {
replacement = replacement.replace(AUTHOR_URL_PLACEHOLDER_JS, "");
}
(None, "pom.xml") => {
replacement = replacement.replace(AUTHOR_URL_PLACEHOLDER_JAVA, "");
}
_ => {}
}
if generate_opts.author_name.is_none()
&& generate_opts.author_email.is_none()
&& generate_opts.author_url.is_none()
&& filename == "package.json"
{
if let Some(start_idx) = replacement.find(AUTHOR_BLOCK_JS) {
if let Some(end_idx) = replacement[start_idx..]
.find("},")
.map(|i| i + start_idx + 2)
{
replacement.replace_range(start_idx..end_idx, "");
match filename {
"package.json" => {
if let Some(start_idx) = replacement.find(AUTHOR_BLOCK_JS) {
if let Some(end_idx) = replacement[start_idx..]
.find("},")
.map(|i| i + start_idx + 2)
{
replacement.replace_range(start_idx..end_idx, "");
}
}
}
"pom.xml" => {
if let Some(start_idx) = replacement.find(AUTHOR_BLOCK_JAVA) {
if let Some(end_idx) = replacement[start_idx..]
.find("</developer>")
.map(|i| i + start_idx + 12)
{
replacement.replace_range(start_idx..end_idx, "");
}
}
}
_ => {}
}
} else if generate_opts.author_name.is_none() && generate_opts.author_email.is_none() {
match filename {
@ -1286,6 +1371,19 @@ fn generate_file(
);
}
if let Some(namespace) = generate_opts.namespace {
replacement = replacement
.replace(
PARSER_NS_CLEANED_PLACEHOLDER,
&namespace.replace(['-', '_'], ""),
)
.replace(PARSER_NS_PLACEHOLDER, namespace);
} else {
replacement = replacement
.replace(PARSER_NS_CLEANED_PLACEHOLDER, "io.github.treesitter")
.replace(PARSER_NS_PLACEHOLDER, "io.github.tree-sitter");
}
if let Some(funding_url) = generate_opts.funding {
match filename {
"pyproject.toml" | "package.json" => {