tree_sitter_cli/
wasm.rs

1use std::{
2    fs,
3    path::{Path, PathBuf},
4};
5
6use anyhow::{anyhow, Context, Result};
7use tree_sitter::wasm_stdlib_symbols;
8use tree_sitter_generate::parse_grammar::GrammarJSON;
9use tree_sitter_loader::Loader;
10use wasmparser::Parser;
11
12pub fn load_language_wasm_file(language_dir: &Path) -> Result<(String, Vec<u8>)> {
13    let grammar_name = get_grammar_name(language_dir)
14        .with_context(|| "Failed to get wasm filename")
15        .unwrap();
16    let wasm_filename = format!("tree-sitter-{grammar_name}.wasm");
17    let contents = fs::read(language_dir.join(&wasm_filename)).with_context(|| {
18        format!("Failed to read {wasm_filename}. Run `tree-sitter build --wasm` first.",)
19    })?;
20    Ok((grammar_name, contents))
21}
22
23pub fn get_grammar_name(language_dir: &Path) -> Result<String> {
24    let src_dir = language_dir.join("src");
25    let grammar_json_path = src_dir.join("grammar.json");
26    let grammar_json = fs::read_to_string(&grammar_json_path)
27        .with_context(|| format!("Failed to read grammar file {grammar_json_path:?}"))?;
28    let grammar: GrammarJSON = serde_json::from_str(&grammar_json)
29        .with_context(|| format!("Failed to parse grammar file {grammar_json_path:?}"))?;
30    Ok(grammar.name)
31}
32
33pub fn compile_language_to_wasm(
34    loader: &Loader,
35    root_dir: Option<&Path>,
36    language_dir: &Path,
37    output_dir: &Path,
38    output_file: Option<PathBuf>,
39    force_docker: bool,
40) -> Result<()> {
41    let grammar_name = get_grammar_name(language_dir)?;
42    let output_filename =
43        output_file.unwrap_or_else(|| output_dir.join(format!("tree-sitter-{grammar_name}.wasm")));
44    let src_path = language_dir.join("src");
45    let scanner_path = loader.get_scanner_path(&src_path);
46    loader.compile_parser_to_wasm(
47        &grammar_name,
48        root_dir,
49        &src_path,
50        scanner_path
51            .as_ref()
52            .and_then(|p| Some(Path::new(p.file_name()?))),
53        &output_filename,
54        force_docker,
55    )?;
56
57    // Exit with an error if the external scanner uses symbols from the
58    // C or C++ standard libraries that aren't available to wasm parsers.
59    let stdlib_symbols = wasm_stdlib_symbols().collect::<Vec<_>>();
60    let dylink_symbols = [
61        "__indirect_function_table",
62        "__memory_base",
63        "__stack_pointer",
64        "__table_base",
65        "__table_base",
66        "memory",
67    ];
68    let builtin_symbols = [
69        "__assert_fail",
70        "__cxa_atexit",
71        "abort",
72        "emscripten_notify_memory_growth",
73        "tree_sitter_debug_message",
74        "proc_exit",
75    ];
76
77    let mut missing_symbols = Vec::new();
78    let wasm_bytes = fs::read(&output_filename)?;
79    let parser = Parser::new(0);
80    for payload in parser.parse_all(&wasm_bytes) {
81        if let wasmparser::Payload::ImportSection(imports) = payload? {
82            for import in imports {
83                let import = import?.name;
84                if !builtin_symbols.contains(&import)
85                    && !stdlib_symbols.contains(&import)
86                    && !dylink_symbols.contains(&import)
87                {
88                    missing_symbols.push(import);
89                }
90            }
91        }
92    }
93
94    if !missing_symbols.is_empty() {
95        Err(anyhow!(
96            concat!(
97                "This external scanner uses a symbol that isn't available to wasm parsers.\n",
98                "\n",
99                "Missing symbols:\n",
100                "    {}\n",
101                "\n",
102                "Available symbols:\n",
103                "    {}",
104            ),
105            missing_symbols.join("\n    "),
106            stdlib_symbols.join("\n    ")
107        ))?;
108    }
109
110    Ok(())
111}