use miette::SourceSpan;
use semver::Version;
use wac_parser::{
Document, Expr, ExternType, ImportStatement, ImportType, InstantiationArgument, InterfaceItem,
PrimaryExpr, Statement, TypeStatement, UsePath, WorldItem, WorldItemPath, WorldRef,
};
use crate::Error;
pub struct PackageVisitor<T>(T);
impl<'a, T> PackageVisitor<T>
where
T: FnMut(&'a str, Option<&'a Version>, SourceSpan) -> bool,
{
pub fn new(cb: T) -> Self {
Self(cb)
}
pub fn visit(&mut self, doc: &'a Document) -> Result<(), Error> {
if let Some(targets) = &doc.directive.targets {
if !(self.0)(
targets.name,
targets.version.as_ref(),
targets.package_name_span(),
) {
return Ok(());
}
}
for stmt in &doc.statements {
match stmt {
Statement::Import(i) => {
if !self.import_statement(i) {
break;
}
}
Statement::Type(t) => {
if !self.type_statement(t) {
break;
}
}
Statement::Let(l) => {
if !self.expr(doc.directive.package.name, &l.expr)? {
break;
}
}
Statement::Export(e) => {
if !self.expr(doc.directive.package.name, &e.expr)? {
break;
}
}
}
}
Ok(())
}
fn import_statement(&mut self, stmt: &'a ImportStatement) -> bool {
match &stmt.ty {
ImportType::Package(p) => (self.0)(p.name, p.version.as_ref(), p.package_name_span()),
ImportType::Interface(i) => {
for item in &i.items {
if !self.interface_item(item) {
return false;
}
}
true
}
ImportType::Func(_) | ImportType::Ident(_) => true,
}
}
fn type_statement(&mut self, stmt: &'a TypeStatement) -> bool {
match stmt {
TypeStatement::Interface(i) => {
for item in &i.items {
if !self.interface_item(item) {
return false;
}
}
true
}
TypeStatement::World(w) => {
for item in &w.items {
if !self.world_item(item) {
return false;
}
}
true
}
TypeStatement::Type(_) => true,
}
}
fn interface_item(&mut self, item: &'a InterfaceItem) -> bool {
match item {
InterfaceItem::Use(u) => match &u.path {
UsePath::Package(p) => (self.0)(p.name, p.version.as_ref(), p.package_name_span()),
UsePath::Ident(_) => true,
},
InterfaceItem::Type(_) | InterfaceItem::Export(_) => true,
}
}
fn world_item(&mut self, item: &'a WorldItem) -> bool {
match item {
WorldItem::Use(u) => match &u.path {
UsePath::Package(p) => (self.0)(p.name, p.version.as_ref(), p.package_name_span()),
UsePath::Ident(_) => true,
},
WorldItem::Type(_) => true,
WorldItem::Import(i) => self.world_item_path(&i.path),
WorldItem::Export(e) => self.world_item_path(&e.path),
WorldItem::Include(i) => match &i.world {
WorldRef::Package(p) => (self.0)(p.name, p.version.as_ref(), p.package_name_span()),
WorldRef::Ident(_) => true,
},
}
}
fn world_item_path(&mut self, path: &'a WorldItemPath) -> bool {
match path {
WorldItemPath::Named(n) => match &n.ty {
ExternType::Interface(i) => {
for item in &i.items {
if !self.interface_item(item) {
return false;
}
}
true
}
ExternType::Ident(_) | ExternType::Func(_) => true,
},
WorldItemPath::Package(p) => {
(self.0)(p.name, p.version.as_ref(), p.package_name_span())
}
WorldItemPath::Ident(_) => true,
}
}
fn expr(&mut self, this: &str, expr: &'a Expr) -> Result<bool, Error> {
match &expr.primary {
PrimaryExpr::New(e) => {
if e.package.name == this {
return Err(Error::CannotInstantiateSelf {
span: e.package.span,
});
}
if !(self.0)(e.package.name, e.package.version.as_ref(), e.package.span) {
return Ok(false);
}
for arg in &e.arguments {
match arg {
InstantiationArgument::Inferred(_)
| InstantiationArgument::Spread(_)
| InstantiationArgument::Fill(_) => continue,
InstantiationArgument::Named(a) => {
if !self.expr(this, &a.expr)? {
return Ok(false);
}
}
}
}
Ok(true)
}
PrimaryExpr::Nested(e) => self.expr(this, &e.inner),
PrimaryExpr::Ident(_) => Ok(true),
}
}
}