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