1use either::Either;
36use nom::{
37 branch::*,
38 bytes::complete::{escaped, is_not, tag},
39 character::complete::{char, multispace0, multispace1, one_of},
40 combinator::*,
41 error::context,
42 multi::many0,
43 sequence::{delimited, preceded, tuple},
44 IResult,
45};
46
47use crate::interface::*;
48
49pub fn parse_interface(mut input: &str) -> Result<Interface, String> {
55 let mut interface = Interface::default();
56 let interface_inner = preceded(
57 tag("interface"),
58 tuple((
59 opt(preceded(space_comments, identifier)),
60 many0(parse_func_or_global),
61 )),
62 );
63 let interface_parser = preceded(space_comments, s_exp(interface_inner));
64
65 if let Result::Ok((inp, (sig_id, out))) = interface_parser(input) {
66 interface.name = sig_id.map(|s_id| s_id.to_string());
67
68 for entry in out.into_iter() {
69 match entry {
70 Either::Left(import) => {
71 if let Some(dup) = interface.imports.insert(import.get_key(), import) {
72 return Err(format!("Duplicate import found {:?}", dup));
73 }
74 }
75 Either::Right(export) => {
76 if let Some(dup) = interface.exports.insert(export.get_key(), export) {
77 return Err(format!("Duplicate export found {:?}", dup));
78 }
79 }
80 }
81 }
82 input = inp;
83 }
84 if let Ok((inp, _)) = space_comments(input) {
86 input = inp;
87 }
88 if !input.is_empty() {
89 Err(format!("Could not parse remaining input: {}", input))
90 } else {
91 Ok(interface)
92 }
93}
94
95fn parse_comment(input: &str) -> IResult<&str, ()> {
96 map(
97 preceded(multispace0, preceded(char(';'), many0(is_not("\n")))),
98 |_| (),
99 )(input)
100}
101
102fn space_comments<'a>(mut input: &'a str) -> IResult<&'a str, ()> {
105 let mut space_found = true;
106 let mut comment_found = true;
107 while space_found || comment_found {
108 let space: IResult<&'a str, _> = multispace1(input);
109 space_found = if let Result::Ok((inp, _)) = space {
110 input = inp;
111 true
112 } else {
113 false
114 };
115 comment_found = if let Result::Ok((inp, _)) = parse_comment(input) {
116 input = inp;
117 true
118 } else {
119 false
120 };
121 }
122 Ok((input, ()))
123}
124
125fn identifier(input: &str) -> IResult<&str, &str> {
127 let name_inner = escaped(is_not("\"\\"), '\\', one_of("\"n\\"));
128 context("identifier", delimited(char('"'), name_inner, char('"')))(input)
129}
130
131fn wasm_type(input: &str) -> IResult<&str, WasmType> {
133 let i32_tag = map(tag("i32"), |_| WasmType::I32);
134 let i64_tag = map(tag("i64"), |_| WasmType::I64);
135 let f32_tag = map(tag("f32"), |_| WasmType::F32);
136 let f64_tag = map(tag("f64"), |_| WasmType::F64);
137
138 alt((i32_tag, i64_tag, f32_tag, f64_tag))(input)
139}
140
141fn s_exp<'a, O1, F>(inner: F) -> impl Fn(&'a str) -> IResult<&'a str, O1>
143where
144 F: Fn(&'a str) -> IResult<&'a str, O1>,
145{
146 delimited(
147 char('('),
148 preceded(space_comments, inner),
149 preceded(space_comments, char(')')),
150 )
151}
152
153fn parse_func_or_global(input: &str) -> IResult<&str, Either<Import, Export>> {
154 preceded(space_comments, alt((func, global)))(input)
155}
156
157fn func(input: &str) -> IResult<&str, Either<Import, Export>> {
160 let param_list_inner = preceded(tag("param"), many0(preceded(space_comments, wasm_type)));
161 let param_list = opt(s_exp(param_list_inner));
162 let result_list_inner = preceded(tag("result"), many0(preceded(space_comments, wasm_type)));
163 let result_list = opt(s_exp(result_list_inner));
164 let import_id_inner = preceded(
165 tag("import"),
166 tuple((
167 preceded(space_comments, identifier),
168 preceded(space_comments, identifier),
169 )),
170 );
171 let export_id_inner = preceded(tag("export"), preceded(space_comments, identifier));
172 let func_id_inner = alt((
173 map(import_id_inner, |(ns, name)| {
174 Either::Left((ns.to_string(), name.to_string()))
175 }),
176 map(export_id_inner, |name| Either::Right(name.to_string())),
177 ));
178 let func_id = s_exp(func_id_inner);
179 let func_import_inner = context(
180 "func import inner",
181 preceded(
182 tag("func"),
183 map(
184 tuple((
185 preceded(space_comments, func_id),
186 preceded(space_comments, param_list),
187 preceded(space_comments, result_list),
188 )),
189 |(func_id, pl, rl)| match func_id {
190 Either::Left((ns, name)) => Either::Left(Import::Func {
191 namespace: ns.to_string(),
192 name: name.to_string(),
193 params: pl.unwrap_or_default(),
194 result: rl.unwrap_or_default(),
195 }),
196 Either::Right(name) => Either::Right(Export::Func {
197 name,
198 params: pl.unwrap_or_default(),
199 result: rl.unwrap_or_default(),
200 }),
201 },
202 ),
203 ),
204 );
205 s_exp(func_import_inner)(input)
206}
207
208fn global(input: &str) -> IResult<&str, Either<Import, Export>> {
211 let global_type_inner = preceded(tag("type"), preceded(space_comments, wasm_type));
212 let type_s_exp = s_exp(global_type_inner);
213 let export_inner = preceded(tag("export"), preceded(space_comments, identifier));
214 let import_inner = preceded(
215 tag("import"),
216 tuple((
217 preceded(space_comments, identifier),
218 preceded(space_comments, identifier),
219 )),
220 );
221 let global_id_inner = alt((
222 map(import_inner, |(ns, name)| {
223 Either::Left(Import::Global {
224 namespace: ns.to_string(),
225 name: name.to_string(),
226 var_type: WasmType::I32,
228 })
229 }),
230 map(export_inner, |name| {
231 Either::Right(Export::Global {
232 name: name.to_string(),
233 var_type: WasmType::I32,
235 })
236 }),
237 ));
238 let global_id = s_exp(global_id_inner);
239 let global_inner = context(
240 "global inner",
241 preceded(
242 tag("global"),
243 map(
244 tuple((
245 preceded(space_comments, global_id),
246 preceded(space_comments, type_s_exp),
247 )),
248 |(import_or_export, var_type)| match import_or_export {
249 Either::Left(Import::Global {
250 namespace, name, ..
251 }) => Either::Left(Import::Global {
252 namespace,
253 name,
254 var_type,
255 }),
256 Either::Right(Export::Global { name, .. }) => {
257 Either::Right(Export::Global { name, var_type })
258 }
259 _ => unreachable!("Invalid value interonally in parse global function"),
260 },
261 ),
262 ),
263 );
264 s_exp(global_inner)(input)
265}
266
267#[cfg(test)]
268mod test {
269 use super::*;
270 use std::collections::HashMap;
271
272 #[test]
273 fn parse_wasm_type() {
274 let i32_res = wasm_type("i32").unwrap();
275 assert_eq!(i32_res, ("", WasmType::I32));
276 let i64_res = wasm_type("i64").unwrap();
277 assert_eq!(i64_res, ("", WasmType::I64));
278 let f32_res = wasm_type("f32").unwrap();
279 assert_eq!(f32_res, ("", WasmType::F32));
280 let f64_res = wasm_type("f64").unwrap();
281 assert_eq!(f64_res, ("", WasmType::F64));
282
283 assert!(wasm_type("i128").is_err());
284 }
285
286 #[test]
287 fn parse_identifier() {
288 let inner_str = "柴は可愛すぎるだと思います";
289 let input = format!("\"{}\"", &inner_str);
290 let parse_res = identifier(&input).unwrap();
291 assert_eq!(parse_res, ("", inner_str))
292 }
293
294 #[test]
295 fn parse_global_import() {
296 let parse_res = global(r#"(global (import "env" "length") (type i32))"#)
297 .ok()
298 .and_then(|(a, b)| Some((a, b.left()?)))
299 .unwrap();
300 assert_eq!(
301 parse_res,
302 (
303 "",
304 Import::Global {
305 namespace: "env".to_string(),
306 name: "length".to_string(),
307 var_type: WasmType::I32,
308 }
309 )
310 );
311 }
312
313 #[test]
314 fn parse_global_export() {
315 let parse_res = global(r#"(global (export "length") (type i32))"#)
316 .ok()
317 .and_then(|(a, b)| Some((a, b.right()?)))
318 .unwrap();
319 assert_eq!(
320 parse_res,
321 (
322 "",
323 Export::Global {
324 name: "length".to_string(),
325 var_type: WasmType::I32,
326 }
327 )
328 );
329 }
330
331 #[test]
332 fn parse_func_import() {
333 let parse_res = func(r#"(func (import "ns" "name") (param f64 i32) (result f64 i32))"#)
334 .ok()
335 .and_then(|(a, b)| Some((a, b.left()?)))
336 .unwrap();
337 assert_eq!(
338 parse_res,
339 (
340 "",
341 Import::Func {
342 namespace: "ns".to_string(),
343 name: "name".to_string(),
344 params: vec![WasmType::F64, WasmType::I32],
345 result: vec![WasmType::F64, WasmType::I32],
346 }
347 )
348 );
349 }
350
351 #[test]
352 fn parse_func_export() {
353 let parse_res = func(r#"(func (export "name") (param f64 i32) (result f64 i32))"#)
354 .ok()
355 .and_then(|(a, b)| Some((a, b.right()?)))
356 .unwrap();
357 assert_eq!(
358 parse_res,
359 (
360 "",
361 Export::Func {
362 name: "name".to_string(),
363 params: vec![WasmType::F64, WasmType::I32],
364 result: vec![WasmType::F64, WasmType::I32],
365 }
366 )
367 );
368
369 let parse_res = func(r#"(func (export "name"))"#)
370 .ok()
371 .and_then(|(a, b)| Some((a, b.right()?)))
372 .unwrap();
373 assert_eq!(
374 parse_res,
375 (
376 "",
377 Export::Func {
378 name: "name".to_string(),
379 params: vec![],
380 result: vec![],
381 }
382 )
383 )
384 }
385
386 #[test]
387 fn parse_imports_test() {
388 let parse_imports = |in_str| {
389 many0(parse_func_or_global)(in_str)
390 .map(|(a, b)| {
391 (
392 a,
393 b.into_iter().filter_map(|x| x.left()).collect::<Vec<_>>(),
394 )
395 })
396 .unwrap()
397 };
398 let parse_res =
399 parse_imports(r#"(func (import "ns" "name") (param f64 i32) (result f64 i32))"#);
400 assert_eq!(
401 parse_res,
402 (
403 "",
404 vec![Import::Func {
405 namespace: "ns".to_string(),
406 name: "name".to_string(),
407 params: vec![WasmType::F64, WasmType::I32],
408 result: vec![WasmType::F64, WasmType::I32],
409 }]
410 )
411 );
412
413 let parse_res = parse_imports(
414 r#"(func (import "ns" "name")
415 (param f64 i32) (result f64 i32))
416 ( global ( import "env" "length" ) ( type
417 ;; i32 is the best type
418 i32 )
419 )
420 (func (import "ns" "name2") (param f32
421 i64)
422 ;; The return value comes next
423 (
424 result
425 f64
426 i32
427 )
428 )"#,
429 );
430 assert_eq!(
431 parse_res,
432 (
433 "",
434 vec![
435 Import::Func {
436 namespace: "ns".to_string(),
437 name: "name".to_string(),
438 params: vec![WasmType::F64, WasmType::I32],
439 result: vec![WasmType::F64, WasmType::I32],
440 },
441 Import::Global {
442 namespace: "env".to_string(),
443 name: "length".to_string(),
444 var_type: WasmType::I32,
445 },
446 Import::Func {
447 namespace: "ns".to_string(),
448 name: "name2".to_string(),
449 params: vec![WasmType::F32, WasmType::I64],
450 result: vec![WasmType::F64, WasmType::I32],
451 },
452 ]
453 )
454 );
455 }
456
457 #[test]
458 fn top_level_test() {
459 let parse_res = parse_interface(
460 r#" (interface
461 (func (import "ns" "name") (param f64 i32) (result f64 i32))
462 (func (export "name2") (param) (result i32))
463 (global (import "env" "length") (type f64)))"#,
464 )
465 .unwrap();
466
467 let imports = vec![
468 Import::Func {
469 namespace: "ns".to_string(),
470 name: "name".to_string(),
471 params: vec![WasmType::F64, WasmType::I32],
472 result: vec![WasmType::F64, WasmType::I32],
473 },
474 Import::Global {
475 namespace: "env".to_string(),
476 name: "length".to_string(),
477 var_type: WasmType::F64,
478 },
479 ];
480 let exports = vec![Export::Func {
481 name: "name2".to_string(),
482 params: vec![],
483 result: vec![WasmType::I32],
484 }];
485 let import_map = imports
486 .into_iter()
487 .map(|entry| (entry.get_key(), entry))
488 .collect::<HashMap<(String, String), Import>>();
489 let export_map = exports
490 .into_iter()
491 .map(|entry| (entry.get_key(), entry))
492 .collect::<HashMap<String, Export>>();
493 assert_eq!(
494 parse_res,
495 Interface {
496 name: None,
497 imports: import_map,
498 exports: export_map,
499 }
500 );
501 }
502
503 #[test]
504 fn duplicates_not_allowed() {
505 let parse_res = parse_interface(
506 r#" (interface "sig_name" (func (import "ns" "name") (param f64 i32) (result f64 i32))
507; test comment
508 ;; hello
509 (func (import "ns" "name") (param) (result i32))
510 (global (export "length") (type f64)))
511
512"#,
513 );
514
515 assert!(parse_res.is_err());
516 }
517
518 #[test]
519 fn test_comment_space_parsing() {
520 let parse_res = space_comments(" ").unwrap();
521 assert_eq!(parse_res, ("", ()));
522 let parse_res = space_comments("").unwrap();
523 assert_eq!(parse_res, ("", ()));
524 let parse_res = space_comments("; hello\n").unwrap();
525 assert_eq!(parse_res, ("", ()));
526 let parse_res = space_comments("abc").unwrap();
527 assert_eq!(parse_res, ("abc", ()));
528 let parse_res = space_comments("\n ; hello\n ").unwrap();
529 assert_eq!(parse_res, ("", ()));
530 let parse_res = space_comments("\n ; hello\n ; abc\n\n ; hello\n").unwrap();
531 assert_eq!(parse_res, ("", ()));
532 }
533
534 #[test]
535 fn test_param_elision() {
536 let parse_res = parse_interface(
537 r#" (interface "interface_name" (func (import "ns" "name") (result f64 i32))
538(func (export "name")))
539"#,
540 )
541 .unwrap();
542
543 let imports = vec![Import::Func {
544 namespace: "ns".to_string(),
545 name: "name".to_string(),
546 params: vec![],
547 result: vec![WasmType::F64, WasmType::I32],
548 }];
549 let exports = vec![Export::Func {
550 name: "name".to_string(),
551 params: vec![],
552 result: vec![],
553 }];
554 let import_map = imports
555 .into_iter()
556 .map(|entry| (entry.get_key(), entry))
557 .collect::<HashMap<(String, String), Import>>();
558 let export_map = exports
559 .into_iter()
560 .map(|entry| (entry.get_key(), entry))
561 .collect::<HashMap<String, Export>>();
562 assert_eq!(
563 parse_res,
564 Interface {
565 name: Some("interface_name".to_string()),
566 imports: import_map,
567 exports: export_map,
568 }
569 );
570 }
571
572 #[test]
573 fn typo_gets_caught() {
574 let interface_src = r#"
575(interface "interface_id"
576(func (import "env" "do_panic") (params i32 i64))
577(global (import "length") (type i32)))"#;
578 let result = parse_interface(interface_src);
579 assert!(result.is_err());
580 }
581
582 #[test]
583 fn parse_trailing_spaces_on_interface() {
584 let parse_res = parse_interface(
585 r#" (interface "really_good_interface" (func (import "ns" "name") (param f64 i32) (result f64 i32))
586; test comment
587 ;; hello
588 (global (import "ns" "length") (type f64))
589)
590
591"#,
592 );
593
594 assert!(parse_res.is_ok());
595 }
596}