1use super::{MODULE_NAME, budget::Budget, walk};
3use crate::runtime::StoreData;
4use crate::runtime::effects;
5use crate::runtime::host::{
6 abi_arg, invariant_trap, register_host_value_async, write_submilli_string_struct_units,
7};
8use crate::runtime::intrinsic_types::build_intrinsic_types;
9use crate::runtime::prelude::vtable::read_string_units;
10use serde::{Deserialize, Serialize};
11use serde_json::Value;
12use wasmtime::{Caller, FuncType, HeapType, Linker, RefType, Result, Val, ValType, bail};
13
14pub fn install(linker: &mut Linker<StoreData>) -> Result<()> {
15 let engine = linker.engine().clone();
16 let intr = build_intrinsic_types(&engine)?;
17 let string = ValType::Ref(RefType::new(false, HeapType::ConcreteStruct(intr.string)));
18 let object = ValType::Ref(RefType::new(
19 false,
20 HeapType::ConcreteStruct(intr.object_shape),
21 ));
22 let options = ValType::Ref(RefType::new(true, HeapType::ConcreteStruct(intr.object)));
23
24 register_host_value_async(
25 linker,
26 MODULE_NAME,
27 crate::mangle::package_symbol(MODULE_NAME, "read"),
28 FuncType::new(
29 &engine,
30 [string.clone(), ValType::F64, ValType::F64],
31 [object.clone()],
32 ),
33 |caller, params| {
34 Box::pin(async move {
35 let mut budget = Budget::new(caller);
36 let path = argument(caller, &mut budget, abi_arg(params, 0)?)?;
37 let request = ReadArguments {
38 path: super::normalize(caller.data().filesystem.cwd(), &path)?,
39 start: integer(abi_arg(params, 1)?, "offset", 1)? - 1,
40 limit: integer(abi_arg(params, 2)?, "limit", 1)?,
41 };
42 let result =
43 effects::execute(caller, "code.read", request, move |caller, _, request| {
44 Box::pin(async move {
45 let value = super::read(
46 caller,
47 &mut budget,
48 &request.path,
49 request.start,
50 request.limit,
51 )
52 .await?;
53 Ok(CodeResult {
54 value,
55 _budget: Some(budget),
56 })
57 })
58 })
59 .await?;
60 encode_json(caller, &result.value)
61 })
62 },
63 )?;
64
65 register_host_value_async(
66 linker,
67 MODULE_NAME,
68 crate::mangle::package_symbol(MODULE_NAME, "search"),
69 FuncType::new(&engine, [string.clone(), options], [object.clone()]),
70 |caller, params| {
71 Box::pin(async move {
72 let mut budget = Budget::new(caller);
73 let request = SearchArguments {
74 pattern: argument(caller, &mut budget, abi_arg(params, 0)?)?,
75 options: walk::Options::read(caller, &mut budget, abi_arg(params, 1)?)?,
76 };
77 let result =
78 effects::execute(caller, "code.search", request, move |caller, _, request| {
79 Box::pin(async move {
80 let value = super::search(
81 caller,
82 &mut budget,
83 &request.pattern,
84 &request.options,
85 )
86 .await?;
87 Ok(CodeResult {
88 value,
89 _budget: Some(budget),
90 })
91 })
92 })
93 .await?;
94 encode_json(caller, &result.value)
95 })
96 },
97 )?;
98
99 register_host_value_async(
100 linker,
101 MODULE_NAME,
102 crate::mangle::package_symbol(MODULE_NAME, "glob"),
103 FuncType::new(&engine, [string.clone()], [object.clone()]),
104 |caller, params| {
105 Box::pin(async move {
106 let mut budget = Budget::new(caller);
107 let request = argument(caller, &mut budget, abi_arg(params, 0)?)?;
108 let result =
109 effects::execute(caller, "code.glob", request, move |caller, _, request| {
110 Box::pin(async move {
111 let value = super::glob(caller, &mut budget, &request).await?;
112 Ok(CodeResult {
113 value,
114 _budget: Some(budget),
115 })
116 })
117 })
118 .await?;
119 encode_json(caller, &result.value)
120 })
121 },
122 )?;
123
124 register_host_value_async(
125 linker,
126 MODULE_NAME,
127 crate::mangle::package_symbol(MODULE_NAME, "tree"),
128 FuncType::new(&engine, [string.clone(), ValType::F64], [object.clone()]),
129 |caller, params| {
130 Box::pin(async move {
131 let mut budget = Budget::new(caller);
132 let path = argument(caller, &mut budget, abi_arg(params, 0)?)?;
133 let request = TreeArguments {
134 path: super::normalize(caller.data().filesystem.cwd(), &path)?,
135 depth: integer(abi_arg(params, 1)?, "depth", 0)?,
136 };
137 let result =
138 effects::execute(caller, "code.tree", request, move |caller, _, request| {
139 Box::pin(async move {
140 let value =
141 super::tree(caller, &mut budget, &request.path, request.depth)
142 .await?;
143 Ok(CodeResult {
144 value,
145 _budget: Some(budget),
146 })
147 })
148 })
149 .await?;
150 encode_json(caller, &result.value)
151 })
152 },
153 )?;
154
155 register_host_value_async(
156 linker,
157 MODULE_NAME,
158 crate::mangle::package_symbol(MODULE_NAME, "edit"),
159 FuncType::new(
160 &engine,
161 [
162 string.clone(),
163 string.clone(),
164 string.clone(),
165 ValType::I32,
166 ValType::F64,
167 ],
168 [object.clone()],
169 ),
170 |caller, params| {
171 Box::pin(async move {
172 let mut budget = Budget::new(caller);
173 let path = argument(caller, &mut budget, abi_arg(params, 0)?)?;
174 let old = argument(caller, &mut budget, abi_arg(params, 1)?)?;
175 let new = argument(caller, &mut budget, abi_arg(params, 2)?)?;
176 let Val::I32(replace_all) = abi_arg(params, 3)? else {
177 return Err(invariant_trap("code.edit: invalid boolean argument"));
178 };
179 let near_line = integer(abi_arg(params, 4)?, "nearLine", 0)?;
180 let request = EditArguments {
181 path: super::normalize(caller.data().filesystem.cwd(), &path)?,
182 old,
183 new,
184 replace_all: *replace_all != 0,
185 near_line,
186 };
187 let result = effects::execute(
188 caller,
189 "code.edit",
190 request,
191 move |caller, effect, request| {
192 Box::pin(async move {
193 let value = super::update::execute(
194 caller,
195 &mut budget,
196 &effect,
197 "edit",
198 &request.path.clone(),
199 request,
200 |caller, budget, request| {
201 Box::pin(super::edit(
202 caller,
203 budget,
204 &request.path,
205 &request.old,
206 &request.new,
207 request.replace_all,
208 request.near_line,
209 ))
210 },
211 )
212 .await?;
213 Ok(CodeResult {
214 value,
215 _budget: Some(budget),
216 })
217 })
218 },
219 )
220 .await?;
221 encode_json(caller, &result.value)
222 })
223 },
224 )?;
225
226 register_host_value_async(
227 linker,
228 MODULE_NAME,
229 crate::mangle::package_symbol(MODULE_NAME, "insertAt"),
230 FuncType::new(
231 &engine,
232 [string.clone(), ValType::F64, string.clone()],
233 [object.clone()],
234 ),
235 |caller, params| {
236 Box::pin(async move {
237 let mut budget = Budget::new(caller);
238 let path = argument(caller, &mut budget, abi_arg(params, 0)?)?;
239 let line = integer(abi_arg(params, 1)?, "line", 1)?;
240 let text = argument(caller, &mut budget, abi_arg(params, 2)?)?;
241 let request = InsertArguments {
242 path: super::normalize(caller.data().filesystem.cwd(), &path)?,
243 line,
244 text,
245 };
246 let result = effects::execute(
247 caller,
248 "code.insertAt",
249 request,
250 move |caller, effect, request| {
251 Box::pin(async move {
252 let value = super::update::execute(
253 caller,
254 &mut budget,
255 &effect,
256 "insertAt",
257 &request.path.clone(),
258 request,
259 |caller, budget, request| {
260 Box::pin(super::insert_at(
261 caller,
262 budget,
263 &request.path,
264 request.line,
265 &request.text,
266 ))
267 },
268 )
269 .await?;
270 Ok(CodeResult {
271 value,
272 _budget: Some(budget),
273 })
274 })
275 },
276 )
277 .await?;
278 encode_json(caller, &result.value)
279 })
280 },
281 )?;
282
283 register_host_value_async(
284 linker,
285 MODULE_NAME,
286 crate::mangle::package_symbol(MODULE_NAME, "diffText"),
287 FuncType::new(&engine, [string.clone(), string.clone()], [string.clone()]),
288 |caller, params| {
289 Box::pin(async move {
290 let mut budget = Budget::new(caller);
291 let first = units(caller, &mut budget, abi_arg(params, 0)?)?;
292 let second = units(caller, &mut budget, abi_arg(params, 1)?)?;
293 let result = super::diff_text(caller, &mut budget, &first, &second)?;
294 Ok(Val::AnyRef(Some(
295 write_submilli_string_struct_units(caller, &result)?.to_anyref(),
296 )))
297 })
298 },
299 )?;
300
301 register_host_value_async(
302 linker,
303 MODULE_NAME,
304 crate::mangle::package_symbol(MODULE_NAME, "diffFiles"),
305 FuncType::new(&engine, [string.clone(), string.clone()], [string.clone()]),
306 |caller, params| {
307 Box::pin(async move {
308 let mut budget = Budget::new(caller);
309 let first = argument(caller, &mut budget, abi_arg(params, 0)?)?;
310 let second = argument(caller, &mut budget, abi_arg(params, 1)?)?;
311 let request = DiffFilesArguments {
312 first: super::normalize(caller.data().filesystem.cwd(), &first)?,
313 second: super::normalize(caller.data().filesystem.cwd(), &second)?,
314 };
315 let result = effects::execute(
316 caller,
317 "code.diffFiles",
318 request,
319 move |caller, _, request| {
320 Box::pin(async move {
321 let value = super::diff_files(
322 caller,
323 &mut budget,
324 &request.first,
325 &request.second,
326 )
327 .await?;
328 Ok(CodeResult {
329 value,
330 _budget: Some(budget),
331 })
332 })
333 },
334 )
335 .await?;
336 Ok(Val::AnyRef(Some(
337 write_submilli_string_struct_units(caller, &result.value)?.to_anyref(),
338 )))
339 })
340 },
341 )?;
342
343 register_host_value_async(
344 linker,
345 MODULE_NAME,
346 crate::mangle::package_symbol(MODULE_NAME, "applyPatch"),
347 FuncType::new(&engine, [string.clone(), string], [object]),
348 |caller, params| {
349 Box::pin(async move {
350 let mut budget = Budget::new(caller);
351 let path = argument(caller, &mut budget, abi_arg(params, 0)?)?;
352 let patch = argument(caller, &mut budget, abi_arg(params, 1)?)?;
353 let request = PatchArguments {
354 path: super::normalize(caller.data().filesystem.cwd(), &path)?,
355 patch,
356 };
357 let result = effects::execute(
358 caller,
359 "code.applyPatch",
360 request,
361 move |caller, effect, request| {
362 Box::pin(async move {
363 let value = super::update::execute(
364 caller,
365 &mut budget,
366 &effect,
367 "applyPatch",
368 &request.path.clone(),
369 request,
370 |caller, budget, request| {
371 Box::pin(super::apply_patch(
372 caller,
373 budget,
374 &request.path,
375 &request.patch,
376 ))
377 },
378 )
379 .await?;
380 Ok(CodeResult {
381 value,
382 _budget: Some(budget),
383 })
384 })
385 },
386 )
387 .await?;
388 encode_json(caller, &result.value)
389 })
390 },
391 )?;
392 Ok(())
393}
394
395#[derive(Serialize)]
396struct ReadArguments {
397 path: String,
398 start: usize,
399 limit: usize,
400}
401
402#[derive(Serialize)]
403struct SearchArguments {
404 pattern: String,
405 options: walk::Options,
406}
407
408#[derive(Serialize)]
409struct TreeArguments {
410 path: String,
411 depth: usize,
412}
413
414#[derive(Serialize)]
415struct EditArguments {
416 path: String,
417 old: String,
418 new: String,
419 replace_all: bool,
420 near_line: usize,
421}
422
423#[derive(Serialize)]
424struct InsertArguments {
425 path: String,
426 line: usize,
427 text: String,
428}
429
430#[derive(Serialize)]
431struct DiffFilesArguments {
432 first: String,
433 second: String,
434}
435
436#[derive(Serialize)]
437struct PatchArguments {
438 path: String,
439 patch: String,
440}
441
442#[derive(Serialize, Deserialize)]
443struct CodeResult<T> {
444 value: T,
445 #[serde(skip)]
447 _budget: Option<Budget>,
448}
449
450fn encode_json(caller: &mut Caller<'_, StoreData>, result: &Value) -> Result<Val> {
451 let mut budget = Budget::new(caller);
452 let encoded = serde_json::to_string(result)?;
453 budget.check_size(encoded.len())?;
454 budget.charge(caller, encoded.len().saturating_mul(4))?;
455 crate::stdlib::session::value::deserialize(caller, &encoded.encode_utf16().collect::<Vec<_>>())
456}
457
458pub(super) fn argument(
459 caller: &mut Caller<'_, StoreData>,
460 budget: &mut Budget,
461 val: &Val,
462) -> Result<String> {
463 String::from_utf16(&units(caller, budget, val)?).map_err(|_| {
464 wasmtime::Error::msg("code: filesystem text must not contain lone UTF-16 surrogates")
465 })
466}
467fn units(caller: &mut Caller<'_, StoreData>, budget: &mut Budget, val: &Val) -> Result<Vec<u16>> {
468 let value = read_string_units(caller, val, "code argument")?;
469 budget.check_size(value.len().saturating_mul(2))?;
470 budget.charge(caller, value.len().saturating_mul(4))?;
471 Ok(value)
472}
473fn integer(val: &Val, name: &str, min: usize) -> Result<usize> {
474 let n = match val {
475 Val::F64(bits) => f64::from_bits(*bits),
476 _ => {
477 return Err(crate::runtime::host::invariant_trap(
478 "code: invalid numeric argument",
479 ));
480 }
481 };
482 if !n.is_finite() || n.fract() != 0.0 || n < min as f64 || n > u32::MAX as f64 {
483 bail!(
484 "code: {name} must be an integer between {min} and {}",
485 u32::MAX
486 );
487 }
488 Ok(n as usize)
489}