Skip to content

Commit ef3d743

Browse files
authored
Implement a json-from-wast subcommand (#1460)
* Implement a `wast2json` subcommand This commit is an implementation of #1395 which brings wabt's `wast2json` functionality to `wasm-tools`. This is implemented entirely in the CLI since if using the crate it's already got all the functionality necessary for this. Additionally this PR attempts to be pretty close to `wast2json` from wabt in terms of structure and syntax. The main difference is that wabt fills out the expected return types of an operation when the operation isn't supposed to return (e.g. `assert_trap`), but `wasm-tools` won't do that as we don't have that information so easily available. This is tested by adding `*.json` files to the `snapshots` directory with example outputs from all known `*.wast` tests. Everything should be included except for the `component-model` tests at this time which don't have support in printing the args/results. Closes #1395 * Fix tests on wasm * Fix building just wast2json * Generate the same test output on Windows * Rename to `json-from-wast` * Fix typo
1 parent 619226e commit ef3d743

395 files changed

Lines changed: 1655006 additions & 24 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/main.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,8 @@ jobs:
157157
- run: cargo check --no-default-features --features metadata
158158
- run: cargo check --no-default-features --features wit-smith
159159
- run: cargo check --no-default-features --features addr2line
160+
- run: cargo check --no-default-features --features json-from-wast
161+
- run: cargo check --no-default-features --features completion
160162
- run: cargo check --no-default-features -p wit-parser
161163
- run: cargo check --no-default-features -p wit-parser --features wat
162164
- run: cargo check --no-default-features -p wit-parser --features serde

Cargo.toml

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -166,13 +166,14 @@ default = [
166166
'wit-smith',
167167
'addr2line',
168168
'completion',
169+
'json-from-wast',
169170
]
170171

171172
# Each subcommand is gated behind a feature and lists the dependencies it needs
172173
validate = ['dep:wasmparser', 'rayon']
173174
print = []
174175
parse = []
175-
smith = ['wasm-smith', 'arbitrary', 'serde', 'serde_derive', 'serde_json']
176+
smith = ['wasm-smith', 'arbitrary', 'dep:serde', 'dep:serde_derive', 'dep:serde_json']
176177
shrink = ['wasm-shrink', 'is_executable']
177178
mutate = ['wasm-mutate']
178179
dump = ['dep:wasmparser']
@@ -183,12 +184,13 @@ demangle = ['rustc-demangle', 'cpp_demangle', 'dep:wasmparser', 'wasm-encoder']
183184
component = [
184185
'wit-component',
185186
'wit-parser',
186-
'wast',
187+
'dep:wast',
187188
'wasm-encoder',
188189
'dep:wasmparser',
189-
'serde_json',
190+
'dep:serde_json',
190191
]
191-
metadata = ['dep:wasmparser', 'wasm-metadata', 'serde_json']
192+
metadata = ['dep:wasmparser', 'wasm-metadata', 'dep:serde_json']
192193
wit-smith = ['dep:wit-smith', 'arbitrary']
193194
addr2line = ['dep:addr2line', 'dep:gimli', 'dep:wasmparser']
194195
completion = ['dep:clap_complete']
196+
json-from-wast = ['dep:serde_derive', 'dep:serde_json', 'dep:wast', 'dep:serde']

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ that can be use programmatically as well:
142142
| `wasm-tools metadata add` | | Add name or producer metadata to a component or module |
143143
| `wasm-tools addr2line` | | Translate wasm offsets to filename/line numbers with DWARF |
144144
| `wasm-tools completion` | | Generate shell completion scripts for `wasm-tools` |
145+
| `wasm-tools json-from-wast` | | Convert a `*.wast` file into JSON commands |
145146

146147
[wasmparser]: https://crates.io/crates/wasmparser
147148
[wat]: https://crates.io/crates/wat

crates/wast/src/core/wast.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ impl Peek for WastRetCore<'_> {
151151
}
152152

