1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
/*!
The guest side of the larvae worm ABI.
A worm is a `wasm32` module that larvae loads and calls. wasm has no strings.
Thus all data crosses as an offset and a length into the linear memory of the
module. This crate owns that protocol, so a worm author does not write it:
```ignore
larvae_worm::frontend!(|source: &str, config: &str| -> anyhow::Result<String> {
luaux::compile_configured(source, Backend::Vide, &Config::parse(config)?)
});
```
The macro is not the only entry point. [`abi`] is public and documented. Thus
a worm with an unusual design can export the raw functions itself, without a
copy of the macro.
# The ABI
A worm exports `memory`, plus:
| export | signature |
|---|---|
| `larvae_alloc` | `(len: u32) -> ptr` |
| `larvae_dealloc` | `(ptr, len: u32)` |
| `larvae_transform` | `(src_ptr, src_len, cfg_ptr, cfg_len) -> *header` |
| `larvae_init` | `(cfg_ptr, cfg_len, rules_ptr, rules_len)` |
| `larvae_visit` | `(rule, epoch, node_id)` |
| `larvae_format` | `(src_ptr, src_len) -> *header` |
| `larvae_lint` | `(src_ptr, src_len) -> *header` |
| `larvae_settings` | `(fmt_ptr, fmt_len, lint_ptr, lint_len)` |
`larvae_transform` returns a pointer to a three word header,
`[out_ptr, out_len, ok]`. `ok` is 1 when the bytes are output and 0 when they
are an error message. The header lives in a static, so the host does not free
it. The host calls `larvae_dealloc(out_ptr, out_len)` when it has read the
payload out.
`larvae_format` and `larvae_lint` return the same header. Their ok payload is
the JSON of a [`wire::Format`] or [`wire::Lint`] reply. The
[`formatter!`], [`linter!`], and [`settings!`] macros write these three
exports, behind the `wire` feature.
*/
/// The ABI revision this crate implements. It must match `api` in `worm.toml`.
pub const ABI_VERSION: u32 = 1;
pub use Node;
/**
Define a front-end worm. It takes source text and returns transformed source.
The closure takes the contents of the file and the `[config.<name>]` table of
the worm, serialized again as TOML. It returns the transformed source. Each
error type that implements [`Display`](core::fmt::Display) works, so
`anyhow::Result<String>` is valid.
```ignore
larvae_worm::frontend!(|source: &str, _config: &str| -> Result<String, String> {
Ok(source.replace("<>", "{}"))
});
```
The macro expands to the three exports in the module docs. Use it once per worm.
*/
/**
Define the rule half of a worm.
Each rule is a name and a handler. larvae calls a rule only on the nodes that
match the `filter` you declared in `worm.toml`. Thus undeclared kinds do not
cross the boundary.
```ignore
larvae_worm::rules! {
"strip_debug" => |node: larvae_worm::Node| {
if node.kind() == "CallExpr" && node.text().starts_with("dprint") {
node.remove();
}
},
}
```
Combine this macro with [`frontend!`](crate::frontend) when a worm holds both roles.
*/
/**
Define the format half of a worm. It needs the `wire` feature.
The closure takes the contents of a claimed file and returns the layout as a
[`wire::Format`]. larvae renders the layout with the width and indentation of
the project, so no worm reimplements the printer. Set `fmt = true` under
`[frontend]` in your `worm.toml`, because larvae only calls the export that
the manifest promises.
The macro writes only the `larvae_format` export. Combine it with
[`frontend!`], which writes the allocator exports that every worm needs.
```ignore
larvae_worm::formatter!(|source: &str| -> Result<larvae_worm::wire::Format, String> {
Ok(larvae_worm::wire::Format::spans(find_luau_regions(source)))
});
```
*/
/**
Define the lint half of a worm. It needs the `wire` feature.
The closure takes the contents of a claimed file and returns the problems as
a [`wire::Lint`]. The findings carry no severity, because the host stamps the
levels from `[lint.rules]` and owns the exit codes. Declare each lint name
under `[lints]` in your `worm.toml`.
The macro writes only the `larvae_lint` export. Combine it with
[`frontend!`], which writes the allocator exports that every worm needs.
```ignore
larvae_worm::linter!(|source: &str| -> Result<larvae_worm::wire::Lint, String> {
Ok(larvae_worm::wire::Lint::default())
});
```
*/
/**
Receive the settings of the project. It needs the `wire` feature.
The macro writes the `larvae_settings` export. The host calls the export once,
directly after init, with the resolved `[fmt]` table and the lint levels of
the project, both as JSON text. Read them back at any later point with
[`wasm_ops::settings`]. Thus the user states a width one time, and not a
second time under `[worms.<name>.config]`.
```ignore
larvae_worm::settings!();
fn width() -> Option<u64> {
let (fmt, _lint) = larvae_worm::wasm_ops::settings();
serde_json::from_str::<serde_json::Value>(&fmt).ok()?["column_width"].as_u64()
}
```
*/