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::{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 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(imports) = payload? {
87 for import in imports {
88 let import = import?.name;
89 if !builtin_symbols.contains(&import)
90 && !stdlib_symbols.contains(&import)
91 && !dylink_symbols.contains(&import)
92 {
93 missing_symbols.push(import);
94 }
95 }
96 }
97 }
98
99 if !missing_symbols.is_empty() {
100 Err(anyhow!(
101 concat!(
102 "This external scanner uses a symbol that isn't available to Wasm parsers.\n",
103 "\n",
104 "Missing symbols:\n",
105 " {}\n",
106 "\n",
107 "Available symbols:\n",
108 " {}",
109 ),
110 missing_symbols.join("\n "),
111 stdlib_symbols.join("\n ")
112 ))?;
113 }
114
115 Ok(())
116}