wit-parser 0.3.0

Tooling for parsing `*.wit` files and working with their contents.
Documentation
//! You can run this test suite with:
//!
//!     cargo test --test all
//!
//! An argument can be passed as well to filter, based on filename, which test
//! to run
//!
//!     cargo test --test all foo.wit

use anyhow::{bail, Context, Result};
use rayon::prelude::*;
use serde::Serialize;
use std::env;
use std::ffi::OsStr;
use std::fs;
use std::io;
use std::path::{Path, PathBuf};
use std::str;
use std::sync::atomic::{AtomicUsize, Ordering::SeqCst};
use wit_parser::*;

fn main() {
    let tests = find_tests();
    let filter = std::env::args().nth(1);

    let tests = tests
        .par_iter()
        .filter_map(|test| {
            if let Some(filter) = &filter {
                if let Some(s) = test.to_str() {
                    if !s.contains(filter) {
                        return None;
                    }
                }
            }
            let contents = fs::read(test).unwrap();
            Some((test, contents))
        })
        .collect::<Vec<_>>();

    println!("running {} test files\n", tests.len());

    let ntests = AtomicUsize::new(0);
    let errors = tests
        .par_iter()
        .filter_map(|(test, contents)| {
            Runner { ntests: &ntests }
                .run(test, contents)
                .context(format!("test {:?} failed", test))
                .err()
        })
        .collect::<Vec<_>>();

    if !errors.is_empty() {
        for msg in errors.iter() {
            eprintln!("{:?}", msg);
        }

        panic!("{} tests failed", errors.len())
    }

    println!(
        "test result: ok. {} directives passed\n",
        ntests.load(SeqCst)
    );
}

/// Recursively finds all tests in a whitelisted set of directories which we
/// then load up and test in parallel.
fn find_tests() -> Vec<PathBuf> {
    let mut tests = Vec::new();
    find_tests("tests/ui".as_ref(), &mut tests);
    tests.sort();
    return tests;

    fn find_tests(path: &Path, tests: &mut Vec<PathBuf>) {
        for f in path.read_dir().unwrap() {
            let f = f.unwrap();
            if f.file_type().unwrap().is_dir() {
                find_tests(&f.path(), tests);
                continue;
            }

            match f.path().extension().and_then(|s| s.to_str()) {
                Some("md") => {}
                Some("wit") => {}
                _ => continue,
            }
            tests.push(f.path());
        }
    }
}

struct Runner<'a> {
    ntests: &'a AtomicUsize,
}

impl Runner<'_> {
    fn run(&mut self, test: &Path, contents: &[u8]) -> Result<()> {
        let contents = str::from_utf8(contents)?;

        let result = World::parse_file(test);

        let result = if contents.contains("// parse-fail") {
            match result {
                Ok(_) => bail!("expected test to not parse but it did"),
                Err(mut e) => {
                    if let Some(err) = e.downcast_mut::<io::Error>() {
                        *err = io::Error::new(
                            io::ErrorKind::Other,
                            "some generic platform-agnostic error message",
                        );
                    }
                    normalize(test, &format!("{:?}", e))
                }
            }
        } else {
            let instance = result?;
            test_world(&instance);
            to_json(&instance)
        };

        // "foo.wit" => "foo.wit.result"
        // "foo.wit.md" => "foo.wit.md.result"
        let result_file = if test.extension() == Some(OsStr::new("md"))
            && test
                .file_stem()
                .and_then(|path| Path::new(path).extension())
                == Some(OsStr::new("wit"))
        {
            test.with_extension("md.result")
        } else {
            test.with_extension("wit.result")
        };
        if env::var_os("BLESS").is_some() {
            fs::write(&result_file, result)?;
        } else {
            let expected = fs::read_to_string(&result_file).context(format!(
                "failed to read test expectation file {:?}\nthis can be fixed with BLESS=1",
                result_file
            ))?;
            let expected = normalize(test, &expected);
            if expected != result {
                bail!(
                    "failed test: expected `{:?}` but found `{:?}`",
                    expected,
                    result
                );
            }
        }
        self.bump_ntests();
        return Ok(());

        fn normalize(test: &Path, s: &str) -> String {
            s.replace(
                &test.display().to_string(),
                &test.display().to_string().replace("\\", "/"),
            )
            .replace("\\parse-fail\\", "/parse-fail/")
            .replace("\r\n", "\n")
        }
    }

    fn bump_ntests(&self) {
        self.ntests.fetch_add(1, SeqCst);
    }
}

