use crate::io::{Filesystem, WitxIo};
use crate::parser::{TopLevelModule, TopLevelSyntax};
use crate::validate::{ModuleValidation, ValidationError};
use crate::{Location, Module, WitxError};
use std::collections::{hash_map::Entry, HashMap};
use std::path::{Path, PathBuf};
pub fn parse_witx(i: impl AsRef<Path>) -> Result<Module, WitxError> {
parse_witx_with(i, Filesystem)
}
pub fn parse_witx_with(i: impl AsRef<Path>, witxio: impl WitxIo) -> Result<Module, WitxError> {
_parse(i.as_ref(), &witxio, &mut Default::default(), None)
}
fn _parse(
path: &Path,
io: &dyn WitxIo,
parsed: &mut HashMap<PathBuf, Option<Module>>,
parent_location: Option<Location>,
) -> Result<Module, WitxError> {
let canon_path = io.canonicalize(path)?;
match parsed.entry(canon_path.clone()) {
Entry::Occupied(e) => match e.get() {
Some(doc) => return Ok(doc.clone()),
None => {
return Err(ValidationError::CyclicModule {
location: parent_location.unwrap(),
}
.into())
}
},
Entry::Vacant(v) => {
v.insert(None);
}
}
let input = io.fgets(&canon_path)?;
let adjust_err = |mut error: wast::Error| {
error.set_path(&path);
error.set_text(&input);
WitxError::Parse(error)
};
let buf = wast::parser::ParseBuffer::new(&input).map_err(adjust_err)?;
let doc = wast::parser::parse::<TopLevelModule>(&buf).map_err(adjust_err)?;
let file_name = path.file_stem().unwrap().to_str().unwrap();
let name = match doc.module_name {
Some(name) => name.name(),
None => file_name,
};
let mut validator = ModuleValidation::new(&input, name, path);
if let Some(name) = doc.module_name {
if file_name != "-" && file_name != name.name() {
let location = validator.location(name.span());
return Err(ValidationError::ModuleNameMismatch {
location,
module_name: name.name().to_owned(),
file_name: file_name.to_owned(),
}
.into());
}
}
let mut submodules = HashMap::new();
for t in doc.decls {
match t.item {
TopLevelSyntax::Decl(d) => {
validator
.validate_decl(&d, &t.comments)
.map_err(WitxError::Validation)?;
}
TopLevelSyntax::Use(u) => {
let path = path
.parent()
.unwrap()
.join(u.from.name())
.with_extension("witx");
let module = match submodules.entry(u.from.name()) {
Entry::Occupied(e) => e.into_mut(),
Entry::Vacant(v) => {
let location = validator.location(u.from.span());
let doc = _parse(&path, io, parsed, Some(location))?;
v.insert(doc)
}
};
validator.validate_use(u, module)?;
}
}
}
for f in doc.functions {
validator
.validate_function(&f.item, &f.comments)
.map_err(WitxError::Validation)?;
}
let doc = validator.into_module();
parsed.insert(canon_path, Some(doc.clone()));
Ok(doc)
}
#[cfg(test)]
mod test {
use super::*;
use crate::ast::*;
use crate::io::MockFs;
#[test]
fn empty() {
parse_witx_with("/a.witx", &MockFs::new(&[("/a.witx", ";; empty")])).expect("parse");
}
#[test]
fn one_use() {
let fs = &MockFs::new(&[
("/a.witx", "(module $a (use $x from $b))"),
("/b.witx", "(module $b (typename $x u8))"),
]);
parse_witx_with("/b.witx", fs).unwrap();
parse_witx_with("/a.witx", fs).unwrap();
}
#[test]
fn multi_use() {
let fs = &MockFs::new(&[
("/a.witx", "(module $a (use $b_float $c_int from $b))"),
(
"/b.witx",
"(module $b (use $c_int from $c) (typename $b_float f64))",
),
("/c.witx", "(module $c (typename $c_int u32))"),
]);
parse_witx_with("/c.witx", fs).expect("parse");
parse_witx_with("/b.witx", fs).expect("parse");
let doc = parse_witx_with("/a.witx", fs).expect("parse");
let b_float = doc.typename(&Id::new("b_float")).unwrap();
assert_eq!(**b_float.type_(), Type::Builtin(BuiltinType::F64));
let c_int = doc.typename(&Id::new("c_int")).unwrap();
assert_eq!(
**c_int.type_(),
Type::Builtin(BuiltinType::U32 {
lang_ptr_size: false
})
);
}
#[test]
fn diamond_dependency() {
let fs = &MockFs::new(&[
(
"/a.witx",
"(module $a\n(use $b_char from $b)\n(use $c_char from $c)\n)",
),
(
"/b.witx",
"(module $b (use $d_char from $d) (typename $b_char $d_char))",
),
(
"/c.witx",
"(module $c (use $d_char from $d) (typename $c_char $d_char))",
),
("/d.witx", "(module $d (typename $d_char u8))"),
]);
parse_witx_with("/d.witx", fs).expect("parse");
parse_witx_with("/c.witx", fs).expect("parse");
parse_witx_with("/b.witx", fs).expect("parse");
let doc = parse_witx_with("/a.witx", fs).expect("parse");
let b_char = doc.typename(&Id::new("b_char")).unwrap();
assert_eq!(
**b_char.type_(),
Type::Builtin(BuiltinType::U8 { lang_c_char: false })
);
assert!(doc.typename(&Id::new("d_char")).is_none());
}
#[test]
fn use_not_found() {
match parse_witx_with(
"/a.witx",
&MockFs::new(&[("/a.witx", "(module $a (use $x from $b))")]),
)
.err()
.unwrap()
{
WitxError::Io(path, _error) => assert_eq!(path, PathBuf::from("/b.witx")),
e => panic!("wrong error: {:?}", e),
}
}
}