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