153153
/// Either a NaN pattern (`nan:canonical`, `nan:arithmetic`) or a value of type `T`.
154-
#[derive(Clone, Debug, PartialEq)]
154+
#[derive(Copy, Clone, Debug, PartialEq)]
155155
#[allow(missing_docs)]
156156
pub enum NanPattern<T> {
157157
CanonicalNan,

crates/wast/src/wast.rs

Lines changed: 57 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -113,8 +113,7 @@ impl WastDirective<'_> {
113113
/// Returns the location in the source that this directive was defined at
114114
pub fn span(&self) -> Span {
115115
match self {
116-
WastDirective::Wat(QuoteWat::Wat(Wat::Module(m))) => m.span,
117-
WastDirective::Wat(QuoteWat::Wat(Wat::Component(c))) => c.span,
116+
WastDirective::Wat(QuoteWat::Wat(w)) => w.span(),
118117
WastDirective::Wat(QuoteWat::QuoteModule(span, _)) => *span,
119118
WastDirective::Wat(QuoteWat::QuoteComponent(span, _)) => *span,
120119
WastDirective::AssertMalformed { span, .. }
@@ -219,11 +218,23 @@ pub enum WastExecute<'a> {
219218
Invoke(WastInvoke<'a>),
220219
Wat(Wat<'a>),
221220
Get {
221+
span: Span,
222222
module: Option<Id<'a>>,
223223
global: &'a str,
224224
},
225225
}
226226

227+
impl<'a> WastExecute<'a> {
228+
/// Returns the first span for this execute statement.
229+
pub fn span(&self) -> Span {
230+
match self {
231+
WastExecute::Invoke(i) => i.span,
232+
WastExecute::Wat(i) => i.span(),
233+
WastExecute::Get { span, .. } => *span,
234+
}
235+
}
236+
}
237+
227238
impl<'a> Parse<'a> for WastExecute<'a> {
228239
fn parse(parser: Parser<'a>) -> Result<Self> {
229240
let mut l = parser.lookahead1();
@@ -232,8 +243,9 @@ impl<'a> Parse<'a> for WastExecute<'a> {
232243
} else if l.peek::<kw::module>()? || l.peek::<kw::component>()? {
233244
Ok(WastExecute::Wat(parse_wat(parser)?))
234245
} else if l.peek::<kw::get>()? {
235-
parser.parse::<kw::get>()?;
246+
let span = parser.parse::<kw::get>()?.0;
236247
Ok(WastExecute::Get {
248+
span,
237249
module: parser.parse()?,
238250
global: parser.parse()?,
239251
})
@@ -296,28 +308,47 @@ impl QuoteWat<'_> {
296308
/// Encodes this module to bytes, either by encoding the module directly or
297309
/// parsing the contents and then encoding it.
298310
pub fn encode(&mut self) -> Result<Vec<u8>, Error> {
311+
match self.to_test()? {
312+
QuoteWatTest::Binary(bytes) => Ok(bytes),
313+
QuoteWatTest::Text(text) => {
314+
let text = std::str::from_utf8(&text).map_err(|_| {
315+
let span = self.span();
316+
Error::new(span, "malformed UTF-8 encoding".to_string())
317+
})?;
318+
let buf = ParseBuffer::new(&text)?;
319+
let mut wat = parser::parse::<Wat<'_>>(&buf)?;
320+
wat.encode()
321+
}
322+
}
323+
}
324+
325+
/// Converts this to either a `QuoteWatTest::Binary` or
326+
/// `QuoteWatTest::Text` depending on what it is internally.
327+
pub fn to_test(&mut self) -> Result<QuoteWatTest, Error> {
299328
let (source, prefix) = match self {
300-
QuoteWat::Wat(m) => return m.encode(),
329+
QuoteWat::Wat(m) => return m.encode().map(QuoteWatTest::Binary),
301330
QuoteWat::QuoteModule(_, source) => (source, None),
302331
QuoteWat::QuoteComponent(_, source) => (source, Some("(component")),
303332
};
304-
let mut ret = String::new();
305-
for (span, src) in source {
306-
match std::str::from_utf8(src) {
307-
Ok(s) => ret.push_str(s),
308-
Err(_) => {
309-
return Err(Error::new(*span, "malformed UTF-8 encoding".to_string()));
310-
}
311-
}
312-
ret.push(' ');
333+
let mut ret = Vec::new();
334+
for (_, src) in source {
335+
ret.extend_from_slice(src);
336+
ret.push(b' ');
313337
}
314338
if let Some(prefix) = prefix {
315-
ret.insert_str(0, prefix);
316-
ret.push(')');
339+
ret.splice(0..0, prefix.as_bytes().iter().copied());
340+
ret.push(b')');
341+
}
342+
Ok(QuoteWatTest::Text(ret))
343+
}
344+
345+
/// Returns the defining span of this module.
346+
pub fn span(&self) -> Span {
347+
match self {
348+
QuoteWat::Wat(w) => w.span(),
349+
QuoteWat::QuoteModule(span, _) => *span,
350+
QuoteWat::QuoteComponent(span, _) => *span,
317351
}
318-
let buf = ParseBuffer::new(&ret)?;
319-
let mut wat = parser::parse::<Wat<'_>>(&buf)?;
320-
wat.encode()
321352
}
322353
}
323354

@@ -345,6 +376,14 @@ impl<'a> Parse<'a> for QuoteWat<'a> {
345376
}
346377
}
347378

379+
/// Returned from [`QuoteWat::to_test`].
380+
#[allow(missing_docs)]
381+
#[derive(Debug)]
382+
pub enum QuoteWatTest {
383+
Binary(Vec<u8>),
384+
Text(Vec<u8>),
385+
}
386+
348387
#[derive(Debug)]
349388
#[allow(missing_docs)]
350389
pub enum WastArg<'a> {

crates/wast/src/wat.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,14 @@ impl Wat<'_> {
3232
Wat::Component(c) => c.encode(),
3333
}
3434
}
35+
36+
/// Returns the defining span of this file.
37+
pub fn span(&self) -> Span {
38+
match self {
39+
Wat::Module(m) => m.span,
40+
Wat::Component(c) => c.span,
41+
}
42+
}
3543
}
3644

3745
impl<'a> Parse<'a> for Wat<'a> {

0 commit comments

Comments
 (0)