1use include_dir::{Dir, include_dir};
2use std::{
3 fmt::{Debug, Display},
4 str::FromStr,
5};
6
7static DATA: Dir<'_> = include_dir!("$CARGO_MANIFEST_DIR/data");
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10#[non_exhaustive]
11pub enum Proposal {
12 Annotations,
13 BulkMemoryOperations,
14 CustomPageSizes,
15 CustomDescriptors,
16 ExceptionHandling,
17 ExtendedConst,
18 FunctionReferences,
19 GC,
20 Memory64,
21 MultiMemory,
22 MultiValue,
23 MutableGlobal,
24 NontrappingFloatToIntConversions,
25 ReferenceTypes,
26 RelaxedSimd,
27 SignExtensionOps,
28 Simd,
29 TailCall,
30 Threads,
31 WideArithmetic,
32}
33
34impl Proposal {
35 pub fn all() -> &'static [Proposal] {
36 &[
37 Proposal::Annotations,
38 Proposal::BulkMemoryOperations,
39 Proposal::CustomPageSizes,
40 Proposal::CustomDescriptors,
41 Proposal::ExceptionHandling,
42 Proposal::ExtendedConst,
43 Proposal::FunctionReferences,
44 Proposal::GC,
45 Proposal::Memory64,
46 Proposal::MultiMemory,
47 Proposal::MultiValue,
48 Proposal::MutableGlobal,
49 Proposal::NontrappingFloatToIntConversions,
50 Proposal::ReferenceTypes,
51 Proposal::RelaxedSimd,
52 Proposal::SignExtensionOps,
53 Proposal::Simd,
54 Proposal::TailCall,
55 Proposal::Threads,
56 Proposal::WideArithmetic,
57 ]
58 }
59}
60
61impl Display for Proposal {
62 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
63 f.write_str((*self).into())
64 }
65}
66
67impl From<Proposal> for &'static str {
68 fn from(proposal: Proposal) -> &'static str {
69 match proposal {
70 Proposal::Annotations => "annotations",
71 Proposal::CustomPageSizes => "custom-page-sizes",
72 Proposal::CustomDescriptors => "custom-descriptors",
73 Proposal::ExceptionHandling => "exceptions",
74 Proposal::ExtendedConst => "extended-const",
75 Proposal::FunctionReferences => "function-references",
76 Proposal::GC => "gc",
77 Proposal::Memory64 => "memory64",
78 Proposal::MultiMemory => "multi-memory",
79 Proposal::Simd => "simd",
80 Proposal::RelaxedSimd => "relaxed-simd",
81 Proposal::TailCall => "tail-call",
82 Proposal::Threads => "threads",
83 Proposal::WideArithmetic => "wide-arithmetic",
84 Proposal::BulkMemoryOperations => "bulk-memory",
85 Proposal::MultiValue => "multi-value",
86 Proposal::MutableGlobal => "mutable-global",
87 Proposal::NontrappingFloatToIntConversions => "nontrapping-float-to-int-conversions",
88 Proposal::ReferenceTypes => "reference-types",
89 Proposal::SignExtensionOps => "sign-extension-ops",
90 }
91 }
92}
93
94impl From<&Proposal> for Proposal {
95 fn from(val: &Proposal) -> Self {
96 *val
97 }
98}
99
100impl FromStr for Proposal {
101 type Err = ();
102
103 fn from_str(s: &str) -> Result<Self, Self::Err> {
104 Ok(match s {
105 "annotations" => Proposal::Annotations,
106 "custom-page-sizes" => Proposal::CustomPageSizes,
107 "custom-descriptors" => Proposal::CustomDescriptors,
108 "exception-handling" | "exceptions" => Proposal::ExceptionHandling,
109 "extended-const" => Proposal::ExtendedConst,
110 "function-references" => Proposal::FunctionReferences,
111 "gc" => Proposal::GC,
112 "memory64" => Proposal::Memory64,
113 "multi-memory" => Proposal::MultiMemory,
114 "simd" => Proposal::Simd,
115 "relaxed-simd" => Proposal::RelaxedSimd,
116 "tail-call" => Proposal::TailCall,
117 "threads" => Proposal::Threads,
118 "wide-arithmetic" => Proposal::WideArithmetic,
119 "bulk-memory-operations" | "bulk-memory" => Proposal::BulkMemoryOperations,
120 "multi-value" => Proposal::MultiValue,
121 "mutable-global" => Proposal::MutableGlobal,
122 "nontrapping-float-to-int-conversions" => Proposal::NontrappingFloatToIntConversions,
123 "reference-types" => Proposal::ReferenceTypes,
124 "sign-extension-ops" => Proposal::SignExtensionOps,
125 _ => return Err(()),
126 })
127 }
128}
129
130#[derive(Debug, Clone, Copy)]
131#[non_exhaustive]
132pub enum SpecVersion {
133 V1,
134 V2,
135 V3,
136 Latest,
137}
138
139impl From<&SpecVersion> for SpecVersion {
140 fn from(val: &SpecVersion) -> Self {
141 *val
142 }
143}
144
145impl SpecVersion {
146 fn name(self) -> &'static str {
147 match self {
148 Self::V1 => "wasm-v1",
149 Self::V2 => "wasm-v2",
150 Self::V3 => "wasm-v3",
151 Self::Latest => "wasm-latest",
152 }
153 }
154
155 pub fn all() -> &'static [SpecVersion] {
156 &[
157 SpecVersion::V1,
158 SpecVersion::V2,
159 SpecVersion::V3,
160 SpecVersion::Latest,
161 ]
162 }
163}
164
165pub fn proposal(name: impl Into<Proposal>) -> impl Iterator<Item = TestFile<'static>> {
167 let name: &'static str = name.into().into();
168 let tests = DATA
169 .get_dir(format!("proposals/{name}"))
170 .expect("spec dir should always exist");
171
172 tests.files().map(|file| TestFile {
173 parent: name.to_string(),
174 name: file
175 .path()
176 .file_name()
177 .unwrap_or_default()
178 .to_string_lossy()
179 .to_string(),
180 contents: file.contents_utf8().expect("file should be utf8"),
181 })
182}
183
184pub fn spec(version: impl Into<SpecVersion>) -> impl Iterator<Item = TestFile<'static>> {
186 let name = version.into().name();
187 let tests = DATA.get_dir(name).expect("spec dir should always exist");
188
189 tests.files().map(|file| TestFile {
190 parent: name.to_string(),
191 name: file
192 .path()
193 .file_name()
194 .unwrap_or_default()
195 .to_string_lossy()
196 .to_string(),
197 contents: file.contents_utf8().expect("file should be utf8"),
198 })
199}
200
201#[derive(Debug)]
203pub struct TestFile<'a> {
204 pub parent: String,
205 pub name: String,
206 pub contents: &'a str,
207}
208
209impl<'a> TestFile<'a> {
210 pub fn name(&self) -> &str {
212 &self.name
213 }
214
215 pub fn parent(&self) -> &str {
217 &self.parent
218 }
219
220 pub fn raw(&self) -> &'a str {
222 self.contents
223 }
224
225 #[cfg(feature = "wast")]
226 pub fn wast(&self) -> wast::parser::Result<WastBuffer<'a>> {
228 let mut lexer = wast::lexer::Lexer::new(self.contents);
229 lexer.allow_confusing_unicode(true);
230 let parse_buffer = wast::parser::ParseBuffer::new_with_lexer(lexer)?;
231
232 Ok(WastBuffer {
233 buffer: parse_buffer,
234 })
235 }
236}
237
238pub struct WastBuffer<'a> {
240 buffer: wast::parser::ParseBuffer<'a>,
243}
244
245impl Debug for WastBuffer<'_> {
246 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
247 f.debug_struct("WastBuffer").finish()
248 }
249}
250
251impl<'a> WastBuffer<'a> {
252 pub fn directives(&'a self) -> wast::parser::Result<Vec<wast::WastDirective<'a>>> {
254 Ok(wast::parser::parse::<wast::Wast<'a>>(&self.buffer)?.directives)
255 }
256}
257
258#[cfg(test)]
259mod tests {
260 use super::*;
261
262 #[test]
263 fn test_enum() {
264 for p in Proposal::all() {
265 let name = p.to_string();
266 let parsed = Proposal::from_str(&name).expect("Failed to parse proposal");
267 assert_eq!(*p, parsed);
268 }
269 }
270
271 #[test]
272 fn test_proposals() {
273 for p in Proposal::all() {
274 for test in proposal(p) {
275 if let Err(e) = test.wast().expect("Failed to lex wast").directives() {
276 panic!(
277 "Failed to parse wast for {}/{}: {e:?}",
278 test.parent, test.name
279 );
280 }
281 }
282 }
283 }
284
285 #[test]
286 fn test_spec_versions() {
287 for v in SpecVersion::all() {
288 for test in spec(v) {
289 if let Err(e) = test.wast().expect("Failed to lex wast").directives() {
290 panic!("Failed to parse wast: {e:?}, {test:?}");
291 }
292 }
293 }
294 }
295}