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