Skip to main content

tree_sitter_cli/
wasm.rs

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