feat(cli)!: add a separate build command to compile parsers
This allows users to build parsers without having to run `test` or `parse` to invoke the compilation process, and allows them to output the object file to wherever they like. The `build-wasm` command was merged into this by just specifying the `--wasm` flag.
This commit is contained in:
parent
a1c39d4760
commit
99b93d83a1
12 changed files with 249 additions and 139 deletions
16
Cargo.lock
generated
16
Cargo.lock
generated
|
|
@ -82,9 +82,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.80"
|
||||
version = "1.0.81"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5ad32ce52e4161730f7098c077cd2ed6229b5804ccf99e5366be1ab72a98b4e1"
|
||||
checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247"
|
||||
|
||||
[[package]]
|
||||
name = "arbitrary"
|
||||
|
|
@ -980,9 +980,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.78"
|
||||
version = "1.0.79"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae"
|
||||
checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
|
@ -1257,18 +1257,18 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.57"
|
||||
version = "1.0.58"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e45bcbe8ed29775f228095caf2cd67af7a4ccf756ebff23a306bf3e8b47b24b"
|
||||
checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.57"
|
||||
version = "1.0.58"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81"
|
||||
checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ use std::path::{Path, PathBuf};
|
|||
use std::time::Instant;
|
||||
use std::{env, fs, str, usize};
|
||||
use tree_sitter::{Language, Parser, Query};
|
||||
use tree_sitter_loader::Loader;
|
||||
use tree_sitter_loader::{CompileConfig, Loader};
|
||||
|
||||
include!("../src/tests/helpers/dirs.rs");
|
||||
|
||||
|
|
@ -210,9 +210,9 @@ fn parse(path: &Path, max_path_length: usize, mut action: impl FnMut(&[u8])) ->
|
|||
}
|
||||
|
||||
fn get_language(path: &Path) -> Language {
|
||||
let src_dir = GRAMMARS_DIR.join(path).join("src");
|
||||
let src_path = GRAMMARS_DIR.join(path).join("src");
|
||||
TEST_LOADER
|
||||
.load_language_at_path(&src_dir, &[&src_dir], None)
|
||||
.with_context(|| format!("Failed to load language at path {src_dir:?}"))
|
||||
.load_language_at_path(CompileConfig::new(&src_path, None, None))
|
||||
.with_context(|| format!("Failed to load language at path {src_path:?}"))
|
||||
.unwrap()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -78,12 +78,6 @@ impl Config {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
const DYLIB_EXTENSION: &str = "so";
|
||||
|
||||
#[cfg(windows)]
|
||||
const DYLIB_EXTENSION: &str = "dll";
|
||||
|
||||
const BUILD_TARGET: &str = env!("BUILD_TARGET");
|
||||
|
||||
pub struct LanguageConfiguration<'a> {
|
||||
|
|
@ -106,7 +100,7 @@ pub struct LanguageConfiguration<'a> {
|
|||
}
|
||||
|
||||
pub struct Loader {
|
||||
parser_lib_path: PathBuf,
|
||||
pub parser_lib_path: PathBuf,
|
||||
languages_by_id: Vec<(PathBuf, OnceCell<Language>, Option<Vec<PathBuf>>)>,
|
||||
language_configurations: Vec<LanguageConfiguration<'static>>,
|
||||
language_configuration_ids_by_file_type: HashMap<String, Vec<usize>>,
|
||||
|
|
@ -120,6 +114,36 @@ pub struct Loader {
|
|||
wasm_store: Mutex<Option<tree_sitter::WasmStore>>,
|
||||
}
|
||||
|
||||
pub struct CompileConfig<'a> {
|
||||
pub src_path: &'a Path,
|
||||
pub header_paths: Vec<&'a Path>,
|
||||
pub parser_path: PathBuf,
|
||||
pub scanner_path: Option<PathBuf>,
|
||||
pub external_files: Option<&'a [PathBuf]>,
|
||||
pub output_path: Option<PathBuf>,
|
||||
pub flags: &'a [&'a str],
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
impl<'a> CompileConfig<'a> {
|
||||
pub fn new(
|
||||
src_path: &'a Path,
|
||||
externals: Option<&'a [PathBuf]>,
|
||||
output_path: Option<PathBuf>,
|
||||
) -> CompileConfig<'a> {
|
||||
Self {
|
||||
src_path,
|
||||
header_paths: vec![src_path],
|
||||
parser_path: src_path.join("parser.c"),
|
||||
scanner_path: None,
|
||||
external_files: externals,
|
||||
output_path,
|
||||
flags: &["TREE_SITTER_REUSE_ALLOCATOR", "TREE_SITTER_INTERNAL_BUILD"],
|
||||
name: String::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Send for Loader {}
|
||||
unsafe impl Sync for Loader {}
|
||||
|
||||
|
|
@ -353,18 +377,29 @@ impl Loader {
|
|||
language
|
||||
.get_or_try_init(|| {
|
||||
let src_path = path.join("src");
|
||||
self.load_language_at_path(&src_path, &[&src_path], externals.as_deref())
|
||||
self.load_language_at_path(CompileConfig::new(
|
||||
&src_path,
|
||||
externals.as_deref(),
|
||||
None,
|
||||
))
|
||||
})
|
||||
.cloned()
|
||||
}
|
||||
|
||||
pub fn load_language_at_path(
|
||||
pub fn compile_parser_at_path(
|
||||
&self,
|
||||
src_path: &Path,
|
||||
header_paths: &[&Path],
|
||||
external_files: Option<&[PathBuf]>,
|
||||
) -> Result<Language> {
|
||||
let grammar_path = src_path.join("grammar.json");
|
||||
grammar_path: &Path,
|
||||
output_path: PathBuf,
|
||||
flags: &[&str],
|
||||
) -> Result<()> {
|
||||
let src_path = grammar_path.join("src");
|
||||
let mut config = CompileConfig::new(&src_path, None, Some(output_path));
|
||||
config.flags = flags;
|
||||
self.load_language_at_path(config).map(|_| ())
|
||||
}
|
||||
|
||||
pub fn load_language_at_path(&self, mut config: CompileConfig) -> Result<Language> {
|
||||
let grammar_path = config.src_path.join("grammar.json");
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct GrammarJSON {
|
||||
|
|
@ -375,85 +410,90 @@ impl Loader {
|
|||
let grammar_json: GrammarJSON = serde_json::from_reader(BufReader::new(&mut grammar_file))
|
||||
.with_context(|| "Failed to parse grammar.json")?;
|
||||
|
||||
self.load_language_at_path_with_name(
|
||||
src_path,
|
||||
header_paths,
|
||||
&grammar_json.name,
|
||||
external_files,
|
||||
)
|
||||
config.name = grammar_json.name;
|
||||
|
||||
self.load_language_at_path_with_name(config)
|
||||
}
|
||||
|
||||
pub fn load_language_at_path_with_name(
|
||||
&self,
|
||||
src_path: &Path,
|
||||
header_paths: &[&Path],
|
||||
name: &str,
|
||||
external_files: Option<&[PathBuf]>,
|
||||
) -> Result<Language> {
|
||||
let mut lib_name = name.to_string();
|
||||
let language_fn_name = format!("tree_sitter_{}", replace_dashes_with_underscores(name));
|
||||
pub fn load_language_at_path_with_name(&self, mut config: CompileConfig) -> Result<Language> {
|
||||
let mut lib_name = config.name.to_string();
|
||||
let language_fn_name = format!(
|
||||
"tree_sitter_{}",
|
||||
replace_dashes_with_underscores(&config.name)
|
||||
);
|
||||
if self.debug_build {
|
||||
lib_name.push_str(".debug._");
|
||||
}
|
||||
|
||||
fs::create_dir_all(&self.parser_lib_path)?;
|
||||
if config.output_path.is_none() {
|
||||
fs::create_dir_all(&self.parser_lib_path)?;
|
||||
}
|
||||
|
||||
let mut library_path = self.parser_lib_path.join(lib_name);
|
||||
library_path.set_extension(DYLIB_EXTENSION);
|
||||
let mut recompile = config.output_path.is_some(); // if specified, always recompile
|
||||
|
||||
let parser_path = src_path.join("parser.c");
|
||||
let scanner_path = self.get_scanner_path(src_path);
|
||||
let output_path = config.output_path.unwrap_or_else(|| {
|
||||
let mut path = self.parser_lib_path.join(lib_name);
|
||||
path.set_extension(env::consts::DLL_EXTENSION);
|
||||
#[cfg(feature = "wasm")]
|
||||
if self.wasm_store.lock().unwrap().is_some() {
|
||||
path.set_extension("wasm");
|
||||
}
|
||||
path
|
||||
});
|
||||
config.output_path = Some(output_path.clone());
|
||||
|
||||
let parser_path = config.src_path.join("parser.c");
|
||||
config.scanner_path = self.get_scanner_path(config.src_path);
|
||||
|
||||
let mut paths_to_check = vec![parser_path.clone()];
|
||||
|
||||
if let Some(scanner_path) = scanner_path.as_ref() {
|
||||
if let Some(scanner_path) = config.scanner_path.as_ref() {
|
||||
paths_to_check.push(scanner_path.clone());
|
||||
}
|
||||
|
||||
paths_to_check.extend(
|
||||
external_files
|
||||
config
|
||||
.external_files
|
||||
.unwrap_or_default()
|
||||
.iter()
|
||||
.map(|p| src_path.join(p)),
|
||||
.map(|p| config.src_path.join(p)),
|
||||
);
|
||||
|
||||
#[cfg(feature = "wasm")]
|
||||
if self.wasm_store.lock().unwrap().is_some() {
|
||||
library_path.set_extension("wasm");
|
||||
if !recompile {
|
||||
recompile = needs_recompile(&output_path, &paths_to_check)
|
||||
.with_context(|| "Failed to compare source and binary timestamps")?;
|
||||
}
|
||||
|
||||
let mut recompile = needs_recompile(&library_path, &paths_to_check)
|
||||
.with_context(|| "Failed to compare source and binary timestamps")?;
|
||||
|
||||
#[cfg(feature = "wasm")]
|
||||
if let Some(wasm_store) = self.wasm_store.lock().unwrap().as_mut() {
|
||||
if recompile {
|
||||
self.compile_parser_to_wasm(
|
||||
name,
|
||||
src_path,
|
||||
scanner_path
|
||||
&config.name,
|
||||
config.src_path,
|
||||
config
|
||||
.scanner_path
|
||||
.as_ref()
|
||||
.and_then(|p| p.strip_prefix(src_path).ok()),
|
||||
&library_path,
|
||||
.and_then(|p| p.strip_prefix(config.src_path).ok()),
|
||||
&output_path,
|
||||
false,
|
||||
)?;
|
||||
}
|
||||
|
||||
let wasm_bytes = fs::read(&library_path)?;
|
||||
return Ok(wasm_store.load_language(name, &wasm_bytes)?);
|
||||
let wasm_bytes = fs::read(&output_path)?;
|
||||
return Ok(wasm_store.load_language(&config.name, &wasm_bytes)?);
|
||||
}
|
||||
|
||||
let lock_path = if env::var("CROSS_RUNNER").is_ok() {
|
||||
PathBuf::from("/tmp")
|
||||
.join("tree-sitter")
|
||||
.join("lock")
|
||||
.join(format!("{name}.lock"))
|
||||
.join(format!("{}.lock", config.name))
|
||||
} else {
|
||||
dirs::cache_dir()
|
||||
.ok_or_else(|| anyhow!("Cannot determine cache directory"))?
|
||||
.join("tree-sitter")
|
||||
.join("lock")
|
||||
.join(format!("{name}.lock"))
|
||||
.join(format!("{}.lock", config.name))
|
||||
};
|
||||
|
||||
if let Ok(lock_file) = fs::OpenOptions::new().write(true).open(&lock_path) {
|
||||
|
|
@ -488,22 +528,15 @@ impl Loader {
|
|||
.open(&lock_path)?;
|
||||
lock_file.lock_exclusive()?;
|
||||
|
||||
self.compile_parser_to_dylib(
|
||||
header_paths,
|
||||
&parser_path,
|
||||
scanner_path.as_deref(),
|
||||
&library_path,
|
||||
&lock_file,
|
||||
&lock_path,
|
||||
)?;
|
||||
self.compile_parser_to_dylib(&config, &lock_file, &lock_path)?;
|
||||
|
||||
if scanner_path.is_some() {
|
||||
self.check_external_scanner(name, &library_path)?;
|
||||
if config.scanner_path.is_some() {
|
||||
self.check_external_scanner(&config.name, &output_path)?;
|
||||
}
|
||||
}
|
||||
|
||||
let library = unsafe { Library::new(&library_path) }
|
||||
.with_context(|| format!("Error opening dynamic library {library_path:?}"))?;
|
||||
let library = unsafe { Library::new(&output_path) }
|
||||
.with_context(|| format!("Error opening dynamic library {output_path:?}"))?;
|
||||
let language = unsafe {
|
||||
let language_fn = library
|
||||
.get::<Symbol<unsafe extern "C" fn() -> Language>>(language_fn_name.as_bytes())
|
||||
|
|
@ -516,32 +549,30 @@ impl Loader {
|
|||
|
||||
fn compile_parser_to_dylib(
|
||||
&self,
|
||||
header_paths: &[&Path],
|
||||
parser_path: &Path,
|
||||
scanner_path: Option<&Path>,
|
||||
library_path: &Path,
|
||||
config: &CompileConfig,
|
||||
lock_file: &fs::File,
|
||||
lock_path: &Path,
|
||||
) -> Result<(), Error> {
|
||||
let mut config = cc::Build::new();
|
||||
config
|
||||
.cpp(true)
|
||||
let mut cc = cc::Build::new();
|
||||
cc.cpp(true)
|
||||
.opt_level(2)
|
||||
.cargo_metadata(false)
|
||||
.cargo_warnings(false)
|
||||
.target(BUILD_TARGET)
|
||||
.host(BUILD_TARGET)
|
||||
.flag_if_supported("-Werror=implicit-function-declaration");
|
||||
let compiler = config.get_compiler();
|
||||
let compiler = cc.get_compiler();
|
||||
let mut command = Command::new(compiler.path());
|
||||
for (key, value) in compiler.env() {
|
||||
command.env(key, value);
|
||||
}
|
||||
|
||||
let output_path = config.output_path.as_ref().unwrap();
|
||||
|
||||
if compiler.is_like_msvc() {
|
||||
command.args(["/nologo", "/LD"]);
|
||||
|
||||
for path in header_paths {
|
||||
for path in &config.header_paths {
|
||||
command.arg(format!("/I{}", path.to_string_lossy()));
|
||||
}
|
||||
|
||||
|
|
@ -550,9 +581,9 @@ impl Loader {
|
|||
} else {
|
||||
command.arg("/O2");
|
||||
}
|
||||
command.arg(parser_path);
|
||||
command.arg(&config.parser_path);
|
||||
|
||||
if let Some(scanner_path) = scanner_path.as_ref() {
|
||||
if let Some(scanner_path) = config.scanner_path.as_ref() {
|
||||
if scanner_path.extension() != Some("c".as_ref()) {
|
||||
eprintln!("Warning: Using a C++ scanner is now deprecated. Please migrate your scanner code to C, as C++ support will be removed in the near future.");
|
||||
}
|
||||
|
|
@ -561,16 +592,16 @@ impl Loader {
|
|||
}
|
||||
command
|
||||
.arg("/link")
|
||||
.arg(format!("/out:{}", library_path.to_str().unwrap()));
|
||||
.arg(format!("/out:{}", output_path.to_str().unwrap()));
|
||||
} else {
|
||||
command
|
||||
.arg("-shared")
|
||||
.arg("-fno-exceptions")
|
||||
.arg("-g")
|
||||
.arg("-o")
|
||||
.arg(library_path);
|
||||
.arg(output_path);
|
||||
|
||||
for path in header_paths {
|
||||
for path in &config.header_paths {
|
||||
command.arg(format!("-I{}", path.to_string_lossy()));
|
||||
}
|
||||
|
||||
|
|
@ -584,7 +615,7 @@ impl Loader {
|
|||
command.arg("-O2");
|
||||
}
|
||||
|
||||
if let Some(scanner_path) = scanner_path.as_ref() {
|
||||
if let Some(scanner_path) = config.scanner_path.as_ref() {
|
||||
if scanner_path.extension() == Some("c".as_ref()) {
|
||||
command.arg("-xc").arg("-std=c11").arg(scanner_path);
|
||||
} else {
|
||||
|
|
@ -592,7 +623,7 @@ impl Loader {
|
|||
command.arg(scanner_path);
|
||||
}
|
||||
}
|
||||
command.arg("-xc").arg(parser_path);
|
||||
command.arg("-xc").arg(&config.parser_path);
|
||||
}
|
||||
|
||||
// For conditional compilation of external scanner code when
|
||||
|
|
@ -967,7 +998,6 @@ impl Loader {
|
|||
.map(|path| {
|
||||
let path = parser_path.join(path);
|
||||
// prevent p being above/outside of parser_path
|
||||
|
||||
if path.starts_with(parser_path) {
|
||||
Ok(path)
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@
|
|||
"install": "node-gyp-build",
|
||||
"prebuildify": "prebuildify --napi --strip",
|
||||
"build": "tree-sitter generate --no-bindings",
|
||||
"build-wasm": "tree-sitter build-wasm",
|
||||
"build-wasm": "tree-sitter build --wasm",
|
||||
"test": "tree-sitter test",
|
||||
"parse": "tree-sitter parse"
|
||||
},
|
||||
|
|
|
|||
106
cli/src/main.rs
106
cli/src/main.rs
|
|
@ -27,12 +27,12 @@ const DEFAULT_GENERATE_ABI_VERSION: usize = 14;
|
|||
enum Commands {
|
||||
InitConfig(InitConfig),
|
||||
Generate(Generate),
|
||||
Build(Build),
|
||||
Parse(Parse),
|
||||
Test(Test),
|
||||
Query(Query),
|
||||
Highlight(Highlight),
|
||||
Tags(Tags),
|
||||
BuildWasm(BuildWasm),
|
||||
Playground(Playground),
|
||||
DumpLanguages(DumpLanguages),
|
||||
}
|
||||
|
|
@ -91,6 +91,30 @@ struct Generate {
|
|||
pub js_runtime: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Args)]
|
||||
#[command(about = "Compile a parser", alias = "b")]
|
||||
struct Build {
|
||||
#[arg(short, long, help = "Build a WASM module instead of a dynamic library")]
|
||||
pub wasm: bool,
|
||||
#[arg(
|
||||
short,
|
||||
long,
|
||||
help = "Run emscripten via docker even if it is installed locally (only if building a WASM module with --wasm)"
|
||||
)]
|
||||
pub docker: bool,
|
||||
#[arg(short, long, help = "The path to output the compiled file")]
|
||||
pub output: Option<String>,
|
||||
#[arg(index = 1, num_args = 1, help = "The path to the grammar directory")]
|
||||
pub path: Option<String>,
|
||||
#[arg(long, help = "Make the parser reuse the same allocator as the library")]
|
||||
pub reuse_allocator: bool,
|
||||
#[arg(
|
||||
long,
|
||||
help = "Build the parser with `TREE_SITTER_INTERNAL_BUILD` defined"
|
||||
)]
|
||||
pub internal_build: bool,
|
||||
}
|
||||
|
||||
#[derive(Args)]
|
||||
#[command(about = "Parse files", alias = "p")]
|
||||
struct Parse {
|
||||
|
|
@ -298,18 +322,6 @@ struct Tags {
|
|||
pub config_path: Option<PathBuf>,
|
||||
}
|
||||
|
||||
#[derive(Args)]
|
||||
#[command(about = "Compile a parser to WASM", alias = "bw")]
|
||||
struct BuildWasm {
|
||||
#[arg(
|
||||
long,
|
||||
help = "Run emscripten via docker even if it is installed locally"
|
||||
)]
|
||||
pub docker: bool,
|
||||
#[arg(index = 1, num_args = 1.., help = "The path to output the wasm file")]
|
||||
pub path: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Args)]
|
||||
#[command(
|
||||
about = "Start local playground for a parser in the browser",
|
||||
|
|
@ -428,6 +440,64 @@ fn run() -> Result<()> {
|
|||
}
|
||||
}
|
||||
|
||||
Commands::Build(build_options) => {
|
||||
if build_options.wasm {
|
||||
let grammar_path =
|
||||
current_dir.join(build_options.path.as_deref().unwrap_or_default());
|
||||
let (output_dir, output_path) = if let Some(ref path) = build_options.output {
|
||||
(current_dir.clone(), Some(current_dir.join(path)))
|
||||
} else {
|
||||
(loader.parser_lib_path.clone(), None)
|
||||
};
|
||||
wasm::compile_language_to_wasm(
|
||||
&loader,
|
||||
&grammar_path,
|
||||
&output_dir,
|
||||
output_path,
|
||||
build_options.docker,
|
||||
)?;
|
||||
} else {
|
||||
let grammar_path =
|
||||
current_dir.join(build_options.path.as_deref().unwrap_or_default());
|
||||
let output_path = if let Some(ref path) = build_options.output {
|
||||
let path = Path::new(path);
|
||||
if path.is_absolute() {
|
||||
path.to_path_buf()
|
||||
} else {
|
||||
current_dir.join(path)
|
||||
}
|
||||
} else {
|
||||
let file_name = grammar_path
|
||||
.file_stem()
|
||||
.unwrap()
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.strip_prefix("tree-sitter-")
|
||||
.unwrap_or("parser");
|
||||
current_dir
|
||||
.join(file_name)
|
||||
.with_extension(env::consts::DLL_EXTENSION)
|
||||
};
|
||||
|
||||
let flags: &[&str] =
|
||||
match (build_options.reuse_allocator, build_options.internal_build) {
|
||||
(true, true) => {
|
||||
&["TREE_SITTER_REUSE_ALLOCATOR", "TREE_SITTER_INTERNAL_BUILD"]
|
||||
}
|
||||
(true, false) => &["TREE_SITTER_REUSE_ALLOCATOR"],
|
||||
(false, true) => &["TREE_SITTER_INTERNAL_BUILD"],
|
||||
(false, false) => &[""],
|
||||
};
|
||||
|
||||
let config = Config::load(None)?;
|
||||
let loader_config = config.get()?;
|
||||
loader.find_all_languages(&loader_config).unwrap();
|
||||
loader
|
||||
.compile_parser_at_path(&grammar_path, output_path, flags)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
Commands::Parse(parse_options) => {
|
||||
let config = Config::load(parse_options.config_path)?;
|
||||
let output = if parse_options.output_dot {
|
||||
|
|
@ -770,16 +840,6 @@ fn run() -> Result<()> {
|
|||
)?;
|
||||
}
|
||||
|
||||
Commands::BuildWasm(wasm_options) => {
|
||||
let grammar_path = current_dir.join(wasm_options.path.unwrap_or_default());
|
||||
wasm::compile_language_to_wasm(
|
||||
&loader,
|
||||
&grammar_path,
|
||||
¤t_dir,
|
||||
wasm_options.docker,
|
||||
)?;
|
||||
}
|
||||
|
||||
Commands::Playground(playground_options) => {
|
||||
let open_in_browser = !playground_options.quiet;
|
||||
let grammar_path = playground_options
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use std::path::{Path, PathBuf};
|
|||
use std::{env, fs};
|
||||
use tree_sitter::Language;
|
||||
use tree_sitter_highlight::HighlightConfiguration;
|
||||
use tree_sitter_loader::Loader;
|
||||
use tree_sitter_loader::{CompileConfig, Loader};
|
||||
use tree_sitter_tags::TagsConfiguration;
|
||||
|
||||
include!("./dirs.rs");
|
||||
|
|
@ -32,13 +32,10 @@ pub fn scratch_dir() -> &'static Path {
|
|||
}
|
||||
|
||||
pub fn get_language(name: &str) -> Language {
|
||||
TEST_LOADER
|
||||
.load_language_at_path(
|
||||
&GRAMMARS_DIR.join(name).join("src"),
|
||||
&[&HEADER_DIR, &GRAMMARS_DIR.join(name).join("src")],
|
||||
None,
|
||||
)
|
||||
.unwrap()
|
||||
let src_dir = GRAMMARS_DIR.join(name).join("src");
|
||||
let mut config = CompileConfig::new(&src_dir, None, None);
|
||||
config.header_paths.push(&HEADER_DIR);
|
||||
TEST_LOADER.load_language_at_path(config).unwrap()
|
||||
}
|
||||
|
||||
pub fn get_language_queries_path(language_name: &str) -> PathBuf {
|
||||
|
|
@ -141,7 +138,9 @@ pub fn get_test_language(name: &str, parser_code: &str, path: Option<&Path>) ->
|
|||
vec![parser_path]
|
||||
};
|
||||
|
||||
TEST_LOADER
|
||||
.load_language_at_path_with_name(&src_dir, &[&HEADER_DIR], name, Some(&paths_to_check))
|
||||
.unwrap()
|
||||
let mut config = CompileConfig::new(&src_dir, Some(&paths_to_check), None);
|
||||
config.header_paths = vec![&HEADER_DIR];
|
||||
config.name = name.to_string();
|
||||
|
||||
TEST_LOADER.load_language_at_path_with_name(config).unwrap()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
use super::generate::parse_grammar::GrammarJSON;
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use std::{fs, path::Path};
|
||||
use std::{
|
||||
fs,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
use tree_sitter::wasm_stdlib_symbols;
|
||||
use tree_sitter_loader::Loader;
|
||||
use wasmparser::Parser;
|
||||
|
|
@ -11,7 +14,7 @@ pub fn load_language_wasm_file(language_dir: &Path) -> Result<(String, Vec<u8>)>
|
|||
.unwrap();
|
||||
let wasm_filename = format!("tree-sitter-{grammar_name}.wasm");
|
||||
let contents = fs::read(language_dir.join(&wasm_filename)).with_context(|| {
|
||||
format!("Failed to read {wasm_filename}. Run `tree-sitter build-wasm` first.",)
|
||||
format!("Failed to read {wasm_filename}. Run `tree-sitter build --wasm` first.",)
|
||||
})?;
|
||||
Ok((grammar_name, contents))
|
||||
}
|
||||
|
|
@ -30,10 +33,12 @@ pub fn compile_language_to_wasm(
|
|||
loader: &Loader,
|
||||
language_dir: &Path,
|
||||
output_dir: &Path,
|
||||
output_file: Option<PathBuf>,
|
||||
force_docker: bool,
|
||||
) -> Result<()> {
|
||||
let grammar_name = get_grammar_name(language_dir)?;
|
||||
let output_filename = output_dir.join(format!("tree-sitter-{grammar_name}.wasm"));
|
||||
let output_filename =
|
||||
output_file.unwrap_or_else(|| output_dir.join(format!("tree-sitter-{grammar_name}.wasm")));
|
||||
let src_path = language_dir.join("src");
|
||||
let scanner_path = loader.get_scanner_path(&src_path);
|
||||
loader.compile_parser_to_wasm(
|
||||
|
|
|
|||
|
|
@ -148,6 +148,24 @@ The first time you run `tree-sitter generate`, it will also generate a few other
|
|||
|
||||
If there is an ambiguity or *local ambiguity* in your grammar, Tree-sitter will detect it during parser generation, and it will exit with a `Unresolved conflict` error message. See below for more information on these errors.
|
||||
|
||||
### Command: `build`
|
||||
|
||||
The `build` command compiles your parser into a dynamically-loadable library, either as a shared object (`.so`, `.dylib`, or `.dll`) or as a WASM module.
|
||||
|
||||
You can specify whether to compile it as a wasm module with the `--wasm`/`-w` flag, and you can opt in to use docker or podman to supply emscripten with the `--docker`/`-d` flag. This removes the need to install emscripten on your machine locally.
|
||||
|
||||
You can specify where to output the shared object file (native or WASM) with the `--output`/`-o` flag, which accepts either an absolute path or relative path. Note that if you don't supply this flag, the CLI will attempt to figure out what the language name is based on the parent directory (so building in `tree-sitter-javascript` will resolve to `javascript`) to use for the output file. If it can't figure it out, it will default to `parser`, thus generating `parser.so` or `parser.wasm` in the current working directory.
|
||||
|
||||
Lastly, you can also specify a path to the actual grammar directory, in case you are not currently in one. This is done by providing a path as the first *positional* argument.
|
||||
|
||||
Example:
|
||||
|
||||
```sh
|
||||
tree-sitter build --wasm --output ./build/parser.wasm tree-sitter-javascript
|
||||
```
|
||||
|
||||
Notice how the `tree-sitter-javascript` argument is the first positional argument.
|
||||
|
||||
### Command: `test`
|
||||
|
||||
The `tree-sitter test` command allows you to easily test that your parser is working correctly.
|
||||
|
|
|
|||
|
|
@ -131,7 +131,7 @@ npm install --save-dev tree-sitter-cli tree-sitter-javascript
|
|||
Then just use tree-sitter cli tool to generate the `.wasm`.
|
||||
|
||||
```sh
|
||||
npx tree-sitter build-wasm node_modules/tree-sitter-javascript
|
||||
npx tree-sitter build --wasm node_modules/tree-sitter-javascript
|
||||
```
|
||||
|
||||
If everything is fine, file `tree-sitter-javascript.wasm` should be generated in current directory.
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ function languageURL(name) {
|
|||
module.exports = Parser.init().then(async () => ({
|
||||
Parser,
|
||||
languageURL,
|
||||
EmbeddedTemplate: await Parser.Language.load(languageURL('embedded_template')),
|
||||
EmbeddedTemplate: await Parser.Language.load(languageURL('embedded-template')),
|
||||
HTML: await Parser.Language.load(languageURL('html')),
|
||||
JavaScript: await Parser.Language.load(languageURL('javascript')),
|
||||
JSON: await Parser.Language.load(languageURL('json')),
|
||||
|
|
|
|||
|
|
@ -30,4 +30,4 @@ while read -r grammar_file; do
|
|||
cd "$grammar_dir"
|
||||
"$tree_sitter" generate src/grammar.json --no-bindings --abi=latest
|
||||
)
|
||||
done <<< "$grammar_files"
|
||||
done <<<"$grammar_files"
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ fi
|
|||
filter_grammar_name=$1
|
||||
|
||||
grammars_dir=${root_dir}/test/fixtures/grammars
|
||||
grammar_files=$(find $grammars_dir -name grammar.js | grep -v node_modules)
|
||||
grammar_files=$(find "$grammars_dir" -name grammar.js | grep -v node_modules)
|
||||
|
||||
while read -r grammar_file; do
|
||||
grammar_dir=$(dirname "$grammar_file")
|
||||
|
|
@ -32,7 +32,5 @@ while read -r grammar_file; do
|
|||
fi
|
||||
|
||||
echo "Compiling ${grammar_name} parser to wasm"
|
||||
"$tree_sitter" build-wasm $build_wasm_args $grammar_dir
|
||||
done <<< "$grammar_files"
|
||||
|
||||
mv tree-sitter-*.wasm target/release/
|
||||
"$tree_sitter" build --wasm $build_wasm_args -o target/release/tree-sitter-"${grammar_name}".wasm "$grammar_dir"
|
||||
done <<<"$grammar_files"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue