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 &[SpecVersion::V1, SpecVersion::V2, SpecVersion::V3, SpecVersion::Latest]
157 }
158}
159
160pub fn proposal(name: impl Into<Proposal>) -> impl Iterator<Item = TestFile<'static>> {
162 let name: &'static str = name.into().into();
163 let tests = DATA.get_dir(format!("proposals/{name}")).expect("spec dir should always exist");
164
165 tests.files().map(|file| TestFile {
166 parent: name.to_string(),
167 name: file.path().file_name().unwrap_or_default().to_string_lossy().to_string(),
168 contents: file.contents_utf8().expect("file should be utf8"),
169 })
170}
171
172pub fn spec(version: impl Into<SpecVersion>) -> impl Iterator<Item = TestFile<'static>> {
174 let name = version.into().name();
175 let tests = DATA.get_dir(name).expect("spec dir should always exist");
176
177 tests.files().map(|file| TestFile {
178 parent: name.to_string(),
179 name: file.path().file_name().unwrap_or_default().to_string_lossy().to_string(),
180 contents: file.contents_utf8().expect("file should be utf8"),
181 })
182}
183
184#[derive(Debug)]
186pub struct TestFile<'a> {
187 pub parent: String,
188 pub name: String,
189 pub contents: &'a str,
190}
191
192impl<'a> TestFile<'a> {
193 pub fn name(&self) -> &str {
195 &self.name
196 }
197
198 pub fn parent(&self) -> &str {
200 &self.parent
201 }
202
203 pub fn raw(&self) -> &'a str {
205 self.contents
206 }
207
208 #[cfg(feature = "wast")]
209 pub fn wast(&self) -> wast::parser::Result<WastBuffer<'a>> {
211 let mut lexer = wast::lexer::Lexer::new(self.contents);
212 lexer.allow_confusing_unicode(true);
213 let parse_buffer = wast::parser::ParseBuffer::new_with_lexer(lexer)?;
214
215 Ok(WastBuffer { buffer: parse_buffer })
216 }
217}
218
219pub struct WastBuffer<'a> {
221 buffer: wast::parser::ParseBuffer<'a>,
224}
225
226impl Debug for WastBuffer<'_> {
227 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
228 f.debug_struct("WastBuffer").finish()
229 }
230}
231
232impl<'a> WastBuffer<'a> {
233 pub fn directives(&'a self) -> wast::parser::Result<Vec<wast::WastDirective<'a>>> {
235 Ok(wast::parser::parse::<wast::Wast<'a>>(&self.buffer)?.directives)
236 }
237}
238
239#[cfg(test)]
240mod tests {
241 use super::*;
242
243 #[test]
244 fn test_enum() {
245 for p in Proposal::all() {
246 let name = p.to_string();
247 let parsed = Proposal::from_str(&name).expect("Failed to parse proposal");
248 assert_eq!(*p, parsed);
249 }
250 }
251
252 #[test]
253 fn test_proposals() {
254 for p in Proposal::all() {
255 for test in proposal(p) {
256 if let Err(e) = test.wast().expect("Failed to lex wast").directives() {
257 panic!("Failed to parse wast for {}/{}: {e:?}", test.parent, test.name);
258 }
259 }
260 }
261 }
262
263 #[test]
264 fn test_spec_versions() {
265 for v in SpecVersion::all() {
266 for test in spec(v) {
267 if let Err(e) = test.wast().expect("Failed to lex wast").directives() {
268 panic!("Failed to parse wast: {e:?}, {test:?}");
269 }
270 }
271 }
272 }
273}