1#![forbid(unsafe_code)]
2#![cfg_attr(not(test), deny(clippy::unwrap_used, clippy::expect_used))]
3
4use futures::executor::block_on;
5use rexlang_engine::{Engine, ValueDisplayOptions, pointer_display_with};
6use rexlang_lexer::Token;
7use rexlang_lsp::{
8 code_actions_for_source_public, completion_for_source, diagnostics_for_source,
9 document_symbols_for_source_public, format_for_source_public, goto_definition_for_source,
10 hover_for_source, references_for_source_public, rename_for_source_public,
11};
12use rexlang_parser::{Parser, ParserLimits, error::ParserErr};
13use rexlang_typesystem::{TypeSystem, TypeSystemLimits};
14use rexlang_util::{GasCosts, GasMeter};
15use wasm_bindgen::prelude::*;
16
17const DEFAULT_GAS_LIMIT: u64 = 5_000_000;
18
19fn new_gas(limit: Option<u64>) -> GasMeter {
20 GasMeter::new(
21 Some(limit.unwrap_or(DEFAULT_GAS_LIMIT)),
22 GasCosts::sensible_defaults(),
23 )
24}
25
26fn new_unlimited_gas() -> GasMeter {
27 GasMeter::unlimited(GasCosts::sensible_defaults())
28}
29
30fn parse_program_with_limits(
31 source: &str,
32 gas: &mut GasMeter,
33 limits: ParserLimits,
34) -> Result<rexlang_ast::expr::Program, String> {
35 let tokens = Token::tokenize(source).map_err(|e| format!("lex error: {e}"))?;
36 let mut parser = Parser::new(tokens);
37 parser.set_limits(limits);
38 parser
39 .parse_program(gas)
40 .map_err(|errs| format_parse_errors(&errs))
41}
42
43fn format_parse_errors(errs: &[ParserErr]) -> String {
44 let mut out = String::from("parse error:");
45 for err in errs {
46 out.push('\n');
47 out.push_str(" ");
48 out.push_str(&err.to_string());
49 }
50 out
51}
52
53pub fn parse_to_json(source: &str, gas_limit: Option<u64>) -> Result<String, String> {
54 let mut gas = new_gas(gas_limit);
55 let program = parse_program_with_limits(source, &mut gas, ParserLimits::safe_defaults())?;
56 serde_json::to_string(&program).map_err(|e| format!("serialization error: {e}"))
57}
58
59pub fn infer_to_json(source: &str, gas_limit: Option<u64>) -> Result<String, String> {
60 let mut gas = new_gas(gas_limit);
61 let program = parse_program_with_limits(source, &mut gas, ParserLimits::safe_defaults())?;
62
63 let mut ts = TypeSystem::with_prelude().map_err(|e| format!("type system error: {e}"))?;
64 ts.set_limits(TypeSystemLimits::safe_defaults());
65 ts.inject_decls(&program.decls)
66 .map_err(|e| format!("type declaration error: {e}"))?;
67
68 let (preds, typ) = ts
69 .infer_with_gas(program.expr.as_ref(), &mut gas)
70 .map_err(|e| format!("type error: {e}"))?;
71
72 let payload = serde_json::json!({
73 "type": typ.to_string(),
74 "predicates": preds
75 .iter()
76 .map(|p| format!("{} {}", p.class, p.typ))
77 .collect::<Vec<_>>(),
78 });
79 serde_json::to_string(&payload).map_err(|e| format!("serialization error: {e}"))
80}
81
82pub fn lsp_diagnostics_to_json(source: &str) -> Result<String, String> {
83 let diagnostics = diagnostics_for_source(source);
84 serde_json::to_string(&diagnostics).map_err(|e| format!("serialization error: {e}"))
85}
86
87pub fn lsp_completions_to_json(source: &str, line: u32, character: u32) -> Result<String, String> {
88 let completions = completion_for_source(source, line, character);
89 serde_json::to_string(&completions).map_err(|e| format!("serialization error: {e}"))
90}
91
92pub fn lsp_hover_to_json(source: &str, line: u32, character: u32) -> Result<String, String> {
93 let hover = hover_for_source(source, line, character);
94 serde_json::to_string(&hover).map_err(|e| format!("serialization error: {e}"))
95}
96
97pub fn lsp_goto_definition_to_json(
98 source: &str,
99 line: u32,
100 character: u32,
101) -> Result<String, String> {
102 let location = goto_definition_for_source(source, line, character);
103 serde_json::to_string(&location).map_err(|e| format!("serialization error: {e}"))
104}
105
106pub fn lsp_references_to_json(
107 source: &str,
108 line: u32,
109 character: u32,
110 include_declaration: bool,
111) -> Result<String, String> {
112 let refs = references_for_source_public(source, line, character, include_declaration);
113 serde_json::to_string(&refs).map_err(|e| format!("serialization error: {e}"))
114}
115
116pub fn lsp_rename_to_json(
117 source: &str,
118 line: u32,
119 character: u32,
120 new_name: &str,
121) -> Result<String, String> {
122 let edit = rename_for_source_public(source, line, character, new_name);
123 serde_json::to_string(&edit).map_err(|e| format!("serialization error: {e}"))
124}
125
126pub fn lsp_document_symbols_to_json(source: &str) -> Result<String, String> {
127 let symbols = document_symbols_for_source_public(source);
128 serde_json::to_string(&symbols).map_err(|e| format!("serialization error: {e}"))
129}
130
131pub fn lsp_format_to_json(source: &str) -> Result<String, String> {
132 let edits = format_for_source_public(source);
133 serde_json::to_string(&edits).map_err(|e| format!("serialization error: {e}"))
134}
135
136pub fn lsp_code_actions_to_json(source: &str, line: u32, character: u32) -> Result<String, String> {
137 let actions = code_actions_for_source_public(source, line, character);
138 serde_json::to_string(&actions).map_err(|e| format!("serialization error: {e}"))
139}
140
141pub async fn eval_to_string(source: &str, gas_limit: Option<u64>) -> Result<String, String> {
142 let mut gas = if gas_limit.is_some() {
143 new_gas(gas_limit)
144 } else {
145 new_unlimited_gas()
146 };
147 let _ = parse_program_with_limits(source, &mut gas, ParserLimits::unlimited())?;
148
149 let mut engine = Engine::with_prelude(()).map_err(|e| format!("engine init error: {e}"))?;
150 engine
151 .type_system
152 .set_limits(rexlang_typesystem::TypeSystemLimits::unlimited());
153 let (value_ptr, _value_ty) = engine
156 .eval_snippet(source, &mut gas)
157 .await
158 .map_err(|e| format!("runtime error: {e}"))?;
159
160 pointer_display_with(&engine.heap, &value_ptr, ValueDisplayOptions::docs())
161 .map_err(|e| format!("display error: {e}"))
162}
163
164fn as_js_err(err: String) -> JsValue {
165 JsValue::from_str(&err)
166}
167
168#[wasm_bindgen(js_name = parseToJson)]
169pub fn wasm_parse_to_json(source: &str, gas_limit: Option<u64>) -> Result<String, JsValue> {
170 parse_to_json(source, gas_limit).map_err(as_js_err)
171}
172
173#[wasm_bindgen(js_name = inferToJson)]
174pub fn wasm_infer_to_json(source: &str, gas_limit: Option<u64>) -> Result<String, JsValue> {
175 infer_to_json(source, gas_limit).map_err(as_js_err)
176}
177
178#[wasm_bindgen(js_name = lspDiagnosticsToJson)]
179pub fn wasm_lsp_diagnostics_to_json(source: &str) -> Result<String, JsValue> {
180 lsp_diagnostics_to_json(source).map_err(as_js_err)
181}
182
183#[wasm_bindgen(js_name = lspCompletionsToJson)]
184pub fn wasm_lsp_completions_to_json(
185 source: &str,
186 line: u32,
187 character: u32,
188) -> Result<String, JsValue> {
189 lsp_completions_to_json(source, line, character).map_err(as_js_err)
190}
191
192#[wasm_bindgen(js_name = lspHoverToJson)]
193pub fn wasm_lsp_hover_to_json(source: &str, line: u32, character: u32) -> Result<String, JsValue> {
194 lsp_hover_to_json(source, line, character).map_err(as_js_err)
195}
196
197#[wasm_bindgen(js_name = lspGotoDefinitionToJson)]
198pub fn wasm_lsp_goto_definition_to_json(
199 source: &str,
200 line: u32,
201 character: u32,
202) -> Result<String, JsValue> {
203 lsp_goto_definition_to_json(source, line, character).map_err(as_js_err)
204}
205
206#[wasm_bindgen(js_name = lspReferencesToJson)]
207pub fn wasm_lsp_references_to_json(
208 source: &str,
209 line: u32,
210 character: u32,
211 include_declaration: bool,
212) -> Result<String, JsValue> {
213 lsp_references_to_json(source, line, character, include_declaration).map_err(as_js_err)
214}
215
216#[wasm_bindgen(js_name = lspRenameToJson)]
217pub fn wasm_lsp_rename_to_json(
218 source: &str,
219 line: u32,
220 character: u32,
221 new_name: &str,
222) -> Result<String, JsValue> {
223 lsp_rename_to_json(source, line, character, new_name).map_err(as_js_err)
224}
225
226#[wasm_bindgen(js_name = lspDocumentSymbolsToJson)]
227pub fn wasm_lsp_document_symbols_to_json(source: &str) -> Result<String, JsValue> {
228 lsp_document_symbols_to_json(source).map_err(as_js_err)
229}
230
231#[wasm_bindgen(js_name = lspFormatToJson)]
232pub fn wasm_lsp_format_to_json(source: &str) -> Result<String, JsValue> {
233 lsp_format_to_json(source).map_err(as_js_err)
234}
235
236#[wasm_bindgen(js_name = lspCodeActionsToJson)]
237pub fn wasm_lsp_code_actions_to_json(
238 source: &str,
239 line: u32,
240 character: u32,
241) -> Result<String, JsValue> {
242 lsp_code_actions_to_json(source, line, character).map_err(as_js_err)
243}
244
245#[wasm_bindgen(js_name = evalToJson)]
246pub fn wasm_eval_to_json(source: &str, gas_limit: Option<u64>) -> Result<String, JsValue> {
247 let mut gas = if gas_limit.is_some() {
248 new_gas(gas_limit)
249 } else {
250 new_unlimited_gas()
251 };
252 let _ = parse_program_with_limits(source, &mut gas, ParserLimits::unlimited())
253 .map_err(as_js_err)?;
254
255 let fut = async move {
256 let mut engine = Engine::with_prelude(()).map_err(|e| format!("engine init error: {e}"))?;
257 let (value_ptr, _value_ty) = engine
258 .eval_snippet(source, &mut gas)
259 .await
260 .map_err(|e| format!("runtime error: {e}"))?;
261 let rendered =
262 pointer_display_with(&engine.heap, &value_ptr, ValueDisplayOptions::unsanitized())
263 .map_err(|e| format!("display error: {e}"))?;
264 let payload = serde_json::json!({ "value": rendered });
265 serde_json::to_string(&payload).map_err(|e| format!("serialization error: {e}"))
266 };
267 block_on(fut).map_err(as_js_err)
268}
269
270#[wasm_bindgen(js_name = evalToString)]
271pub fn wasm_eval_to_string(source: &str, gas_limit: Option<u64>) -> Result<String, JsValue> {
272 block_on(eval_to_string(source, gas_limit)).map_err(as_js_err)
273}
274
275#[cfg(test)]
276mod tests {
277 use super::{
278 eval_to_string, lsp_code_actions_to_json, lsp_diagnostics_to_json, wasm_eval_to_json,
279 };
280 use futures::executor::block_on;
281
282 #[test]
283 fn eval_to_string_hides_snippet_prefix_and_numeric_suffix() {
284 let source = r#"
285type T = A | B
286let
287 x = A,
288 n = 2
289in
290 (n, [x, B])
291"#;
292 let full = wasm_eval_to_json(source, None).expect("wasm eval failed");
293 assert!(full.contains("2i32"));
294 assert!(full.contains("@snippet"));
295
296 let sanitized = block_on(eval_to_string(source, None)).expect("wasm string eval failed");
297 assert_eq!(sanitized, "(2, [A, B])");
298 }
299
300 #[test]
301 fn lsp_diagnostics_preserve_all_unknown_var_usages() {
302 let source = r#"
303let
304 f = \x -> missing + x
305in
306 missing + (f missing)
307"#;
308 let json = lsp_diagnostics_to_json(source).expect("diagnostics json");
309 let diagnostics: serde_json::Value =
310 serde_json::from_str(&json).expect("diagnostics parse");
311 let count = diagnostics
312 .as_array()
313 .expect("diagnostics array")
314 .iter()
315 .filter(|diag| {
316 diag.get("message")
317 .and_then(serde_json::Value::as_str)
318 .is_some_and(|m| m.contains("unbound variable missing"))
319 })
320 .count();
321 assert_eq!(count, 3, "diagnostics: {diagnostics:#?}");
322 }
323
324 #[test]
325 fn lsp_code_actions_include_unknown_var_fixes() {
326 let source = r#"
327let
328 x = 1
329in
330 y + x
331"#;
332 let json = lsp_code_actions_to_json(source, 4, 2).expect("code actions json");
333 let actions: serde_json::Value = serde_json::from_str(&json).expect("actions parse");
334 let has_replace = actions
335 .as_array()
336 .expect("actions array")
337 .iter()
338 .any(|item| {
339 item.get("title")
340 .and_then(serde_json::Value::as_str)
341 .is_some_and(|title| title.contains("Replace `y` with `x`"))
342 });
343 assert!(has_replace, "actions: {actions:#?}");
344 }
345}