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 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}