use crate::{
io::{Filesystem, WitxIo},
parser::{
CommentSyntax, DeclSyntax, Documented, EnumSyntax, FlagsSyntax, HandleSyntax,
ImportTypeSyntax, IntSyntax, ModuleDeclSyntax, StructSyntax, TypedefSyntax, UnionSyntax,
VariantSyntax,
},
BuiltinType, Definition, Entry, EnumDatatype, EnumVariant, FlagsDatatype, FlagsMember,
HandleDatatype, Id, IntConst, IntDatatype, IntRepr, InterfaceFunc, InterfaceFuncParam,
InterfaceFuncParamPosition, Location, Module, ModuleDefinition, ModuleEntry, ModuleImport,
ModuleImportVariant, NamedType, StructDatatype, StructMember, Type, TypePassedBy, TypeRef,
UnionDatatype, UnionVariant,
};
use std::collections::{hash_map, HashMap};
use std::path::Path;
use std::rc::Rc;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum ValidationError {
#[error("Unknown name `{name}`")]
UnknownName { name: String, location: Location },
#[error("Redefinition of name `{name}`")]
NameAlreadyExists {
name: String,
at_location: Location,
previous_location: Location,
},
#[error("Wrong kind of name `{name}`: expected {expected}, got {got}")]
WrongKindName {
name: String,
location: Location,
expected: &'static str,
got: &'static str,
},
#[error("Recursive definition of name `{name}`")]
Recursive { name: String, location: Location },
#[error("Invalid representation `{repr:?}`")]
InvalidRepr {
repr: BuiltinType,
location: Location,
},
#[error("First result type must be pass-by-value")]
InvalidFirstResultType { location: Location },
#[error("Anonymous structured types (struct, union, enum, flags, handle) are not permitted")]
AnonymousStructure { location: Location },
#[error("Invalid union field `{name}`: {reason}")]
InvalidUnionField {
name: String,
reason: String,
location: Location,
},
}
impl ValidationError {
pub fn report_with(&self, witxio: &dyn WitxIo) -> String {
use ValidationError::*;
match self {
UnknownName { location, .. }
| WrongKindName { location, .. }
| Recursive { location, .. }
| InvalidRepr { location, .. }
| InvalidFirstResultType { location, .. }
| AnonymousStructure { location, .. }
| InvalidUnionField { location, .. } => {
format!("{}\n{}", location.highlight_source_with(witxio), &self)
}
NameAlreadyExists {
at_location,
previous_location,
..
} => format!(
"{}\n{}\nOriginally defined at:\n{}",
at_location.highlight_source_with(witxio),
&self,
previous_location.highlight_source_with(witxio),
),
}
}
pub fn report(&self) -> String {
self.report_with(&Filesystem)
}
}
struct IdentValidation {
names: HashMap<String, Location>,
}
impl IdentValidation {
fn new() -> Self {
Self {
names: HashMap::new(),
}
}
fn introduce(&mut self, syntax: &str, location: Location) -> Result<Id, ValidationError> {
if let Some(introduced) = self.names.get(syntax) {
Err(ValidationError::NameAlreadyExists {
name: syntax.to_string(),
at_location: location,
previous_location: introduced.clone(),
})
} else {
self.names.insert(syntax.to_string(), location);
Ok(Id::new(syntax))
}
}
fn get(&self, syntax: &str, location: Location) -> Result<Id, ValidationError> {
if self.names.get(syntax).is_some() {
Ok(Id::new(syntax))
} else {
Err(ValidationError::UnknownName {
name: syntax.to_string(),
location,
})
}
}
}
pub struct DocValidation {
scope: IdentValidation,
pub entries: HashMap<Id, Entry>,
}
pub struct DocValidationScope<'a> {
doc: &'a mut DocValidation,
text: &'a str,
path: &'a Path,
}
impl DocValidation {
pub fn new() -> Self {
Self {
scope: IdentValidation::new(),
entries: HashMap::new(),
}
}
pub fn scope<'a>(&'a mut self, text: &'a str, path: &'a Path) -> DocValidationScope<'a> {
DocValidationScope {
doc: self,
text,
path,
}
}
}
impl DocValidationScope<'_> {
fn location(&self, span: wast::Span) -> Location {
let (line, column) = span.linecol_in(self.text);
Location {
line: line + 1,
column: column + 1,
path: self.path.to_path_buf(),
}
}
fn introduce(&mut self, name: &wast::Id<'_>) -> Result<Id, ValidationError> {
let loc = self.location(name.span());
self.doc.scope.introduce(name.name(), loc)
}
fn get(&self, name: &wast::Id<'_>) -> Result<Id, ValidationError> {
let loc = self.location(name.span());
self.doc.scope.get(name.name(), loc)
}
pub fn validate_decl(
&mut self,
decl: &DeclSyntax,
comments: &CommentSyntax,
) -> Result<Definition, ValidationError> {
match decl {
DeclSyntax::Typename(decl) => {
let name = self.introduce(&decl.ident)?;
let docs = comments.docs();
let tref = self.validate_datatype(&decl.def, true, decl.ident.span())?;
let rc_datatype = Rc::new(NamedType {
name: name.clone(),
tref,
docs,
});
self.doc
.entries
.insert(name, Entry::Typename(Rc::downgrade(&rc_datatype)));
Ok(Definition::Typename(rc_datatype))
}
DeclSyntax::Module(syntax) => {
let name = self.introduce(&syntax.name)?;
let mut module_validator = ModuleValidation::new(self);
let definitions = syntax
.decls
.iter()
.map(|d| module_validator.validate_decl(&d))
.collect::<Result<Vec<_>, _>>()?;
let rc_module = Rc::new(Module::new(
name.clone(),
definitions,
module_validator.entries,
comments.docs(),
));
self.doc
.entries
.insert(name, Entry::Module(Rc::downgrade(&rc_module)));
Ok(Definition::Module(rc_module))
}
}
}
fn validate_datatype(
&self,
syntax: &TypedefSyntax,
named: bool,
span: wast::Span,
) -> Result<TypeRef, ValidationError> {
match syntax {
TypedefSyntax::Ident(syntax) => {
let i = self.get(syntax)?;
match self.doc.entries.get(&i) {
Some(Entry::Typename(weak_ref)) => Ok(TypeRef::Name(
weak_ref.upgrade().expect("weak backref to defined type"),
)),
Some(e) => Err(ValidationError::WrongKindName {
name: i.as_str().to_string(),
location: self.location(syntax.span()),
expected: "datatype",
got: e.kind(),
}),
None => Err(ValidationError::Recursive {
name: i.as_str().to_string(),
location: self.location(syntax.span()),
}),
}
}
TypedefSyntax::Enum { .. }
| TypedefSyntax::Int { .. }
| TypedefSyntax::Flags { .. }
| TypedefSyntax::Struct { .. }
| TypedefSyntax::Union { .. }
| TypedefSyntax::Handle { .. }
if !named =>
{
Err(ValidationError::AnonymousStructure {
location: self.location(span),
})
}
other => Ok(TypeRef::Value(Rc::new(match other {
TypedefSyntax::Enum(syntax) => Type::Enum(self.validate_enum(&syntax, span)?),
TypedefSyntax::Int(syntax) => Type::Int(self.validate_int(&syntax, span)?),
TypedefSyntax::Flags(syntax) => Type::Flags(self.validate_flags(&syntax, span)?),
TypedefSyntax::Struct(syntax) => Type::Struct(self.validate_struct(&syntax, span)?),
TypedefSyntax::Union(syntax) => Type::Union(self.validate_union(&syntax, span)?),
TypedefSyntax::Handle(syntax) => Type::Handle(self.validate_handle(syntax, span)?),
TypedefSyntax::Array(syntax) => {
Type::Array(self.validate_datatype(syntax, false, span)?)
}
TypedefSyntax::Pointer(syntax) => {
Type::Pointer(self.validate_datatype(syntax, false, span)?)
}
TypedefSyntax::ConstPointer(syntax) => {
Type::ConstPointer(self.validate_datatype(syntax, false, span)?)
}
TypedefSyntax::Builtin(builtin) => Type::Builtin(*builtin),
TypedefSyntax::Ident { .. } => unreachable!(),
}))),
}
}
fn validate_enum(
&self,
syntax: &EnumSyntax,
span: wast::Span,
) -> Result<EnumDatatype, ValidationError> {
let mut enum_scope = IdentValidation::new();
let repr = self.validate_int_repr(&syntax.repr, span)?;
let variants = syntax
.members
.iter()
.map(|i| {
let name = enum_scope.introduce(i.item.name(), self.location(i.item.span()))?;
let docs = i.comments.docs();
Ok(EnumVariant { name, docs })
})
.collect::<Result<Vec<EnumVariant>, _>>()?;
Ok(EnumDatatype { repr, variants })
}
fn validate_int(
&self,
syntax: &IntSyntax,
span: wast::Span,
) -> Result<IntDatatype, ValidationError> {
let mut int_scope = IdentValidation::new();
let repr = self.validate_int_repr(&syntax.repr, span)?;
let consts = syntax
.consts
.iter()
.map(|i| {
let name =
int_scope.introduce(i.item.name.name(), self.location(i.item.name.span()))?;
let value = i.item.value;
let docs = i.comments.docs();
Ok(IntConst { name, value, docs })
})
.collect::<Result<Vec<IntConst>, _>>()?;
Ok(IntDatatype { repr, consts })
}
fn validate_flags(
&self,
syntax: &FlagsSyntax,
span: wast::Span,
) -> Result<FlagsDatatype, ValidationError> {
let mut flags_scope = IdentValidation::new();
let repr = self.validate_int_repr(&syntax.repr, span)?;
let flags = syntax
.flags
.iter()
.map(|i| {
let name = flags_scope.introduce(i.item.name(), self.location(i.item.span()))?;
let docs = i.comments.docs();
Ok(FlagsMember { name, docs })
})
.collect::<Result<Vec<FlagsMember>, _>>()?;
Ok(FlagsDatatype { repr, flags })
}
fn validate_struct(
&self,
syntax: &StructSyntax,
_span: wast::Span,
) -> Result<StructDatatype, ValidationError> {
let mut member_scope = IdentValidation::new();
let members = syntax
.fields
.iter()
.map(|f| {
let name = member_scope
.introduce(f.item.name.name(), self.location(f.item.name.span()))?;
let tref = self.validate_datatype(&f.item.type_, false, f.item.name.span())?;
let docs = f.comments.docs();
Ok(StructMember { name, tref, docs })
})
.collect::<Result<Vec<StructMember>, _>>()?;
Ok(StructDatatype { members })
}
fn validate_union(
&self,
syntax: &UnionSyntax,
span: wast::Span,
) -> Result<UnionDatatype, ValidationError> {
let mut variant_scope = IdentValidation::new();
let tag_id = self.get(&syntax.tag)?;
let (tag, mut variant_name_uses) = match self.doc.entries.get(&tag_id) {
Some(Entry::Typename(weak_ref)) => {
let named_dt = weak_ref.upgrade().expect("weak backref to defined type");
match &*named_dt.type_() {
Type::Enum(e) => {
let uses = e
.variants
.iter()
.map(|v| (v.name.clone(), false))
.collect::<HashMap<Id, bool>>();
Ok((named_dt, uses))
}
other => Err(ValidationError::WrongKindName {
name: syntax.tag.name().to_string(),
location: self.location(syntax.tag.span()),
expected: "enum",
got: other.kind(),
}),
}
}
other => Err(ValidationError::WrongKindName {
name: syntax.tag.name().to_string(),
location: self.location(syntax.tag.span()),
expected: "enum",
got: match other {
Some(e) => e.kind(),
None => "unknown",
},
}),
}?;
let variants = syntax
.fields
.iter()
.map(|v| {
let variant_name = match v.item {
VariantSyntax::Field(ref f) => &f.name,
VariantSyntax::Empty(ref name) => name,
};
let name = variant_scope
.introduce(variant_name.name(), self.location(variant_name.span()))?;
let tref = match &v.item {
VariantSyntax::Field(f) => {
Some(self.validate_datatype(&f.type_, false, variant_name.span())?)
}
VariantSyntax::Empty { .. } => None,
};
let docs = v.comments.docs();
match variant_name_uses.entry(name.clone()) {
hash_map::Entry::Occupied(mut e) => {
if *e.get() {
Err(ValidationError::InvalidUnionField {
name: variant_name.name().to_string(),
reason: "variant already defined".to_owned(),
location: self.location(variant_name.span()),
})?;
} else {
e.insert(true);
}
}
hash_map::Entry::Vacant { .. } => Err(ValidationError::InvalidUnionField {
name: variant_name.name().to_string(),
reason: format!(
"does not correspond to variant in tag `{}`",
tag.name.as_str()
),
location: self.location(variant_name.span()),
})?,
}
Ok(UnionVariant { name, tref, docs })
})
.collect::<Result<Vec<UnionVariant>, _>>()?;
let unused_variants = variant_name_uses
.iter()
.filter(|(_k, used)| **used == false)
.map(|(k, _)| k.clone())
.collect::<Vec<Id>>();
if !unused_variants.is_empty() {
Err(ValidationError::InvalidUnionField {
name: unused_variants
.iter()
.map(|i| i.as_str())
.collect::<Vec<_>>()
.join(", "),
reason: format!("missing variants from tag `{}`", tag.name.as_str()),
location: self.location(span),
})?;
}
Ok(UnionDatatype { tag, variants })
}
fn validate_handle(
&self,
syntax: &HandleSyntax,
_span: wast::Span,
) -> Result<HandleDatatype, ValidationError> {
let supertypes = syntax
.supertypes
.iter()
.map(|id_syntax| {
let id = self.get(&id_syntax)?;
match self.doc.entries.get(&id) {
Some(Entry::Typename(weak_ref)) => {
let named_dt = weak_ref.upgrade().expect("weak backref to defined type");
match &*named_dt.type_() {
Type::Handle { .. } => Ok(TypeRef::Name(named_dt)),
other => Err(ValidationError::WrongKindName {
name: id_syntax.name().to_string(),
location: self.location(id_syntax.span()),
expected: "handle",
got: other.kind(),
}),
}
}
Some(entry) => Err(ValidationError::WrongKindName {
name: id_syntax.name().to_string(),
location: self.location(id_syntax.span()),
expected: "handle",
got: entry.kind(),
}),
None => Err(ValidationError::Recursive {
name: id_syntax.name().to_string(),
location: self.location(id_syntax.span()),
}),
}
})
.collect::<Result<Vec<TypeRef>, _>>()?;
Ok(HandleDatatype { supertypes })
}
fn validate_int_repr(
&self,
type_: &BuiltinType,
span: wast::Span,
) -> Result<IntRepr, ValidationError> {
match type_ {
BuiltinType::U8 => Ok(IntRepr::U8),
BuiltinType::U16 => Ok(IntRepr::U16),
BuiltinType::U32 => Ok(IntRepr::U32),
BuiltinType::U64 => Ok(IntRepr::U64),
_ => Err(ValidationError::InvalidRepr {
repr: type_.clone(),
location: self.location(span),
}),
}
}
}
struct ModuleValidation<'a> {
doc: &'a DocValidationScope<'a>,
scope: IdentValidation,
pub entries: HashMap<Id, ModuleEntry>,
}
impl<'a> ModuleValidation<'a> {
fn new(doc: &'a DocValidationScope<'a>) -> Self {
Self {
doc,
scope: IdentValidation::new(),
entries: HashMap::new(),
}
}
fn validate_decl(
&mut self,
decl: &Documented<ModuleDeclSyntax>,
) -> Result<ModuleDefinition, ValidationError> {
match &decl.item {
ModuleDeclSyntax::Import(syntax) => {
let loc = self.doc.location(syntax.name_loc);
let name = self.scope.introduce(syntax.name, loc)?;
let variant = match syntax.type_ {
ImportTypeSyntax::Memory => ModuleImportVariant::Memory,
};
let rc_import = Rc::new(ModuleImport {
name: name.clone(),
variant,
docs: decl.comments.docs(),
});
self.entries
.insert(name, ModuleEntry::Import(Rc::downgrade(&rc_import)));
Ok(ModuleDefinition::Import(rc_import))
}
ModuleDeclSyntax::Func(syntax) => {
let loc = self.doc.location(syntax.export_loc);
let name = self.scope.introduce(syntax.export, loc)?;
let mut argnames = IdentValidation::new();
let params = syntax
.params
.iter()
.enumerate()
.map(|(ix, f)| {
Ok(InterfaceFuncParam {
name: argnames.introduce(
f.item.name.name(),
self.doc.location(f.item.name.span()),
)?,
tref: self.doc.validate_datatype(
&f.item.type_,
false,
f.item.name.span(),
)?,
position: InterfaceFuncParamPosition::Param(ix),
docs: f.comments.docs(),
})
})
.collect::<Result<Vec<_>, _>>()?;
let results = syntax
.results
.iter()
.enumerate()
.map(|(ix, f)| {
let tref =
self.doc
.validate_datatype(&f.item.type_, false, f.item.name.span())?;
if ix == 0 {
match tref.type_().passed_by() {
TypePassedBy::Value(_) => {}
_ => Err(ValidationError::InvalidFirstResultType {
location: self.doc.location(f.item.name.span()),
})?,
}
}
Ok(InterfaceFuncParam {
name: argnames.introduce(
f.item.name.name(),
self.doc.location(f.item.name.span()),
)?,
tref,
position: InterfaceFuncParamPosition::Result(ix),
docs: f.comments.docs(),
})
})
.collect::<Result<Vec<_>, _>>()?;
let noreturn = syntax.noreturn;
let rc_func = Rc::new(InterfaceFunc {
name: name.clone(),
params,
results,
noreturn,
docs: decl.comments.docs(),
});
self.entries
.insert(name, ModuleEntry::Func(Rc::downgrade(&rc_func)));
Ok(ModuleDefinition::Func(rc_func))
}
}
}
}