diff --git a/Cargo.lock b/Cargo.lock index 6d25cdff74..dd46f31d47 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1653,6 +1653,7 @@ dependencies = [ "wast", "wat", "wit-component", + "wit-encoder", "wit-parser 0.210.0", "wit-smith", ] @@ -2138,6 +2139,14 @@ dependencies = [ "wit-parser 0.210.0", ] +[[package]] +name = "wit-encoder" +version = "0.210.0" +dependencies = [ + "pretty_assertions", + "semver", +] + [[package]] name = "wit-parser" version = "0.202.0" diff --git a/Cargo.toml b/Cargo.toml index ed66749879..c3d369d3e6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,6 +37,7 @@ members = [ 'crates/fuzz-stats', 'crates/wasm-mutate-stats', 'fuzz', + 'crates/wit-encoder', 'crates/wit-parser/fuzz', 'crates/wit-component/dl', 'playground/component', @@ -94,6 +95,7 @@ wasmprinter = { version = "0.210.0", path = "crates/wasmprinter" } wast = { version = "210.0.0", path = "crates/wast" } wat = { version = "1.210.0", path = "crates/wat" } wit-component = { version = "0.210.0", path = "crates/wit-component" } +wit-encoder = { version = "0.210.0", path = "crates/wit-encoder" } wit-parser = { version = "0.210.0", path = "crates/wit-parser" } wit-smith = { version = "0.210.0", path = "crates/wit-smith" } @@ -140,6 +142,7 @@ cpp_demangle = { version = "0.4.0", optional = true } # Dependencies of `component` wit-component = { workspace = true, optional = true, features = ['dummy-module', 'wat', 'semver-check'] } +wit-encoder = { workspace = true, optional = true } wit-parser = { workspace = true, optional = true, features = ['decoding', 'wat', 'serde'] } wast = { workspace = true, optional = true } @@ -208,6 +211,7 @@ compose = ['wasm-compose', 'dep:wasmparser'] demangle = ['rustc-demangle', 'cpp_demangle', 'dep:wasmparser', 'wasm-encoder'] component = [ 'wit-component', + 'wit-encoder', 'wit-parser', 'dep:wast', 'wasm-encoder', diff --git a/ci/publish.rs b/ci/publish.rs index 8103ead618..60418a2982 100644 --- a/ci/publish.rs +++ b/ci/publish.rs @@ -28,6 +28,7 @@ const CRATES_TO_PUBLISH: &[&str] = &[ "wit-parser", "wasm-metadata", "wit-component", + "wit-encoder", "wasm-compose", "wit-smith", "wasm-tools", diff --git a/crates/wit-encoder/Cargo.toml b/crates/wit-encoder/Cargo.toml new file mode 100644 index 0000000000..ae5da01377 --- /dev/null +++ b/crates/wit-encoder/Cargo.toml @@ -0,0 +1,15 @@ +[package] +description = "A WIT encoder for Rust" +documentation = "https://docs.rs/wit-encoder" +edition.workspace = true +license = "Apache-2.0 WITH LLVM-exception" +name = "wit-encoder" +repository = "https://github.com/bytecodealliance/wasm-tools/tree/main/crates/wit-encoder" +version.workspace = true + +[lints] +workspace = true + +[dependencies] +semver = { workspace = true } +pretty_assertions = { workspace = true } diff --git a/crates/wit-encoder/src/docs.rs b/crates/wit-encoder/src/docs.rs new file mode 100644 index 0000000000..137fd744e7 --- /dev/null +++ b/crates/wit-encoder/src/docs.rs @@ -0,0 +1,37 @@ +use std::fmt; + +use crate::{Render, RenderOpts}; + +/// Documentation +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] +pub struct Docs { + contents: String, +} + +impl Docs { + pub fn new(contents: impl Into) -> Self { + Self { + contents: contents.into(), + } + } +} + +impl From for Docs +where + S: Into, +{ + fn from(value: S) -> Self { + Self { + contents: value.into(), + } + } +} + +impl Render for Docs { + fn render(&self, f: &mut fmt::Formatter<'_>, opts: &RenderOpts) -> fmt::Result { + for line in self.contents.lines() { + write!(f, "{}/// {}\n", opts.spaces(), line)?; + } + Ok(()) + } +} diff --git a/crates/wit-encoder/src/enum_.rs b/crates/wit-encoder/src/enum_.rs new file mode 100644 index 0000000000..80c0afc89b --- /dev/null +++ b/crates/wit-encoder/src/enum_.rs @@ -0,0 +1,59 @@ +use crate::{Docs, Ident}; + +/// A variant without a payload +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] +pub struct Enum { + pub(crate) cases: Vec, +} + +impl FromIterator for Enum +where + C: Into, +{ + fn from_iter>(iter: T) -> Self { + Self { + cases: iter.into_iter().map(|c| c.into()).collect(), + } + } +} + +impl Enum { + pub fn cases(&self) -> &[EnumCase] { + &self.cases + } + + pub fn cases_mut(&mut self) -> &mut Vec { + &mut self.cases + } +} + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct EnumCase { + pub(crate) name: Ident, + pub(crate) docs: Option, +} + +impl From for EnumCase +where + N: Into, +{ + fn from(value: N) -> Self { + Self { + name: value.into(), + docs: None, + } + } +} + +impl EnumCase { + pub fn new(name: impl Into) -> Self { + Self { + name: name.into(), + docs: None, + } + } + + pub fn docs(&mut self, docs: Option>) { + self.docs = docs.map(|d| d.into()); + } +} diff --git a/crates/wit-encoder/src/flags.rs b/crates/wit-encoder/src/flags.rs new file mode 100644 index 0000000000..81e2fd0f4a --- /dev/null +++ b/crates/wit-encoder/src/flags.rs @@ -0,0 +1,62 @@ +use crate::{ident::Ident, Docs}; + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Flags { + pub(crate) flags: Vec, +} + +impl Flags { + pub fn new(flags: impl IntoIterator>) -> Self { + Self { + flags: flags.into_iter().map(|f| f.into()).collect(), + } + } + + pub fn flags(&self) -> &[Flag] { + &self.flags + } + + pub fn flags_mut(&mut self) -> &mut Vec { + &mut self.flags + } +} + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Flag { + pub(crate) name: Ident, + pub(crate) docs: Option, +} + +impl Flag { + pub fn new(name: impl Into) -> Self { + Flag { + name: name.into(), + docs: None, + } + } + + pub fn docs(&mut self, docs: Option>) { + self.docs = docs.map(|d| d.into()); + } +} + +impl Into for (T,) +where + T: Into, +{ + fn into(self) -> Flag { + Flag::new(self.0) + } +} + +impl Into for (T, D) +where + T: Into, + D: Into, +{ + fn into(self) -> Flag { + let mut flag = Flag::new(self.0); + flag.docs(Some(self.1)); + flag + } +} diff --git a/crates/wit-encoder/src/function.rs b/crates/wit-encoder/src/function.rs new file mode 100644 index 0000000000..fa3e81e2f2 --- /dev/null +++ b/crates/wit-encoder/src/function.rs @@ -0,0 +1,168 @@ +use std::fmt::{self, Display}; + +use crate::{ident::Ident, Docs, Type}; + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] +pub struct Params { + items: Vec<(Ident, Type)>, +} + +impl From<(N, Type)> for Params +where + N: Into, +{ + fn from(value: (N, Type)) -> Self { + Self { + items: vec![(value.0.into(), value.1)], + } + } +} + +impl FromIterator<(N, Type)> for Params +where + N: Into, +{ + fn from_iter>(iter: T) -> Self { + Self { + items: iter.into_iter().map(|(n, t)| (n.into(), t)).collect(), + } + } +} + +impl Display for Params { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut peekable = self.items.iter().peekable(); + while let Some((name, type_)) = peekable.next() { + write!(f, "{}: {}", name, type_)?; + if peekable.peek().is_some() { + write!(f, ", ")?; + } + } + Ok(()) + } +} + +impl Params { + pub fn empty() -> Self { + Self::default() + } + + pub fn items(&self) -> &Vec<(Ident, Type)> { + &self.items + } + + pub fn items_mut(&mut self) -> &mut Vec<(Ident, Type)> { + &mut self.items + } +} + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum Results { + Named(Params), + Anon(Type), +} + +impl Display for Results { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Results::Anon(type_) => type_.fmt(f)?, + Results::Named(vals) => { + if !vals.items.is_empty() { + write!(f, "(")?; + let mut peekable = vals.items.iter().peekable(); + while let Some((name, type_)) = peekable.next() { + write!(f, "{}: {}", name, type_)?; + if peekable.peek().is_some() { + write!(f, ", ")?; + } + } + write!(f, ")")?; + } + } + }; + Ok(()) + } +} + +impl Default for Results { + fn default() -> Self { + Results::empty() + } +} + +impl From for Results { + fn from(value: Type) -> Self { + Results::Anon(value) + } +} + +impl FromIterator<(N, Type)> for Results +where + N: Into, +{ + fn from_iter>(iter: T) -> Self { + Results::Named(Params::from_iter(iter)) + } +} + +impl Results { + // For the common case of an empty results list. + pub fn empty() -> Results { + Results::Named(Default::default()) + } + + pub fn anon(type_: Type) -> Results { + Results::Anon(type_) + } + + pub fn named(types: impl IntoIterator, Type)>) -> Results { + Results::Named( + types + .into_iter() + .map(|(name, ty)| (name.into(), ty)) + .collect(), + ) + } + + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + pub fn len(&self) -> usize { + match self { + Results::Named(params) => params.items().len(), + Results::Anon(_) => 1, + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct StandaloneFunc { + pub(crate) name: Ident, + pub(crate) params: Params, + pub(crate) results: Results, + pub(crate) docs: Option, +} + +impl StandaloneFunc { + pub fn new(name: impl Into) -> Self { + Self { + name: name.into(), + params: Params::empty(), + results: Results::empty(), + docs: None, + } + } + + pub fn params(&mut self, params: impl Into) { + self.params = params.into(); + } + + pub fn results(&mut self, results: impl Into) { + self.results = results.into(); + } + + pub fn docs(&mut self, docs: Option>) { + self.docs = docs.map(|d| d.into()); + } +} diff --git a/crates/wit-encoder/src/ident.rs b/crates/wit-encoder/src/ident.rs new file mode 100644 index 0000000000..0d9b86a480 --- /dev/null +++ b/crates/wit-encoder/src/ident.rs @@ -0,0 +1,40 @@ +use std::{borrow::Cow, fmt}; + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Ident(Cow<'static, str>); + +impl Ident { + pub fn new(s: impl Into>) -> Self { + let s: Cow<'static, str> = s.into(); + if is_keyword(&s) { + Self(Cow::Owned(format!("%{}", s))) + } else { + Self(s) + } + } +} + +impl From for Ident +where + S: Into>, +{ + fn from(value: S) -> Self { + Self::new(value) + } +} + +impl fmt::Display for Ident { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} + +fn is_keyword(name: &str) -> bool { + match name { + "u8" | "u16" | "u32" | "u64" | "s8" | "s16" | "s32" | "s64" | "float32" | "float64" + | "char" | "bool" | "string" | "tuple" | "list" | "option" | "result" | "use" | "type" + | "resource" | "func" | "record" | "enum" | "flags" | "variant" | "static" + | "interface" | "world" | "import" | "export" | "package" => true, + _ => false, + } +} diff --git a/crates/wit-encoder/src/interface.rs b/crates/wit-encoder/src/interface.rs new file mode 100644 index 0000000000..943a36d04f --- /dev/null +++ b/crates/wit-encoder/src/interface.rs @@ -0,0 +1,80 @@ +use std::fmt; + +use crate::{Docs, Ident, Render, RenderOpts, StandaloneFunc, TypeDef}; + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Interface { + /// Name of this interface. + pub(crate) name: Ident, + + // Interface items + pub(crate) items: Vec, + + /// Documentation associated with this interface. + pub(crate) docs: Option, +} + +impl Interface { + /// Create a new instance of `Interface`. + pub fn new(name: impl Into) -> Self { + Self { + name: name.into(), + items: vec![], + docs: None, + } + } + + /// Add a `TypeDef` to the interface + pub fn type_def(&mut self, type_def: TypeDef) { + self.items.push(InterfaceItem::TypeDef(type_def)); + } + + /// Add an `Function` to the interface + pub fn function(&mut self, function: StandaloneFunc) { + self.items.push(InterfaceItem::Function(function)); + } + + pub fn items(&self) -> &[InterfaceItem] { + &self.items + } + + pub fn functions_mut(&mut self) -> &mut Vec { + &mut self.items + } + + /// Set the documentation of this interface. + pub fn docs(&mut self, docs: Option>) { + self.docs = docs.map(|d| d.into()); + } +} + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum InterfaceItem { + TypeDef(TypeDef), + Function(StandaloneFunc), +} + +pub type InterfaceItems = Vec; + +impl Render for InterfaceItems { + fn render(&self, f: &mut fmt::Formatter<'_>, opts: &RenderOpts) -> fmt::Result { + for item in self { + match item { + InterfaceItem::TypeDef(type_def) => { + type_def.render(f, opts)?; + } + InterfaceItem::Function(func) => { + if let Some(docs) = &func.docs { + docs.render(f, opts)?; + } + write!(f, "{}{}: func({})", opts.spaces(), func.name, func.params,)?; + if !func.results.is_empty() { + write!(f, " -> {}", func.results)?; + } + write!(f, ";\n")?; + } + } + } + Ok(()) + } +} diff --git a/crates/wit-encoder/src/lib.rs b/crates/wit-encoder/src/lib.rs new file mode 100644 index 0000000000..908904da46 --- /dev/null +++ b/crates/wit-encoder/src/lib.rs @@ -0,0 +1,36 @@ +//! A WIT encoder for Rust. +//! +//! This crate is modeled after the `wasm-encoder` crate but is used to encode +//! WIT documents instead of WebAssembly modules. + +mod docs; +mod enum_; +mod flags; +mod function; +mod ident; +mod interface; +mod package; +mod record; +mod render; +mod resource; +mod result; +mod tuple; +mod ty; +mod variant; +mod world; + +pub use docs::*; +pub use enum_::*; +pub use flags::*; +pub use function::*; +pub use ident::*; +pub use interface::*; +pub use package::*; +pub use record::*; +pub use render::*; +pub use resource::*; +pub use result::*; +pub use tuple::*; +pub use ty::*; +pub use variant::*; +pub use world::*; diff --git a/crates/wit-encoder/src/package.rs b/crates/wit-encoder/src/package.rs new file mode 100644 index 0000000000..d25abfc4e8 --- /dev/null +++ b/crates/wit-encoder/src/package.rs @@ -0,0 +1,120 @@ +use std::fmt; + +use semver::Version; + +use crate::{ident::Ident, Interface, Render, RenderOpts, World}; + +/// A WIT package. +/// +/// A package is a collection of interfaces and worlds. Packages additionally +/// have a unique identifier that affects generated components and uniquely +/// identifiers this particular package. +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Package { + /// A unique name corresponding to this package. + name: PackageName, + + /// World items + items: Vec, +} + +impl Package { + /// Create a new instance of `Package`. + pub fn new(name: PackageName) -> Self { + Self { + name, + items: vec![], + } + } + + /// Add an `Interface` to the package + pub fn interface(&mut self, interface: Interface) { + self.items.push(PackageItem::Interface(interface)) + } + + /// Add a `World` to the package + pub fn world(&mut self, world: World) { + self.items.push(PackageItem::World(world)) + } +} + +impl Render for Package { + fn render(&self, f: &mut fmt::Formatter<'_>, opts: &RenderOpts) -> fmt::Result { + write!(f, "{}package {};\n", opts.spaces(), self.name)?; + write!(f, "\n")?; + for item in &self.items { + match item { + PackageItem::Interface(interface) => { + if let Some(docs) = &interface.docs { + docs.render(f, opts)?; + } + write!(f, "{}interface {} {{\n", opts.spaces(), interface.name)?; + interface.items.render(f, &opts.indent())?; + write!(f, "{}}}\n", opts.spaces())?; + } + PackageItem::World(world) => { + world.render(f, opts)?; + } + } + } + Ok(()) + } +} + +impl fmt::Display for Package { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.render(f, &RenderOpts::default()) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum PackageItem { + Interface(Interface), + World(World), +} + +/// A structure used to keep track of the name of a package, containing optional +/// information such as a namespace and version information. +/// +/// This is directly encoded as an "ID" in the binary component representation +/// with an interfaced tacked on as well. +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct PackageName { + /// A namespace such as `wasi` in `wasi:foo/bar` + namespace: String, + /// The kebab-name of this package, which is always specified. + name: Ident, + /// Optional major/minor version information. + version: Option, +} + +impl PackageName { + /// Create a new instance of `PackageName` + pub fn new( + namespace: impl Into, + name: impl Into, + version: Option, + ) -> Self { + Self { + namespace: namespace.into(), + name: name.into(), + version, + } + } +} + +impl From for String { + fn from(name: PackageName) -> String { + name.to_string() + } +} + +impl fmt::Display for PackageName { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}:{}", self.namespace, self.name)?; + if let Some(version) = &self.version { + write!(f, "@{version}")?; + } + Ok(()) + } +} diff --git a/crates/wit-encoder/src/record.rs b/crates/wit-encoder/src/record.rs new file mode 100644 index 0000000000..4280ca85d9 --- /dev/null +++ b/crates/wit-encoder/src/record.rs @@ -0,0 +1,64 @@ +use crate::{ident::Ident, Docs, Type}; + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Record { + pub(crate) fields: Vec, +} + +impl Record { + pub fn new(fields: impl IntoIterator>) -> Self { + Self { + fields: fields.into_iter().map(|f| f.into()).collect(), + } + } + + pub fn fields(&self) -> &[Field] { + &self.fields + } + + pub fn fields_mut(&mut self) -> &mut Vec { + &mut self.fields + } +} + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Field { + pub(crate) name: Ident, + pub(crate) ty: Type, + pub(crate) docs: Option, +} + +impl Field { + pub fn new(name: impl Into, ty: Type) -> Self { + Self { + name: name.into(), + ty, + docs: None, + } + } + + pub fn docs(&mut self, docs: Option>) { + self.docs = docs.map(|d| d.into()); + } +} + +impl Into for (N, Type) +where + N: Into, +{ + fn into(self) -> Field { + Field::new(self.0, self.1) + } +} + +impl Into for (N, Type, D) +where + N: Into, + D: Into, +{ + fn into(self) -> Field { + let mut field = Field::new(self.0, self.1); + field.docs(Some(self.2.into())); + field + } +} diff --git a/crates/wit-encoder/src/render.rs b/crates/wit-encoder/src/render.rs new file mode 100644 index 0000000000..2339e32077 --- /dev/null +++ b/crates/wit-encoder/src/render.rs @@ -0,0 +1,50 @@ +use std::fmt; + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct RenderOpts { + /// width of each indent + pub indent_width: usize, + /// current indent depth + pub ident_count: usize, +} + +impl Default for RenderOpts { + fn default() -> Self { + Self { + indent_width: 4, + ident_count: 0, + } + } +} + +impl RenderOpts { + /// Indent + /// + /// This will clone Self, and increment self.ident_count. + pub fn indent(&self) -> Self { + Self { + indent_width: self.indent_width, + ident_count: self.ident_count + 1, + } + } + + /// Outdent + /// + /// This will clone Self, and decrement self.ident_count. + pub fn outdent(&self) -> Self { + Self { + indent_width: self.indent_width, + ident_count: self.ident_count - 1, + } + } + + /// Get the actual characters + pub fn spaces(&self) -> String { + let space_count = self.indent_width * self.ident_count; + " ".repeat(space_count) + } +} + +pub trait Render { + fn render(&self, f: &mut fmt::Formatter<'_>, options: &RenderOpts) -> fmt::Result; +} diff --git a/crates/wit-encoder/src/resource.rs b/crates/wit-encoder/src/resource.rs new file mode 100644 index 0000000000..0d78c4cefd --- /dev/null +++ b/crates/wit-encoder/src/resource.rs @@ -0,0 +1,96 @@ +use crate::{ident::Ident, Docs, Params, Results}; + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Resource { + pub funcs: Vec, +} + +impl Resource { + pub fn empty() -> Self { + Self { funcs: vec![] } + } + + pub fn func(&mut self, func: ResourceFunc) { + self.funcs.push(func); + } + + pub fn funcs(&self) -> &[ResourceFunc] { + &self.funcs + } + + pub fn funcs_mut(&mut self) -> &mut Vec { + &mut self.funcs + } +} + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct ResourceFunc { + pub(crate) kind: ResourceFuncKind, + pub(crate) params: Params, + pub(crate) docs: Option, +} + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum ResourceFuncKind { + Method(Ident, Results), + Static(Ident, Results), + Constructor, +} + +impl ResourceFunc { + pub fn method(name: impl Into) -> Self { + Self { + kind: ResourceFuncKind::Method(name.into(), Results::empty()), + params: Params::empty(), + docs: None, + } + } + + pub fn static_(name: impl Into) -> Self { + Self { + kind: ResourceFuncKind::Static(name.into(), Results::empty()), + params: Params::empty(), + docs: None, + } + } + + pub fn constructor() -> Self { + Self { + kind: ResourceFuncKind::Constructor, + params: Params::empty(), + docs: None, + } + } + + pub fn name(&mut self, name: impl Into) { + match &self.kind { + ResourceFuncKind::Method(_, results) => { + self.kind = ResourceFuncKind::Method(name.into(), results.clone()) + } + ResourceFuncKind::Static(_, results) => { + self.kind = ResourceFuncKind::Static(name.into(), results.clone()) + } + ResourceFuncKind::Constructor => panic!("constructors cannot have a name"), + } + } + + pub fn params(&mut self, params: impl Into) { + self.params = params.into(); + } + + pub fn results(&mut self, results: impl Into) { + match &self.kind { + ResourceFuncKind::Method(name, _) => { + self.kind = ResourceFuncKind::Method(name.clone(), results.into()) + } + ResourceFuncKind::Static(name, _) => { + self.kind = ResourceFuncKind::Static(name.clone(), results.into()) + } + ResourceFuncKind::Constructor => panic!("constructors cannot have results"), + } + } + + pub fn docs(&mut self, docs: Option>) { + self.docs = docs.map(|d| d.into()); + } +} diff --git a/crates/wit-encoder/src/result.rs b/crates/wit-encoder/src/result.rs new file mode 100644 index 0000000000..de4dc9cbfa --- /dev/null +++ b/crates/wit-encoder/src/result.rs @@ -0,0 +1,59 @@ +use std::fmt::Display; + +use crate::Type; + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Result_ { + ok: Option, + err: Option, +} + +impl Result_ { + pub fn ok(type_: Type) -> Self { + Self { + ok: Some(type_), + err: None, + } + } + pub fn err(type_: Type) -> Self { + Self { + ok: None, + err: Some(type_), + } + } + pub fn both(ok: Type, err: Type) -> Self { + Self { + ok: Some(ok), + err: Some(err), + } + } + pub fn empty() -> Self { + Self { + ok: None, + err: None, + } + } + pub fn is_empty(&self) -> bool { + self.ok.is_none() && self.err.is_none() + } +} + +impl Display for Result_ { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "result")?; + if !self.is_empty() { + write!(f, "<")?; + if let Some(type_) = &self.ok { + type_.fmt(f)?; + } else { + write!(f, "_")?; + } + if let Some(type_) = &self.err { + write!(f, ", ")?; + type_.fmt(f)?; + } + write!(f, ">")?; + } + Ok(()) + } +} diff --git a/crates/wit-encoder/src/tuple.rs b/crates/wit-encoder/src/tuple.rs new file mode 100644 index 0000000000..77554ba574 --- /dev/null +++ b/crates/wit-encoder/src/tuple.rs @@ -0,0 +1,33 @@ +use std::fmt::Display; + +use crate::Type; + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Tuple { + pub(crate) types: Vec, +} + +impl Tuple { + pub fn types(&self) -> &[Type] { + &self.types + } + + pub fn types_mut(&mut self) -> &mut Vec { + &mut self.types + } +} + +impl Display for Tuple { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "tuple<")?; + let mut peekable = self.types.iter().peekable(); + while let Some(type_) = peekable.next() { + type_.fmt(f)?; + if peekable.peek().is_some() { + write!(f, ", ")?; + } + } + write!(f, ">")?; + Ok(()) + } +} diff --git a/crates/wit-encoder/src/ty.rs b/crates/wit-encoder/src/ty.rs new file mode 100644 index 0000000000..dea714e4ff --- /dev/null +++ b/crates/wit-encoder/src/ty.rs @@ -0,0 +1,420 @@ +use std::fmt::{self, Display}; + +use crate::{ + ident::Ident, Docs, Enum, EnumCase, Field, Flag, Flags, Record, Render, RenderOpts, Resource, + ResourceFunc, Result_, Tuple, Variant, +}; + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum Type { + Bool, + U8, + U16, + U32, + U64, + S8, + S16, + S32, + S64, + F32, + F64, + Char, + String, + Borrow(Ident), + Option(Box), + Result(Box), + List(Box), + Tuple(Tuple), + Named(Ident), +} + +impl Type { + pub fn borrow(name: impl Into) -> Self { + Type::Borrow(name.into()) + } + pub fn option(type_: Type) -> Self { + Type::Option(Box::new(type_)) + } + pub fn result(result: Result_) -> Self { + Type::Result(Box::new(result)) + } + pub fn result_ok(type_: Type) -> Self { + Type::Result(Box::new(Result_::ok(type_))) + } + pub fn result_err(type_: Type) -> Self { + Type::Result(Box::new(Result_::err(type_))) + } + pub fn result_both(ok: Type, err: Type) -> Self { + Type::Result(Box::new(Result_::both(ok, err))) + } + pub fn result_empty() -> Self { + Type::Result(Box::new(Result_::empty())) + } + pub fn list(type_: Type) -> Self { + Type::List(Box::new(type_)) + } + pub fn tuple(types: impl IntoIterator) -> Self { + Type::Tuple(Tuple { + types: types.into_iter().collect(), + }) + } + pub fn named(name: impl Into) -> Self { + Type::Named(name.into()) + } +} +impl From for Type { + fn from(value: Result_) -> Self { + Self::result(value) + } +} +impl From for Type { + fn from(value: Tuple) -> Self { + Type::Tuple(value) + } +} +impl From for Type { + fn from(value: Ident) -> Self { + Self::named(value) + } +} + +impl Display for Type { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Type::Bool => write!(f, "bool"), + Type::U8 => write!(f, "u8"), + Type::U16 => write!(f, "u16"), + Type::U32 => write!(f, "u32"), + Type::U64 => write!(f, "u64"), + Type::S8 => write!(f, "s8"), + Type::S16 => write!(f, "s16"), + Type::S32 => write!(f, "s32"), + Type::S64 => write!(f, "s64"), + Type::F32 => write!(f, "f32"), + Type::F64 => write!(f, "f64"), + Type::Char => write!(f, "char"), + Type::String => write!(f, "string"), + Type::Named(name) => write!(f, "{}", name), + Type::Borrow(type_) => { + write!(f, "borrow<{type_}>") + } + Type::Option(type_) => { + write!(f, "option<{type_}>") + } + Type::Result(result) => result.fmt(f), + Type::List(type_) => { + write!(f, "list<{type_}>") + } + Type::Tuple(tuple) => tuple.fmt(f), + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct VariantCase { + name: Ident, + ty: Option, + docs: Option, +} + +impl VariantCase { + pub fn empty(name: impl Into) -> Self { + Self { + name: name.into(), + ty: None, + docs: None, + } + } + pub fn value(name: impl Into, ty: Type) -> Self { + Self { + name: name.into(), + ty: Some(ty), + docs: None, + } + } + pub fn docs(&mut self, docs: Option>) { + self.docs = docs.map(|d| d.into()); + } +} + +impl Into for (N,) +where + N: Into, +{ + fn into(self) -> VariantCase { + VariantCase::empty(self.0) + } +} + +impl Into for (N, Type) +where + N: Into, +{ + fn into(self) -> VariantCase { + VariantCase::value(self.0, self.1) + } +} + +impl Into for (N, Type, D) +where + N: Into, + D: Into, +{ + fn into(self) -> VariantCase { + let mut field = VariantCase::value(self.0, self.1); + field.docs(Some(self.2.into())); + field + } +} + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct TypeDef { + name: Ident, + kind: TypeDefKind, + docs: Option, +} + +impl TypeDef { + pub fn new(name: impl Into, kind: TypeDefKind) -> Self { + TypeDef { + name: name.into(), + kind, + docs: None, + } + } + + pub fn record( + name: impl Into, + fields: impl IntoIterator>, + ) -> Self { + TypeDef { + name: name.into(), + kind: TypeDefKind::record(fields), + docs: None, + } + } + + pub fn resource( + name: impl Into, + funcs: impl IntoIterator>, + ) -> Self { + TypeDef { + name: name.into(), + kind: TypeDefKind::resource(funcs), + docs: None, + } + } + + pub fn flags(name: impl Into, flags: impl IntoIterator>) -> Self { + TypeDef { + name: name.into(), + kind: TypeDefKind::flags(flags), + docs: None, + } + } + + pub fn variant( + name: impl Into, + cases: impl IntoIterator>, + ) -> Self { + TypeDef { + name: name.into(), + kind: TypeDefKind::variant(cases), + docs: None, + } + } + + pub fn enum_( + name: impl Into, + cases: impl IntoIterator>, + ) -> Self { + TypeDef { + name: name.into(), + kind: TypeDefKind::enum_(cases), + docs: None, + } + } + + pub fn type_(name: impl Into, type_: Type) -> Self { + TypeDef { + name: name.into(), + kind: TypeDefKind::type_(type_), + docs: None, + } + } + + pub fn name(&self) -> &Ident { + &self.name + } + + pub fn name_mut(&mut self) -> &mut Ident { + &mut self.name + } + + pub fn kind(&self) -> &TypeDefKind { + &self.kind + } + + pub fn kind_mut(&mut self) -> &mut TypeDefKind { + &mut self.kind + } + + pub fn docs(&mut self, docs: Option>) { + self.docs = docs.map(|d| d.into()); + } +} + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum TypeDefKind { + Record(Record), + Resource(Resource), + Flags(Flags), + Variant(Variant), + Enum(Enum), + Type(Type), +} + +impl TypeDefKind { + pub fn record(fields: impl IntoIterator>) -> Self { + Self::Record(Record { + fields: fields.into_iter().map(|c| c.into()).collect(), + }) + } + + pub fn resource(funcs: impl IntoIterator>) -> Self { + Self::Resource(Resource { + funcs: funcs.into_iter().map(|f| f.into()).collect(), + }) + } + + pub fn flags(flags: impl IntoIterator>) -> Self { + Self::Flags(Flags { + flags: flags.into_iter().map(|f| f.into()).collect(), + }) + } + + pub fn variant(cases: impl IntoIterator>) -> Self { + Self::Variant(Variant { + cases: cases.into_iter().map(|c| c.into()).collect(), + }) + } + + pub fn enum_(cases: impl IntoIterator>) -> Self { + Self::Enum(Enum { + cases: cases.into_iter().map(|c| c.into()).collect(), + }) + } + + pub fn type_(type_: Type) -> Self { + Self::Type(type_) + } +} + +impl Render for TypeDef { + fn render(&self, f: &mut fmt::Formatter<'_>, opts: &RenderOpts) -> fmt::Result { + match &self.kind { + TypeDefKind::Record(record) => { + if let Some(docs) = &self.docs { + docs.render(f, opts)?; + } + write!(f, "{}record {} {{\n", opts.spaces(), self.name)?; + for field in &record.fields { + let opts = opts.indent(); + if let Some(docs) = &field.docs { + docs.render(f, &opts)?; + } + write!(f, "{}{}: {},\n", opts.spaces(), field.name, field.ty)?; + } + write!(f, "{}}}\n", opts.spaces())?; + } + TypeDefKind::Resource(resource) => { + if let Some(docs) = &self.docs { + docs.render(f, opts)?; + } + write!(f, "{}resource {} {{\n", opts.spaces(), self.name)?; + for func in &resource.funcs { + let opts = opts.indent(); + if let Some(docs) = &func.docs { + docs.render(f, &opts)?; + } + match &func.kind { + crate::ResourceFuncKind::Method(name, results) => { + write!(f, "{}{}: func({})", opts.spaces(), name, func.params)?; + if !results.is_empty() { + write!(f, " -> {}", results)?; + } + write!(f, ";\n")?; + } + crate::ResourceFuncKind::Static(name, results) => { + write!(f, "{}{}: static func({})", opts.spaces(), name, func.params)?; + if !results.is_empty() { + write!(f, " -> {}", results)?; + } + write!(f, ";\n")?; + } + crate::ResourceFuncKind::Constructor => { + write!(f, "{}constructor({});\n", opts.spaces(), func.params)?; + } + } + } + write!(f, "{}}}\n", opts.spaces())?; + } + TypeDefKind::Flags(flags) => { + if let Some(docs) = &self.docs { + docs.render(f, opts)?; + } + write!(f, "{}flags {} {{\n", opts.spaces(), self.name)?; + for flag in &flags.flags { + let opts = opts.indent(); + if let Some(docs) = &flag.docs { + docs.render(f, &opts)?; + } + write!(f, "{}{},\n", opts.spaces(), flag.name)?; + } + write!(f, "{}}}\n", opts.spaces())?; + } + TypeDefKind::Variant(variant) => { + if let Some(docs) = &self.docs { + docs.render(f, opts)?; + } + write!(f, "{}variant {} {{\n", opts.spaces(), self.name)?; + for case in &variant.cases { + let opts = opts.indent(); + if let Some(docs) = &case.docs { + docs.render(f, &opts)?; + } + match &case.ty { + Some(type_) => { + write!(f, "{}{}({}),\n", opts.spaces(), case.name, type_)?; + } + None => { + write!(f, "{}{},\n", opts.spaces(), case.name)?; + } + } + } + write!(f, "{}}}\n", opts.spaces())?; + } + TypeDefKind::Enum(enum_) => { + if let Some(docs) = &self.docs { + docs.render(f, opts)?; + } + write!(f, "{}enum {} {{\n", opts.spaces(), self.name)?; + for case in &enum_.cases { + let opts = opts.indent(); + if let Some(docs) = &case.docs { + docs.render(f, &opts)?; + } + write!(f, "{}{},\n", opts.spaces(), case.name)?; + } + write!(f, "{}}}\n", opts.spaces())?; + } + TypeDefKind::Type(type_) => { + if let Some(docs) = &self.docs { + docs.render(f, opts)?; + } + write!(f, "{}type {} = {};\n", opts.spaces(), self.name, type_)?; + } + } + Ok(()) + } +} diff --git a/crates/wit-encoder/src/variant.rs b/crates/wit-encoder/src/variant.rs new file mode 100644 index 0000000000..dd52ab273b --- /dev/null +++ b/crates/wit-encoder/src/variant.rs @@ -0,0 +1,28 @@ +use crate::VariantCase; + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Variant { + pub(crate) cases: Vec, +} + +impl Variant { + pub fn cases(&self) -> &[VariantCase] { + &self.cases + } + + pub fn cases_mut(&mut self) -> &mut Vec { + &mut self.cases + } +} + +impl From for Variant +where + I: IntoIterator, + C: Into, +{ + fn from(value: I) -> Self { + Self { + cases: value.into_iter().map(|c| c.into()).collect(), + } + } +} diff --git a/crates/wit-encoder/src/world.rs b/crates/wit-encoder/src/world.rs new file mode 100644 index 0000000000..867d550550 --- /dev/null +++ b/crates/wit-encoder/src/world.rs @@ -0,0 +1,210 @@ +use std::fmt; + +use crate::{ident::Ident, Docs, Interface, Render, RenderOpts, StandaloneFunc}; + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct World { + /// The WIT identifier name of this world. + name: Ident, + + /// All imported and exported items into this world. + items: Vec, + + /// Documentation associated with this world declaration. + docs: Option, +} + +impl World { + /// Create a new world. + pub fn new(name: impl Into) -> Self { + Self { + name: name.into(), + items: vec![], + docs: None, + } + } + + /// Add a `name` to the world + pub fn name(&mut self, name: impl Into) { + self.name = name.into(); + } + + /// Add an import or export to the world + pub fn item(&mut self, item: WorldItem) { + self.items.push(item); + } + + pub fn inline_interface_import(&mut self, value: Interface) { + self.item(WorldItem::inline_interface_import(value)); + } + pub fn inline_interface_export(&mut self, value: Interface) { + self.item(WorldItem::inline_interface_export(value)); + } + pub fn named_interface_import(&mut self, value: impl Into) { + self.item(WorldItem::named_interface_import(value)); + } + pub fn named_interface_export(&mut self, value: impl Into) { + self.item(WorldItem::named_interface_export(value)); + } + pub fn function_import(&mut self, value: StandaloneFunc) { + self.item(WorldItem::function_import(value)); + } + pub fn function_export(&mut self, value: StandaloneFunc) { + self.item(WorldItem::function_export(value)); + } + + /// Set the documentation + pub fn docs(&mut self, docs: Option>) { + self.docs = docs.map(|d| d.into()); + } +} + +impl Render for World { + fn render(&self, f: &mut fmt::Formatter<'_>, opts: &RenderOpts) -> fmt::Result { + fn import(f: &mut fmt::Formatter<'_>, opts: &RenderOpts) -> fmt::Result { + write!(f, "{}import ", opts.spaces()) + } + fn export(f: &mut fmt::Formatter<'_>, opts: &RenderOpts) -> fmt::Result { + write!(f, "{}export ", opts.spaces()) + } + fn render_function( + f: &mut fmt::Formatter<'_>, + _opts: &RenderOpts, + func: &StandaloneFunc, + ) -> fmt::Result { + write!(f, "{}: func({})", func.name, func.params)?; + if !func.results.is_empty() { + write!(f, " -> {}", func.results)?; + } + write!(f, ";\n")?; + Ok(()) + } + write!(f, "{}world {} {{\n", opts.spaces(), self.name)?; + let opts = &opts.indent(); + for item in &self.items { + match item { + WorldItem::InlineInterfaceImport(interface) => { + if let Some(docs) = &interface.docs { + docs.render(f, opts)?; + } + import(f, opts)?; + write!(f, "{}: interface {{\n", interface.name)?; + interface.items.render(f, &opts.indent())?; + write!(f, "{}}}\n", opts.spaces())?; + } + WorldItem::InlineInterfaceExport(interface) => { + if let Some(docs) = &interface.docs { + docs.render(f, opts)?; + } + export(f, opts)?; + write!(f, "{}: interface {{\n", interface.name)?; + interface.items.render(f, &opts.indent())?; + write!(f, "{}}}\n", opts.spaces())?; + } + WorldItem::NamedInterfaceImport(interface) => { + if let Some(docs) = &interface.docs { + docs.render(f, opts)?; + } + import(f, opts)?; + write!(f, "{};\n", interface.name)?; + } + WorldItem::NamedInterfaceExport(interface) => { + if let Some(docs) = &interface.docs { + docs.render(f, opts)?; + } + export(f, opts)?; + write!(f, "{};\n", interface.name)?; + } + WorldItem::FunctionImport(function) => { + if let Some(docs) = &function.docs { + docs.render(f, opts)?; + } + import(f, opts)?; + render_function(f, opts, function)?; + } + WorldItem::FunctionExport(function) => { + if let Some(docs) = &function.docs { + docs.render(f, opts)?; + } + export(f, opts)?; + render_function(f, opts, function)?; + } + } + } + let opts = &opts.outdent(); + write!(f, "{}}}\n", opts.spaces())?; + Ok(()) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum WorldItem { + /// An imported inline interface + InlineInterfaceImport(Interface), + + /// An exported inline interface + InlineInterfaceExport(Interface), + + /// Refers to a named interface import + NamedInterfaceImport(WorldNamedInterface), + + /// Refers to a named interface export + NamedInterfaceExport(WorldNamedInterface), + + /// A function is being directly imported from this world. + FunctionImport(StandaloneFunc), + + /// A function is being directly exported from this world. + FunctionExport(StandaloneFunc), +} + +impl WorldItem { + pub fn inline_interface_import(value: Interface) -> Self { + Self::InlineInterfaceImport(value) + } + pub fn inline_interface_export(value: Interface) -> Self { + Self::InlineInterfaceExport(value) + } + pub fn named_interface_import(value: impl Into) -> Self { + Self::NamedInterfaceImport(value.into()) + } + pub fn named_interface_export(value: impl Into) -> Self { + Self::NamedInterfaceExport(value.into()) + } + pub fn function_import(value: StandaloneFunc) -> Self { + Self::FunctionImport(value) + } + pub fn function_export(value: StandaloneFunc) -> Self { + Self::FunctionExport(value) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct WorldNamedInterface { + /// Name of this interface. + pub(crate) name: Ident, + + /// Documentation associated with this interface. + pub(crate) docs: Option, +} + +impl From for WorldNamedInterface +where + N: Into, +{ + fn from(name: N) -> Self { + Self::new(name) + } +} + +impl WorldNamedInterface { + pub fn new(name: impl Into) -> Self { + Self { + name: name.into(), + docs: None, + } + } + pub fn docs(&mut self, docs: Option>) { + self.docs = docs.map(|d| d.into()); + } +} diff --git a/crates/wit-encoder/tests/functions.rs b/crates/wit-encoder/tests/functions.rs new file mode 100644 index 0000000000..fd5c5e8c6c --- /dev/null +++ b/crates/wit-encoder/tests/functions.rs @@ -0,0 +1,88 @@ +use pretty_assertions::assert_eq; +use wit_encoder::{Params, Result_, Results, StandaloneFunc, Type}; + +const PACKAGE: &str = "package foo:functions; + +interface functions { + f1: func(); + f2: func(a: u32); + f3: func() -> u32; + /// this is a documentation comment + /// for the f4 function + f4: func() -> tuple; + f5: func(a: f32, b: f32) -> tuple; + f6: func(a: option) -> result; + f7: func() -> (u: u32, f: f32); + f8: func() -> (u: u32); + f9: func() -> result; + f10: func() -> result<_, f32>; + f11: func() -> result; +} +"; + +#[test] +fn smoke() { + let name = wit_encoder::PackageName::new("foo", "functions", None); + let mut package = wit_encoder::Package::new(name); + + package.interface({ + let mut interface = wit_encoder::Interface::new("functions"); + interface.function(StandaloneFunc::new("f1")); + interface.function({ + let mut func = StandaloneFunc::new("f2"); + func.params(Params::from_iter([("a", Type::U32)])); + func + }); + interface.function({ + let mut func = StandaloneFunc::new("f3"); + func.results(Type::U32); + func + }); + interface.function({ + let mut func = StandaloneFunc::new("f4"); + func.results(Type::tuple(vec![Type::U32, Type::U32])); + func.docs(Some("this is a documentation comment\nfor the f4 function")); + func + }); + interface.function({ + let mut func = StandaloneFunc::new("f5"); + func.params(Params::from_iter([("a", Type::F32), ("b", Type::F32)])); + func.results(Type::tuple(vec![Type::U32, Type::U32])); + func + }); + interface.function({ + let mut func = StandaloneFunc::new("f6"); + func.params(Params::from_iter([("a", Type::option(Type::U32))])); + func.results(Type::result(Result_::both(Type::U32, Type::F32))); + func + }); + interface.function({ + let mut func = StandaloneFunc::new("f7"); + func.results(Results::named(vec![("u", Type::U32), ("f", Type::F32)])); + func + }); + interface.function({ + let mut func = StandaloneFunc::new("f8"); + func.results(Results::named(vec![("u", Type::U32)])); + func + }); + interface.function({ + let mut func = StandaloneFunc::new("f9"); + func.results(Type::result(Result_::ok(Type::F32))); + func + }); + interface.function({ + let mut func = StandaloneFunc::new("f10"); + func.results(Type::result(Result_::err(Type::F32))); + func + }); + interface.function({ + let mut func = StandaloneFunc::new("f11"); + func.results(Type::result(Result_::empty())); + func + }); + interface + }); + + assert_eq!(PACKAGE, package.to_string()); +} diff --git a/crates/wit-encoder/tests/type_defs.rs b/crates/wit-encoder/tests/type_defs.rs new file mode 100644 index 0000000000..c4723f9ea3 --- /dev/null +++ b/crates/wit-encoder/tests/type_defs.rs @@ -0,0 +1,246 @@ +use pretty_assertions::assert_eq; +use wit_encoder::{ + Field, Flag, Params, ResourceFunc, Result_, Results, Type, TypeDef, TypeDefKind, VariantCase, +}; + +const PACKAGE: &str = "package wit-encoder:tests; + +/// interface documentation +interface type-defs { + type t1 = u8; + type t2 = u16; + type t3 = u32; + type t4 = u64; + type t5 = s8; + type t6 = s16; + type t7 = s32; + type t8 = s64; + type t9a = f32; + type t9b = f32; + type t10a = f64; + type t10b = f64; + type t11 = char; + type t12 = list; + type t13 = string; + type t14 = option; + type t15 = result; + type t16 = result<_, u32>; + type t17 = result; + /// this is a documentation comment + type t18 = result; + record t20 { + } + record t21 { + a: u32, + } + record t22 { + a: u32, + } + record t23 { + a: u32, + b: u64, + } + record t24 { + a: u32, + b: u64, + } + record t25 { + x: u32, + } + record %record { + a: u32, + } + type t26 = tuple<>; + type t27 = tuple; + type t29 = tuple; + flags t30 { + } + flags t31 { + /// option a + a, + /// option b + b, + /// option c + c, + } + flags t32 { + a, + b, + c, + } + variant t33 { + a, + } + variant t34 { + a, + b, + } + variant t35 { + a, + b, + } + variant t36 { + a, + b(u32), + } + variant t37 { + a, + b(option), + } + enum t41 { + a, + b, + c, + } + enum t42 { + a, + b, + c, + } + type t43 = bool; + type t44 = string; + type t45 = list>>; + type t46 = t44; + type foo = bar; + type bar = u32; + resource t50 { + } + resource t51 { + /// create a new t51 + constructor(a: u32); + /// set a + set-a: func(a: u32); + /// get a + get-a: func() -> u32; + /// do b + b: static func(); + } +} +"; + +#[test] +fn types() { + let name = wit_encoder::PackageName::new("wit-encoder", "tests", None); + let mut package = wit_encoder::Package::new(name); + package.interface({ + let mut interface = wit_encoder::Interface::new("type-defs"); + interface.docs(Some("interface documentation")); + interface.type_def(TypeDef::type_("t1", Type::U8)); + interface.type_def(TypeDef::type_("t2", Type::U16)); + interface.type_def(TypeDef::type_("t3", Type::U32)); + interface.type_def(TypeDef::type_("t4", Type::U64)); + interface.type_def(TypeDef::type_("t5", Type::S8)); + interface.type_def(TypeDef::type_("t6", Type::S16)); + interface.type_def(TypeDef::type_("t7", Type::S32)); + interface.type_def(TypeDef::type_("t8", Type::S64)); + interface.type_def(TypeDef::type_("t9a", Type::F32)); + interface.type_def(TypeDef::type_("t9b", Type::F32)); + interface.type_def(TypeDef::type_("t10a", Type::F64)); + interface.type_def(TypeDef::type_("t10b", Type::F64)); + interface.type_def(TypeDef::type_("t11", Type::Char)); + interface.type_def(TypeDef::type_("t12", Type::list(Type::Char))); + interface.type_def(TypeDef::type_("t13", Type::String)); + interface.type_def(TypeDef::type_("t14", Type::option(Type::U32))); + interface.type_def(TypeDef::type_( + "t15", + Type::result_both(Type::U32, Type::U32), + )); + interface.type_def(TypeDef::type_("t16", Type::result(Result_::err(Type::U32)))); + interface.type_def(TypeDef::type_("t17", Type::result(Result_::ok(Type::U32)))); + interface.type_def({ + let mut type_ = TypeDef::type_("t18", Type::result(Result_::empty())); + type_.docs(Some("this is a documentation comment")); + type_ + }); + + interface.type_def(TypeDef::record("t20", Vec::::new())); + interface.type_def(TypeDef::record("t21", [Field::new("a", Type::U32)])); + interface.type_def(TypeDef::record("t22", [Field::new("a", Type::U32)])); + interface.type_def(TypeDef::record( + "t23", + [Field::new("a", Type::U32), Field::new("b", Type::U64)], + )); + interface.type_def(TypeDef::record( + "t24", + [Field::new("a", Type::U32), Field::new("b", Type::U64)], + )); + interface.type_def(TypeDef::record("t25", [Field::new("x", Type::U32)])); + interface.type_def(TypeDef::record("record", [Field::new("a", Type::U32)])); + + interface.type_def(TypeDef::type_("t26", Type::tuple(Vec::::new()))); + interface.type_def(TypeDef::type_("t27", Type::tuple([Type::U32]))); + interface.type_def(TypeDef::type_("t29", Type::tuple([Type::U32, Type::U64]))); + + interface.type_def(TypeDef::flags("t30", Vec::::new())); + interface.type_def(TypeDef::flags( + "t31", + [("a", "option a"), ("b", "option b"), ("c", "option c")], + )); + interface.type_def(TypeDef::flags("t32", [("a",), ("b",), ("c",)])); + + interface.type_def(TypeDef::variant("t33", [("a",)])); + interface.type_def(TypeDef::variant("t34", [("a",), ("b",)])); + interface.type_def(TypeDef::variant("t35", [("a",), ("b",)])); + interface.type_def(TypeDef::new( + "t36", + TypeDefKind::Variant( + [VariantCase::empty("a"), VariantCase::value("b", Type::U32)].into(), + ), + )); + interface.type_def(TypeDef::new( + "t37", + TypeDefKind::Variant( + [ + VariantCase::empty("a"), + VariantCase::value("b", Type::option(Type::U32)), + ] + .into(), + ), + )); + + interface.type_def(TypeDef::enum_("t41", ["a", "b", "c"])); + interface.type_def(TypeDef::enum_("t42", ["a", "b", "c"])); + + interface.type_def(TypeDef::type_("t43", Type::Bool)); + interface.type_def(TypeDef::type_("t44", Type::String)); + interface.type_def(TypeDef::type_( + "t45", + Type::list(Type::list(Type::list(Type::named("t32")))), + )); + interface.type_def(TypeDef::type_("t46", Type::named("t44"))); + interface.type_def(TypeDef::type_("foo", Type::named("bar"))); + interface.type_def(TypeDef::type_("bar", Type::U32)); + + interface.type_def(TypeDef::resource("t50", Vec::::new())); + interface.type_def(TypeDef::resource( + "t51", + [ + { + let mut func = ResourceFunc::constructor(); + func.params(Params::from_iter([("a", Type::U32)])); + func.docs(Some("create a new t51")); + func + }, + { + let mut func = ResourceFunc::method("set-a"); + func.params(Params::from_iter([("a", Type::U32)])); + func.docs(Some("set a")); + func + }, + { + let mut func = ResourceFunc::method("get-a"); + func.results(Results::anon(Type::U32)); + func.docs(Some("get a")); + func + }, + { + let mut func = ResourceFunc::static_("b"); + func.docs(Some("do b")); + func + }, + ], + )); + interface + }); + assert_eq!(PACKAGE, package.to_string()); +} diff --git a/crates/wit-encoder/tests/world.rs b/crates/wit-encoder/tests/world.rs new file mode 100644 index 0000000000..38e71bb9d7 --- /dev/null +++ b/crates/wit-encoder/tests/world.rs @@ -0,0 +1,56 @@ +use pretty_assertions::assert_eq; +use wit_encoder::{Interface, StandaloneFunc, Type}; + +const PACKAGE: &str = "package foo:functions; + +interface error-reporter { +} +world %world { + /// inline interface + export example: interface { + /// func docs + do-nothing: func(); + } + /// scan stuff + export scan: func() -> list; + import error-reporter; + import print: func(s: string); +} +"; + +#[test] +fn worlds() { + let name = wit_encoder::PackageName::new("foo", "functions", None); + let mut package = wit_encoder::Package::new(name); + + package.interface(wit_encoder::Interface::new("error-reporter")); + + package.world({ + let mut world = wit_encoder::World::new("world"); + world.inline_interface_export({ + let mut interface = Interface::new("example"); + interface.docs(Some("inline interface")); + interface.function({ + let mut func = StandaloneFunc::new("do-nothing"); + func.docs(Some("func docs")); + func + }); + interface + }); + world.function_export({ + let mut func = StandaloneFunc::new("scan"); + func.results(Type::list(Type::U8)); + func.docs(Some("scan stuff")); + func + }); + world.named_interface_import("error-reporter"); + world.function_import({ + let mut func: StandaloneFunc = StandaloneFunc::new("print"); + func.params(("s", Type::String)); + func + }); + world + }); + + assert_eq!(PACKAGE, package.to_string()); +}