fn to_json(world: &World) -> String {
    #[derive(Serialize)]
    struct World {
        name: String,
        #[serde(skip_serializing_if = "Option::is_none")]
        default: Option<Interface>,
        #[serde(skip_serializing_if = "Vec::is_empty")]
        imports: Vec<(String, Interface)>,
        #[serde(skip_serializing_if = "Vec::is_empty")]
        exports: Vec<(String, Interface)>,
    }

    #[derive(Serialize)]
    struct Interface {
        #[serde(skip_serializing_if = "Vec::is_empty")]
        types: Vec<TypeDef>,
        #[serde(skip_serializing_if = "Vec::is_empty")]
        functions: Vec<Function>,
        #[serde(skip_serializing_if = "Vec::is_empty")]
        globals: Vec<Global>,
    }

    #[derive(Serialize)]
    struct Resource {
        name: String,
        #[serde(skip_serializing_if = "Option::is_none")]
        supertype: Option<String>,
        #[serde(skip_serializing_if = "Option::is_none")]
        foreign_module: Option<String>,
    }

    #[derive(Serialize)]
    struct TypeDef {
        idx: usize,
        #[serde(skip_serializing_if = "Option::is_none")]
        name: Option<String>,
        #[serde(flatten)]
        ty: Type,
        #[serde(skip_serializing_if = "Option::is_none")]
        foreign_module: Option<String>,
    }

    #[derive(Serialize)]
    #[serde(rename_all = "kebab-case")]
    enum Type {
        Primitive(String),
        Record {
            fields: Vec<(String, String)>,
        },
        Flags {
            flags: Vec<String>,
        },
        Enum {
            cases: Vec<String>,
        },
        Variant {
            cases: Vec<(String, Option<String>)>,
        },
        Tuple {
            types: Vec<String>,
        },
        Option(String),
        Result {
            ok: Option<String>,
            err: Option<String>,
        },
        Future(Option<String>),
        Stream {
            element: Option<String>,
            end: Option<String>,
        },
        List(String),
        Union {
            cases: Vec<String>,
        },
    }

    #[derive(Serialize)]
    struct Function {
        name: String,
        params: Vec<String>,
        results: Vec<String>,
    }

    #[derive(Serialize)]
    struct Global {
        name: String,
        ty: String,
    }

    let world = World {
        name: world.name.clone(),
        default: world.default.as_ref().map(translate_interface),
        imports: world
            .imports
            .iter()
            .map(|(name, iface)| (name.clone(), translate_interface(iface)))
            .collect(),
        exports: world
            .exports
            .iter()
            .map(|(name, iface)| (name.clone(), translate_interface(iface)))
            .collect(),
    };

    return serde_json::to_string_pretty(&world).unwrap();

    fn translate_interface(i: &wit_parser::Interface) -> Interface {
        let types = i
            .types
            .iter()
            .map(|(i, r)| TypeDef {
                idx: i.index(),
                name: r.name.clone(),
                ty: translate_typedef(r),
                foreign_module: r.foreign_module.clone(),
            })
            .collect::<Vec<_>>();
        let functions = i
            .functions
            .iter()
            .map(|f| Function {
                name: f.name.clone(),
                params: f.params.iter().map(|(_, ty)| translate_type(ty)).collect(),
                results: f
                    .results
                    .iter_types()
                    .map(|ty| translate_type(ty))
                    .collect(),
            })
            .collect::<Vec<_>>();
        let globals = i
            .globals
            .iter()
            .map(|g| Global {
                name: g.name.clone(),
                ty: translate_type(&g.ty),
            })
            .collect::<Vec<_>>();

        Interface {
            types,
            functions,
            globals,
        }
    }

    fn translate_typedef(ty: &wit_parser::TypeDef) -> Type {
        match &ty.kind {
            TypeDefKind::Type(t) => Type::Primitive(translate_type(t)),
            TypeDefKind::Record(r) => Type::Record {
                fields: r
                    .fields
                    .iter()
                    .map(|f| (f.name.clone(), translate_type(&f.ty)))
                    .collect(),
            },
            TypeDefKind::Tuple(t) => Type::Tuple {
                types: t.types.iter().map(|ty| translate_type(ty)).collect(),
            },
            TypeDefKind::Flags(r) => Type::Flags {
                flags: r.flags.iter().map(|f| f.name.clone()).collect(),
            },
            TypeDefKind::Enum(r) => Type::Enum {
                cases: r.cases.iter().map(|f| f.name.clone()).collect(),
            },
            TypeDefKind::Variant(v) => Type::Variant {
                cases: v
                    .cases
                    .iter()
                    .map(|f| (f.name.clone(), translate_optional_type(f.ty.as_ref())))
                    .collect(),
            },
            TypeDefKind::Option(t) => Type::Option(translate_type(t)),
            TypeDefKind::Result(r) => Type::Result {
                ok: translate_optional_type(r.ok.as_ref()),
                err: translate_optional_type(r.err.as_ref()),
            },
            TypeDefKind::Future(t) => Type::Future(translate_optional_type(t.as_ref())),
            TypeDefKind::Stream(s) => Type::Stream {
                element: translate_optional_type(s.element.as_ref()),
                end: translate_optional_type(s.end.as_ref()),
            },
            TypeDefKind::List(ty) => Type::List(translate_type(ty)),
            TypeDefKind::Union(u) => Type::Union {
                cases: u.cases.iter().map(|c| translate_type(&c.ty)).collect(),
            },
        }
    }

    fn translate_type(ty: &wit_parser::Type) -> String {
        use wit_parser::Type;
        match ty {
            Type::Bool => format!("bool"),
            Type::U8 => format!("u8"),
            Type::U16 => format!("u16"),
            Type::U32 => format!("u32"),
            Type::U64 => format!("u64"),
            Type::S8 => format!("s8"),
            Type::S16 => format!("s16"),
            Type::S32 => format!("s32"),
            Type::S64 => format!("s64"),
            Type::Float32 => format!("float32"),
            Type::Float64 => format!("float64"),
            Type::Char => format!("char"),
            Type::String => format!("string"),
            Type::Id(id) => format!("type-{}", id.index()),
        }
    }

    fn translate_optional_type(ty: Option<&wit_parser::Type>) -> Option<String> {
        ty.map(translate_type)
    }
}

fn test_world(world: &World) {
    for (_, interface) in world.imports.iter() {
        test_interface(interface);
    }
    for (_, interface) in world.exports.iter() {
        test_interface(interface);
    }
    if let Some(default) = &world.default {
        test_interface(default);
    }
}

fn test_interface(interface: &Interface) {
    let mut sizes = SizeAlign::default();
    sizes.fill(interface);
}