#[cfg(feature = "static-grammar-libs")]
use anyhow::bail;
#[cfg(feature = "static-grammar-libs")]
use thiserror::Error;
#[cfg(feature = "static-grammar-libs")]
use cargo_emit::{rerun_if_changed, rerun_if_env_changed};
#[cfg(feature = "static-grammar-libs")]
use rayon::prelude::*;
#[cfg(feature = "static-grammar-libs")]
use std::{
env,
fmt::Display,
fs,
path::{Path, PathBuf},
vec,
};
use anyhow::Result;
#[cfg(feature = "static-grammar-libs")]
#[derive(Debug, Default)]
struct GrammarCompileInfo<'a> {
display_name: &'a str,
path: PathBuf,
c_sources: Vec<&'a str>,
cpp_sources: Vec<&'a str>,
}
#[cfg(feature = "static-grammar-libs")]
struct CompileParams {
pub dir: PathBuf,
pub c_sources: Vec<PathBuf>,
pub cpp_sources: Vec<PathBuf>,
pub display_name: String,
}
#[cfg(feature = "static-grammar-libs")]
#[derive(Debug, Error)]
enum CompileParamError {
#[error("Subdirectory for grammar {0} was not found")]
SubdirectoryNotFound(String),
#[error("Source files {source_files:?} not found for {grammar}")]
SourceFilesNotFound {
grammar: String,
source_files: Vec<String>,
},
}
#[cfg(feature = "static-grammar-libs")]
const BUILD_ENV_VARS: &[&str] = &["CC", "CXX", "LD_LIBRARY_PATH", "PATH"];
#[cfg(feature = "static-grammar-libs")]
fn codegen_language_map<T: ToString + Display>(languages: &[T]) -> String {
let body: String = languages
.iter()
.map(|lang| format!("\"{}\" => tree_sitter_{},\n", lang, lang))
.collect();
let map_decl = format!(
"\nstatic LANGUAGES: phf::Map<&'static str, unsafe extern \"C\" fn() -> Language> = phf_map! {{\n {}\n }};\n", body);
map_decl
}
#[cfg(feature = "static-grammar-libs")]
fn compile_grammar(
include: &Path,
c_sources: &[PathBuf],
cpp_sources: &[PathBuf],
output_name: &str,
) -> Result<(), cc::Error> {
if !cpp_sources.is_empty() {
cc::Build::new()
.cpp(true)
.include(include)
.files(cpp_sources)
.warnings(false)
.flag_if_supported("-std=c++14")
.try_compile(&format!("{}-cpp-compile-diffsiter", &output_name))?;
}
if !c_sources.is_empty() {
cc::Build::new()
.include(include)
.files(c_sources)
.warnings(false)
.try_compile(output_name)?;
}
Ok(())
}
#[cfg(feature = "static-grammar-libs")]
fn extra_cargo_directives() {
for &env_var in BUILD_ENV_VARS {
rerun_if_env_changed!(env_var);
}
}
#[cfg(feature = "static-grammar-libs")]
fn preprocess_compile_info(grammar: &GrammarCompileInfo) -> CompileParams {
let dir = grammar.path.join("src");
let c_sources: Vec<_> = grammar
.c_sources
.iter()
.map(|&filename| dir.join(filename))
.collect();
let cpp_sources: Vec<_> = grammar
.cpp_sources
.iter()
.map(|&filename| dir.join(filename))
.collect();
CompileParams {
dir,
c_sources,
cpp_sources,
display_name: grammar.display_name.into(),
}
}
#[cfg(feature = "static-grammar-libs")]
fn verify_compile_params(compile_params: &CompileParams) -> Result<(), CompileParamError> {
if !compile_params.dir.exists() {
return Err(CompileParamError::SubdirectoryNotFound(
compile_params.display_name.to_string(),
));
}
let missing_sources = compile_params
.c_sources
.iter()
.chain(compile_params.cpp_sources.iter())
.filter_map(|file| {
if file.exists() {
None
} else {
Some(file.to_string_lossy().to_string())
}
})
.collect::<Vec<String>>();
if !missing_sources.is_empty() {
return Err(CompileParamError::SourceFilesNotFound {
grammar: compile_params.display_name.to_string(),
source_files: missing_sources,
});
}
Ok(())
}
#[cfg(feature = "static-grammar-libs")]
fn grammars() -> Vec<GrammarCompileInfo<'static>> {
let grammars = vec![
GrammarCompileInfo {
display_name: "rust",
path: PathBuf::from("grammars/tree-sitter-rust"),
c_sources: vec!["parser.c", "scanner.c"],
..GrammarCompileInfo::default()
},
GrammarCompileInfo {
display_name: "cpp",
path: PathBuf::from("grammars/tree-sitter-cpp"),
c_sources: vec!["parser.c"],
cpp_sources: vec!["scanner.cc"],
},
GrammarCompileInfo {
display_name: "python",
path: PathBuf::from("grammars/tree-sitter-python"),
c_sources: vec!["parser.c"],
cpp_sources: vec!["scanner.cc"],
},
GrammarCompileInfo {
display_name: "bash",
path: PathBuf::from("grammars/tree-sitter-bash"),
c_sources: vec!["parser.c"],
cpp_sources: vec!["scanner.cc"],
},
GrammarCompileInfo {
display_name: "ocaml",
path: PathBuf::from("grammars/tree-sitter-ocaml/ocaml"),
c_sources: vec!["parser.c"],
cpp_sources: vec!["scanner.cc"],
},
GrammarCompileInfo {
display_name: "go",
path: PathBuf::from("grammars/tree-sitter-go"),
c_sources: vec!["parser.c"],
..GrammarCompileInfo::default()
},
GrammarCompileInfo {
display_name: "ruby",
path: PathBuf::from("grammars/tree-sitter-ruby"),
c_sources: vec!["parser.c"],
cpp_sources: vec!["scanner.cc"],
},
GrammarCompileInfo {
display_name: "java",
path: PathBuf::from("grammars/tree-sitter-java"),
c_sources: vec!["parser.c"],
..GrammarCompileInfo::default()
},
GrammarCompileInfo {
display_name: "c_sharp",
path: PathBuf::from("grammars/tree-sitter-c-sharp"),
c_sources: vec!["parser.c", "scanner.c"],
..GrammarCompileInfo::default()
},
GrammarCompileInfo {
display_name: "css",
path: PathBuf::from("grammars/tree-sitter-css"),
c_sources: vec!["parser.c", "scanner.c"],
..GrammarCompileInfo::default()
},
GrammarCompileInfo {
display_name: "php",
path: PathBuf::from("grammars/tree-sitter-php"),
c_sources: vec!["parser.c"],
cpp_sources: vec!["scanner.cc"],
},
GrammarCompileInfo {
display_name: "json",
path: PathBuf::from("grammars/tree-sitter-json"),
c_sources: vec!["parser.c"],
..GrammarCompileInfo::default()
},
GrammarCompileInfo {
display_name: "hcl",
path: PathBuf::from("grammars/tree-sitter-hcl"),
c_sources: vec!["parser.c"],
cpp_sources: vec!["scanner.cc"],
},
GrammarCompileInfo {
display_name: "typescript",
path: PathBuf::from("grammars/tree-sitter-typescript/typescript"),
c_sources: vec!["parser.c", "scanner.c"],
cpp_sources: vec![],
},
GrammarCompileInfo {
display_name: "tsx",
path: PathBuf::from("grammars/tree-sitter-typescript/tsx"),
c_sources: vec!["parser.c", "scanner.c"],
cpp_sources: vec![],
}, ];
grammars
}
#[cfg(feature = "static-grammar-libs")]
fn compile_static_grammars() -> Result<()> {
let grammars = grammars();
let mut codegen = String::from(
r#"
use tree_sitter::Language;
use phf::phf_map;
"#,
);
let mut languages = Vec::new();
languages.reserve(grammars.len());
let compile_params: Vec<CompileParams> = grammars.iter().map(preprocess_compile_info).collect();
compile_params
.iter()
.map(verify_compile_params)
.collect::<Result<Vec<_>, CompileParamError>>()?;
compile_params
.par_iter()
.map(|p| {
compile_grammar(
&p.dir,
&p.c_sources[..],
&p.cpp_sources[..],
&p.display_name,
)
})
.collect::<Result<Vec<_>, _>>()?;
for params in &compile_params {
let language = ¶ms.display_name;
codegen += &format!(
"extern \"C\" {{ pub fn tree_sitter_{}() -> Language; }}\n",
language
);
languages.push(language.as_str());
for source in params.c_sources.iter().chain(params.cpp_sources.iter()) {
if let Some(grammar_path) = &source.as_path().to_str() {
rerun_if_changed!((*grammar_path).to_string());
} else {
bail!("Path to grammar for {} is not a valid string", language);
}
}
}
extra_cargo_directives();
codegen += &codegen_language_map(&languages[..]);
let codegen_out_dir = env::var_os("OUT_DIR").unwrap();
let codegen_path = Path::new(&codegen_out_dir).join("generated_grammar.rs");
fs::write(&codegen_path, codegen)?;
Ok(())
}
fn main() -> Result<()> {
#[cfg(feature = "static-grammar-libs")]
compile_static_grammars()?;
#[cfg(feature = "better-build-info")]
build_info_build::build_script();
Ok(())
}