feat: add a histrical wit-bindgen
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
[package]
|
||||
name = "wit-bindgen-gen-wasmtime-py"
|
||||
version = "0.1.0"
|
||||
authors = ["Alex Crichton <alex@alexcrichton.com>"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
wit-bindgen-gen-core = { path = '../gen-core', version = '0.1.0' }
|
||||
heck = "0.3"
|
||||
structopt = { version = "0.3", default-features = false, optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
test-helpers = { path = '../test-helpers', features = ['wit-bindgen-gen-wasmtime-py'] }
|
||||
@@ -0,0 +1,4 @@
|
||||
fn main() {
|
||||
println!("cargo:rerun-if-changed=build.rs");
|
||||
// this build script is currently only here so OUT_DIR is set for testing.
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
[mypy]
|
||||
|
||||
disallow_any_unimported = True
|
||||
|
||||
disallow_untyped_calls = True
|
||||
disallow_untyped_defs = True
|
||||
disallow_incomplete_defs = True
|
||||
check_untyped_defs = True
|
||||
disallow_untyped_decorators = True
|
||||
strict_optional = True
|
||||
|
||||
warn_return_any = True
|
||||
warn_unused_configs = True
|
||||
@@ -0,0 +1,368 @@
|
||||
use crate::Source;
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
/// Tracks all of the import and intrinsics that a given codegen
|
||||
/// requires and how to generate them when needed.
|
||||
#[derive(Default)]
|
||||
pub struct Dependencies {
|
||||
pub needs_clamp: bool,
|
||||
pub needs_store: bool,
|
||||
pub needs_load: bool,
|
||||
pub needs_validate_guest_char: bool,
|
||||
pub needs_expected: bool,
|
||||
pub needs_i32_to_f32: bool,
|
||||
pub needs_f32_to_i32: bool,
|
||||
pub needs_i64_to_f64: bool,
|
||||
pub needs_f64_to_i64: bool,
|
||||
pub needs_decode_utf8: bool,
|
||||
pub needs_encode_utf8: bool,
|
||||
pub needs_list_canon_lift: bool,
|
||||
pub needs_list_canon_lower: bool,
|
||||
pub needs_t_typevar: bool,
|
||||
pub needs_resources: bool,
|
||||
pub pyimports: BTreeMap<String, Option<BTreeSet<String>>>,
|
||||
}
|
||||
|
||||
impl Dependencies {
|
||||
/// Record that a Python import is required
|
||||
///
|
||||
/// Examples
|
||||
/// ```
|
||||
/// # use wit_bindgen_gen_wasmtime_py::dependencies::Dependencies;
|
||||
/// # let mut deps = Dependencies::default();
|
||||
/// // Import a specific item from a module
|
||||
/// deps.pyimport("typing", "NamedTuple");
|
||||
/// // Import an entire module
|
||||
/// deps.pyimport("collections", None);
|
||||
/// ```
|
||||
pub fn pyimport<'a>(&mut self, module: &str, name: impl Into<Option<&'a str>>) {
|
||||
let name = name.into();
|
||||
let list = self
|
||||
.pyimports
|
||||
.entry(module.to_string())
|
||||
.or_insert(match name {
|
||||
Some(_) => Some(BTreeSet::new()),
|
||||
None => None,
|
||||
});
|
||||
match name {
|
||||
Some(name) => {
|
||||
assert!(list.is_some());
|
||||
list.as_mut().unwrap().insert(name.to_string());
|
||||
}
|
||||
None => assert!(list.is_none()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a `Source` containing all of the intrinsics
|
||||
/// required according to this `Dependencies` struct.
|
||||
pub fn intrinsics(&mut self) -> Source {
|
||||
let mut src = Source::default();
|
||||
|
||||
if self.needs_clamp {
|
||||
src.push_str(
|
||||
"
|
||||
def _clamp(i: int, min: int, max: int) -> int:
|
||||
if i < min or i > max:
|
||||
raise OverflowError(f'must be between {min} and {max}')
|
||||
return i
|
||||
",
|
||||
);
|
||||
}
|
||||
if self.needs_store {
|
||||
// TODO: this uses native endianness
|
||||
self.pyimport("ctypes", None);
|
||||
src.push_str(
|
||||
"
|
||||
def _store(ty: Any, mem: wasmtime.Memory, store: wasmtime.Storelike, base: int, offset: int, val: Any) -> None:
|
||||
ptr = (base & 0xffffffff) + offset
|
||||
if ptr + ctypes.sizeof(ty) > mem.data_len(store):
|
||||
raise IndexError('out-of-bounds store')
|
||||
raw_base = mem.data_ptr(store)
|
||||
c_ptr = ctypes.POINTER(ty)(
|
||||
ty.from_address(ctypes.addressof(raw_base.contents) + ptr)
|
||||
)
|
||||
c_ptr[0] = val
|
||||
",
|
||||
);
|
||||
}
|
||||
if self.needs_load {
|
||||
// TODO: this uses native endianness
|
||||
self.pyimport("ctypes", None);
|
||||
src.push_str(
|
||||
"
|
||||
def _load(ty: Any, mem: wasmtime.Memory, store: wasmtime.Storelike, base: int, offset: int) -> Any:
|
||||
ptr = (base & 0xffffffff) + offset
|
||||
if ptr + ctypes.sizeof(ty) > mem.data_len(store):
|
||||
raise IndexError('out-of-bounds store')
|
||||
raw_base = mem.data_ptr(store)
|
||||
c_ptr = ctypes.POINTER(ty)(
|
||||
ty.from_address(ctypes.addressof(raw_base.contents) + ptr)
|
||||
)
|
||||
return c_ptr[0]
|
||||
",
|
||||
);
|
||||
}
|
||||
if self.needs_validate_guest_char {
|
||||
src.push_str(
|
||||
"
|
||||
def _validate_guest_char(i: int) -> str:
|
||||
if i > 0x10ffff or (i >= 0xd800 and i <= 0xdfff):
|
||||
raise TypeError('not a valid char')
|
||||
return chr(i)
|
||||
",
|
||||
);
|
||||
}
|
||||
if self.needs_expected {
|
||||
self.pyimport("dataclasses", "dataclass");
|
||||
self.pyimport("typing", "TypeVar");
|
||||
self.pyimport("typing", "Generic");
|
||||
self.pyimport("typing", "Union");
|
||||
self.needs_t_typevar = true;
|
||||
src.push_str(
|
||||
"
|
||||
@dataclass
|
||||
class Ok(Generic[T]):
|
||||
value: T
|
||||
E = TypeVar('E')
|
||||
@dataclass
|
||||
class Err(Generic[E]):
|
||||
value: E
|
||||
|
||||
Expected = Union[Ok[T], Err[E]]
|
||||
",
|
||||
);
|
||||
}
|
||||
if self.needs_i32_to_f32 || self.needs_f32_to_i32 {
|
||||
self.pyimport("ctypes", None);
|
||||
src.push_str("_i32_to_f32_i32 = ctypes.pointer(ctypes.c_int32(0))\n");
|
||||
src.push_str(
|
||||
"_i32_to_f32_f32 = ctypes.cast(_i32_to_f32_i32, ctypes.POINTER(ctypes.c_float))\n",
|
||||
);
|
||||
if self.needs_i32_to_f32 {
|
||||
src.push_str(
|
||||
"
|
||||
def _i32_to_f32(i: int) -> float:
|
||||
_i32_to_f32_i32[0] = i # type: ignore
|
||||
return _i32_to_f32_f32[0] # type: ignore
|
||||
",
|
||||
);
|
||||
}
|
||||
if self.needs_f32_to_i32 {
|
||||
src.push_str(
|
||||
"
|
||||
def _f32_to_i32(i: float) -> int:
|
||||
_i32_to_f32_f32[0] = i # type: ignore
|
||||
return _i32_to_f32_i32[0] # type: ignore
|
||||
",
|
||||
);
|
||||
}
|
||||
}
|
||||
if self.needs_i64_to_f64 || self.needs_f64_to_i64 {
|
||||
self.pyimport("ctypes", None);
|
||||
src.push_str("_i64_to_f64_i64 = ctypes.pointer(ctypes.c_int64(0))\n");
|
||||
src.push_str(
|
||||
"_i64_to_f64_f64 = ctypes.cast(_i64_to_f64_i64, ctypes.POINTER(ctypes.c_double))\n",
|
||||
);
|
||||
if self.needs_i64_to_f64 {
|
||||
src.push_str(
|
||||
"
|
||||
def _i64_to_f64(i: int) -> float:
|
||||
_i64_to_f64_i64[0] = i # type: ignore
|
||||
return _i64_to_f64_f64[0] # type: ignore
|
||||
",
|
||||
);
|
||||
}
|
||||
if self.needs_f64_to_i64 {
|
||||
src.push_str(
|
||||
"
|
||||
def _f64_to_i64(i: float) -> int:
|
||||
_i64_to_f64_f64[0] = i # type: ignore
|
||||
return _i64_to_f64_i64[0] # type: ignore
|
||||
",
|
||||
);
|
||||
}
|
||||
}
|
||||
if self.needs_decode_utf8 {
|
||||
self.pyimport("ctypes", None);
|
||||
src.push_str(
|
||||
"
|
||||
def _decode_utf8(mem: wasmtime.Memory, store: wasmtime.Storelike, ptr: int, len: int) -> str:
|
||||
ptr = ptr & 0xffffffff
|
||||
len = len & 0xffffffff
|
||||
if ptr + len > mem.data_len(store):
|
||||
raise IndexError('string out of bounds')
|
||||
base = mem.data_ptr(store)
|
||||
base = ctypes.POINTER(ctypes.c_ubyte)(
|
||||
ctypes.c_ubyte.from_address(ctypes.addressof(base.contents) + ptr)
|
||||
)
|
||||
return ctypes.string_at(base, len).decode('utf-8')
|
||||
",
|
||||
);
|
||||
}
|
||||
if self.needs_encode_utf8 {
|
||||
self.pyimport("ctypes", None);
|
||||
self.pyimport("typing", "Tuple");
|
||||
src.push_str(
|
||||
"
|
||||
def _encode_utf8(val: str, realloc: wasmtime.Func, mem: wasmtime.Memory, store: wasmtime.Storelike) -> Tuple[int, int]:
|
||||
bytes = val.encode('utf8')
|
||||
ptr = realloc(store, 0, 0, 1, len(bytes))
|
||||
assert(isinstance(ptr, int))
|
||||
ptr = ptr & 0xffffffff
|
||||
if ptr + len(bytes) > mem.data_len(store):
|
||||
raise IndexError('string out of bounds')
|
||||
base = mem.data_ptr(store)
|
||||
base = ctypes.POINTER(ctypes.c_ubyte)(
|
||||
ctypes.c_ubyte.from_address(ctypes.addressof(base.contents) + ptr)
|
||||
)
|
||||
ctypes.memmove(base, bytes, len(bytes))
|
||||
return (ptr, len(bytes))
|
||||
",
|
||||
);
|
||||
}
|
||||
if self.needs_list_canon_lift {
|
||||
self.pyimport("ctypes", None);
|
||||
self.pyimport("typing", "List");
|
||||
// TODO: this is doing a native-endian read, not a little-endian
|
||||
// read
|
||||
src.push_str(
|
||||
"
|
||||
def _list_canon_lift(ptr: int, len: int, size: int, ty: Any, mem: wasmtime.Memory ,store: wasmtime.Storelike) -> Any:
|
||||
ptr = ptr & 0xffffffff
|
||||
len = len & 0xffffffff
|
||||
if ptr + len * size > mem.data_len(store):
|
||||
raise IndexError('list out of bounds')
|
||||
raw_base = mem.data_ptr(store)
|
||||
base = ctypes.POINTER(ty)(
|
||||
ty.from_address(ctypes.addressof(raw_base.contents) + ptr)
|
||||
)
|
||||
if ty == ctypes.c_uint8:
|
||||
return ctypes.string_at(base, len)
|
||||
return base[:len]
|
||||
",
|
||||
);
|
||||
}
|
||||
if self.needs_list_canon_lower {
|
||||
self.pyimport("ctypes", None);
|
||||
self.pyimport("typing", "List");
|
||||
self.pyimport("typing", "Tuple");
|
||||
// TODO: is there a faster way to memcpy other than iterating over
|
||||
// the input list?
|
||||
// TODO: this is doing a native-endian write, not a little-endian
|
||||
// write
|
||||
src.push_str(
|
||||
"
|
||||
def _list_canon_lower(list: Any, ty: Any, size: int, align: int, realloc: wasmtime.Func, mem: wasmtime.Memory, store: wasmtime.Storelike) -> Tuple[int, int]:
|
||||
total_size = size * len(list)
|
||||
ptr = realloc(store, 0, 0, align, total_size)
|
||||
assert(isinstance(ptr, int))
|
||||
ptr = ptr & 0xffffffff
|
||||
if ptr + total_size > mem.data_len(store):
|
||||
raise IndexError('list realloc return of bounds')
|
||||
raw_base = mem.data_ptr(store)
|
||||
base = ctypes.POINTER(ty)(
|
||||
ty.from_address(ctypes.addressof(raw_base.contents) + ptr)
|
||||
)
|
||||
for i, val in enumerate(list):
|
||||
base[i] = val
|
||||
return (ptr, len(list))
|
||||
",
|
||||
);
|
||||
}
|
||||
|
||||
if self.needs_resources {
|
||||
self.pyimport("typing", "TypeVar");
|
||||
self.pyimport("typing", "Generic");
|
||||
self.pyimport("typing", "List");
|
||||
self.pyimport("typing", "Optional");
|
||||
self.pyimport("dataclasses", "dataclass");
|
||||
self.needs_t_typevar = true;
|
||||
src.push_str(
|
||||
"
|
||||
@dataclass
|
||||
class SlabEntry(Generic[T]):
|
||||
next: int
|
||||
val: Optional[T]
|
||||
|
||||
class Slab(Generic[T]):
|
||||
head: int
|
||||
list: List[SlabEntry[T]]
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.list = []
|
||||
self.head = 0
|
||||
|
||||
def insert(self, val: T) -> int:
|
||||
if self.head >= len(self.list):
|
||||
self.list.append(SlabEntry(next = len(self.list) + 1, val = None))
|
||||
ret = self.head
|
||||
slot = self.list[ret]
|
||||
self.head = slot.next
|
||||
slot.next = -1
|
||||
slot.val = val
|
||||
return ret
|
||||
|
||||
def get(self, idx: int) -> T:
|
||||
if idx >= len(self.list):
|
||||
raise IndexError('handle index not valid')
|
||||
slot = self.list[idx]
|
||||
if slot.next == -1:
|
||||
assert(slot.val is not None)
|
||||
return slot.val
|
||||
raise IndexError('handle index not valid')
|
||||
|
||||
def remove(self, idx: int) -> T:
|
||||
ret = self.get(idx)
|
||||
slot = self.list[idx]
|
||||
slot.val = None
|
||||
slot.next = self.head
|
||||
self.head = idx
|
||||
return ret
|
||||
",
|
||||
);
|
||||
}
|
||||
src
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
use super::Dependencies;
|
||||
|
||||
#[test]
|
||||
fn test_pyimport_only_contents() {
|
||||
let mut deps = Dependencies::default();
|
||||
deps.pyimport("typing", None);
|
||||
deps.pyimport("typing", None);
|
||||
assert_eq!(deps.pyimports, BTreeMap::from([("typing".into(), None)]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pyimport_only_module() {
|
||||
let mut deps = Dependencies::default();
|
||||
deps.pyimport("typing", "Union");
|
||||
deps.pyimport("typing", "List");
|
||||
deps.pyimport("typing", "NamedTuple");
|
||||
assert_eq!(
|
||||
deps.pyimports,
|
||||
BTreeMap::from([(
|
||||
"typing".into(),
|
||||
Some(BTreeSet::from([
|
||||
"Union".into(),
|
||||
"List".into(),
|
||||
"NamedTuple".into()
|
||||
]))
|
||||
)])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn test_pyimport_conflicting() {
|
||||
let mut deps = Dependencies::default();
|
||||
deps.pyimport("typing", "NamedTuple");
|
||||
deps.pyimport("typing", None);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,501 @@
|
||||
use heck::*;
|
||||
use wit_bindgen_gen_core::wit_parser::*;
|
||||
|
||||
use crate::dependencies::Dependencies;
|
||||
|
||||
/// A [Source] represents some unit of Python code
|
||||
/// and keeps track of its indent.
|
||||
#[derive(Default)]
|
||||
pub struct Source {
|
||||
s: String,
|
||||
indent: usize,
|
||||
}
|
||||
|
||||
impl Source {
|
||||
/// Appends a string slice to this [Source].
|
||||
///
|
||||
/// Strings without newlines, they are simply appended.
|
||||
/// Strings with newlines are appended and also new lines
|
||||
/// are indented based on the current indent level.
|
||||
pub fn push_str(&mut self, src: &str) {
|
||||
let lines = src.lines().collect::<Vec<_>>();
|
||||
let mut trim = None;
|
||||
for (i, line) in lines.iter().enumerate() {
|
||||
self.s.push_str(if lines.len() == 1 {
|
||||
line
|
||||
} else {
|
||||
let trim = match trim {
|
||||
Some(n) => n,
|
||||
None => {
|
||||
let val = line.len() - line.trim_start().len();
|
||||
if !line.is_empty() {
|
||||
trim = Some(val);
|
||||
}
|
||||
val
|
||||
}
|
||||
};
|
||||
line.get(trim..).unwrap_or("")
|
||||
});
|
||||
if i != lines.len() - 1 || src.ends_with("\n") {
|
||||
self.newline();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Prints the documentation as comments
|
||||
/// e.g.
|
||||
/// > \# Line one of docs node
|
||||
/// >
|
||||
/// > \# Line two of docs node
|
||||
pub fn comment(&mut self, docs: &Docs) {
|
||||
let docs = match &docs.contents {
|
||||
Some(docs) => docs,
|
||||
None => return,
|
||||
};
|
||||
for line in docs.lines() {
|
||||
self.push_str(&format!("# {}\n", line));
|
||||
}
|
||||
}
|
||||
|
||||
/// Prints the documentation as comments
|
||||
/// e.g.
|
||||
/// > """
|
||||
/// >
|
||||
/// > Line one of docs node
|
||||
/// >
|
||||
/// > Line two of docs node
|
||||
/// >
|
||||
/// > """
|
||||
pub fn docstring(&mut self, docs: &Docs) {
|
||||
let docs = match &docs.contents {
|
||||
Some(docs) => docs,
|
||||
None => return,
|
||||
};
|
||||
let triple_quote = r#"""""#;
|
||||
self.push_str(triple_quote);
|
||||
self.newline();
|
||||
for line in docs.lines() {
|
||||
self.push_str(line);
|
||||
self.newline();
|
||||
}
|
||||
self.push_str(triple_quote);
|
||||
self.newline();
|
||||
}
|
||||
|
||||
/// Indent the source one level.
|
||||
pub fn indent(&mut self) {
|
||||
self.indent += 4;
|
||||
self.s.push_str(" ");
|
||||
}
|
||||
|
||||
/// Unindent, or in Python terms "dedent",
|
||||
/// the source one level.
|
||||
pub fn dedent(&mut self) {
|
||||
self.indent -= 4;
|
||||
assert!(self.s.ends_with(" "));
|
||||
self.s.pop();
|
||||
self.s.pop();
|
||||
self.s.pop();
|
||||
self.s.pop();
|
||||
}
|
||||
|
||||
/// Go to the next line and apply any indent.
|
||||
pub fn newline(&mut self) {
|
||||
self.s.push_str("\n");
|
||||
for _ in 0..self.indent {
|
||||
self.s.push_str(" ");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Deref for Source {
|
||||
type Target = str;
|
||||
fn deref(&self) -> &str {
|
||||
&self.s
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Source> for String {
|
||||
fn from(s: Source) -> String {
|
||||
s.s
|
||||
}
|
||||
}
|
||||
|
||||
/// [SourceBuilder] combines together a [Source]
|
||||
/// with other contextual information and state.
|
||||
///
|
||||
/// This allows you to generate code for the Source using
|
||||
/// high-level tools that take care of updating dependencies
|
||||
/// and retrieving interface details.
|
||||
///
|
||||
/// You can create a [SourceBuilder] easily using a [Source]
|
||||
/// ```
|
||||
/// # use wit_bindgen_gen_wasmtime_py::dependencies::Dependencies;
|
||||
/// # use wit_bindgen_gen_core::wit_parser::{Interface, Type};
|
||||
/// # use wit_bindgen_gen_wasmtime_py::source::Source;
|
||||
/// # let mut deps = Dependencies::default();
|
||||
/// # let mut interface = Interface::default();
|
||||
/// # let iface = &interface;
|
||||
/// let mut source = Source::default();
|
||||
/// let mut builder = source.builder(&mut deps, iface);
|
||||
/// builder.print_ty(&Type::Bool, false);
|
||||
/// ```
|
||||
pub struct SourceBuilder<'s, 'd, 'i> {
|
||||
source: &'s mut Source,
|
||||
pub deps: &'d mut Dependencies,
|
||||
iface: &'i Interface,
|
||||
}
|
||||
|
||||
impl<'s, 'd, 'i> Source {
|
||||
/// Create a [SourceBuilder] for the current source.
|
||||
pub fn builder(
|
||||
&'s mut self,
|
||||
deps: &'d mut Dependencies,
|
||||
iface: &'i Interface,
|
||||
) -> SourceBuilder<'s, 'd, 'i> {
|
||||
SourceBuilder {
|
||||
source: self,
|
||||
deps,
|
||||
iface,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s, 'd, 'i> SourceBuilder<'s, 'd, 'i> {
|
||||
/// See [Dependencies::pyimport].
|
||||
pub fn pyimport<'a>(&mut self, module: &str, name: impl Into<Option<&'a str>>) {
|
||||
self.deps.pyimport(module, name)
|
||||
}
|
||||
|
||||
/// Appends a type's Python representation to this `Source`.
|
||||
/// Records any required intrinsics and imports in the `deps`.
|
||||
/// Uses Python forward reference syntax (e.g. 'Foo')
|
||||
/// on the root type only if `forward_ref` is true.
|
||||
pub fn print_ty(&mut self, ty: &Type, forward_ref: bool) {
|
||||
match ty {
|
||||
Type::Unit => self.push_str("None"),
|
||||
Type::Bool => self.push_str("bool"),
|
||||
Type::U8
|
||||
| Type::S8
|
||||
| Type::U16
|
||||
| Type::S16
|
||||
| Type::U32
|
||||
| Type::S32
|
||||
| Type::U64
|
||||
| Type::S64 => self.push_str("int"),
|
||||
Type::Float32 | Type::Float64 => self.push_str("float"),
|
||||
Type::Char => self.push_str("str"),
|
||||
Type::String => self.push_str("str"),
|
||||
Type::Handle(id) => {
|
||||
if forward_ref {
|
||||
self.push_str("'");
|
||||
}
|
||||
let handle_name = &self.iface.resources[*id].name.to_camel_case();
|
||||
self.source.push_str(handle_name);
|
||||
if forward_ref {
|
||||
self.push_str("'");
|
||||
}
|
||||
}
|
||||
Type::Id(id) => {
|
||||
let ty = &self.iface.types[*id];
|
||||
if let Some(name) = &ty.name {
|
||||
self.push_str(&name.to_camel_case());
|
||||
return;
|
||||
}
|
||||
match &ty.kind {
|
||||
TypeDefKind::Type(t) => self.print_ty(t, forward_ref),
|
||||
TypeDefKind::Tuple(t) => self.print_tuple(t),
|
||||
TypeDefKind::Record(_)
|
||||
| TypeDefKind::Flags(_)
|
||||
| TypeDefKind::Enum(_)
|
||||
| TypeDefKind::Variant(_)
|
||||
| TypeDefKind::Union(_) => {
|
||||
unreachable!()
|
||||
}
|
||||
TypeDefKind::Option(t) => {
|
||||
self.deps.pyimport("typing", "Optional");
|
||||
self.push_str("Optional[");
|
||||
self.print_ty(t, true);
|
||||
self.push_str("]");
|
||||
}
|
||||
TypeDefKind::Expected(e) => {
|
||||
self.deps.needs_expected = true;
|
||||
self.push_str("Expected[");
|
||||
self.print_ty(&e.ok, true);
|
||||
self.push_str(", ");
|
||||
self.print_ty(&e.err, true);
|
||||
self.push_str("]");
|
||||
}
|
||||
TypeDefKind::List(t) => self.print_list(t),
|
||||
TypeDefKind::Future(t) => {
|
||||
self.push_str("Future[");
|
||||
self.print_ty(t, true);
|
||||
self.push_str("]");
|
||||
}
|
||||
TypeDefKind::Stream(s) => {
|
||||
self.push_str("Stream[");
|
||||
self.print_ty(&s.element, true);
|
||||
self.push_str(", ");
|
||||
self.print_ty(&s.end, true);
|
||||
self.push_str("]");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Appends a tuple type's Python representation to this `Source`.
|
||||
/// Records any required intrinsics and imports in the `deps`.
|
||||
/// Uses Python forward reference syntax (e.g. 'Foo') for named type parameters.
|
||||
pub fn print_tuple(&mut self, tuple: &Tuple) {
|
||||
if tuple.types.is_empty() {
|
||||
return self.push_str("None");
|
||||
}
|
||||
self.deps.pyimport("typing", "Tuple");
|
||||
self.push_str("Tuple[");
|
||||
for (i, t) in tuple.types.iter().enumerate() {
|
||||
if i > 0 {
|
||||
self.push_str(", ");
|
||||
}
|
||||
self.print_ty(t, true);
|
||||
}
|
||||
self.push_str("]");
|
||||
}
|
||||
|
||||
/// Appends a Python type representing a sequence of the `element` type to this `Source`.
|
||||
/// If the element type is `Type::U8`, the result type is `bytes` otherwise it is a `List[T]`
|
||||
/// Records any required intrinsics and imports in the `deps`.
|
||||
/// Uses Python forward reference syntax (e.g. 'Foo') for named type parameters.
|
||||
pub fn print_list(&mut self, element: &Type) {
|
||||
match element {
|
||||
Type::U8 => self.push_str("bytes"),
|
||||
t => {
|
||||
self.deps.pyimport("typing", "List");
|
||||
self.push_str("List[");
|
||||
self.print_ty(t, true);
|
||||
self.push_str("]");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Print variable declaration.
|
||||
/// Brings name into scope and binds type to it.
|
||||
pub fn print_var_declaration<'a>(&mut self, name: &'a str, ty: &Type) {
|
||||
self.push_str(name);
|
||||
self.push_str(": ");
|
||||
self.print_ty(ty, true);
|
||||
self.push_str("\n");
|
||||
}
|
||||
|
||||
pub fn print_sig(&mut self, func: &Function, in_import: bool) -> Vec<String> {
|
||||
if !in_import {
|
||||
if let FunctionKind::Static { .. } = func.kind {
|
||||
self.push_str("@classmethod\n");
|
||||
}
|
||||
}
|
||||
self.source.push_str("def ");
|
||||
match &func.kind {
|
||||
FunctionKind::Method { .. } => self.source.push_str(&func.item_name().to_snake_case()),
|
||||
FunctionKind::Static { .. } if !in_import => {
|
||||
self.source.push_str(&func.item_name().to_snake_case())
|
||||
}
|
||||
_ => self.source.push_str(&func.name.to_snake_case()),
|
||||
}
|
||||
if in_import {
|
||||
self.source.push_str("(self");
|
||||
} else if let FunctionKind::Static { .. } = func.kind {
|
||||
self.source.push_str("(cls, caller: wasmtime.Store, obj: '");
|
||||
self.source.push_str(&self.iface.name.to_camel_case());
|
||||
self.source.push_str("'");
|
||||
} else {
|
||||
self.source.push_str("(self, caller: wasmtime.Store");
|
||||
}
|
||||
let mut params = Vec::new();
|
||||
for (i, (param, ty)) in func.params.iter().enumerate() {
|
||||
if i == 0 {
|
||||
if let FunctionKind::Method { .. } = func.kind {
|
||||
params.push("self".to_string());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
self.source.push_str(", ");
|
||||
self.source.push_str(¶m.to_snake_case());
|
||||
params.push(param.to_snake_case());
|
||||
self.source.push_str(": ");
|
||||
self.print_ty(ty, true);
|
||||
}
|
||||
self.source.push_str(") -> ");
|
||||
self.print_ty(&func.result, true);
|
||||
params
|
||||
}
|
||||
|
||||
/// Print a wrapped union definition.
|
||||
/// e.g.
|
||||
/// ```py
|
||||
/// @dataclass
|
||||
/// class Foo0:
|
||||
/// value: int
|
||||
///
|
||||
/// @dataclass
|
||||
/// class Foo1:
|
||||
/// value: int
|
||||
///
|
||||
/// Foo = Union[Foo0, Foo1]
|
||||
/// ```
|
||||
pub fn print_union_wrapped(&mut self, name: &str, union: &Union, docs: &Docs) {
|
||||
self.deps.pyimport("dataclasses", "dataclass");
|
||||
let mut cases = Vec::new();
|
||||
let name = name.to_camel_case();
|
||||
for (i, case) in union.cases.iter().enumerate() {
|
||||
self.source.push_str("@dataclass\n");
|
||||
let name = format!("{name}{i}");
|
||||
self.source.push_str(&format!("class {name}:\n"));
|
||||
self.source.indent();
|
||||
self.source.docstring(&case.docs);
|
||||
self.source.push_str("value: ");
|
||||
self.print_ty(&case.ty, true);
|
||||
self.source.newline();
|
||||
self.source.dedent();
|
||||
self.source.newline();
|
||||
cases.push(name);
|
||||
}
|
||||
|
||||
self.deps.pyimport("typing", "Union");
|
||||
self.source.comment(docs);
|
||||
self.source
|
||||
.push_str(&format!("{name} = Union[{}]\n", cases.join(", ")));
|
||||
self.source.newline();
|
||||
}
|
||||
|
||||
pub fn print_union_raw(&mut self, name: &str, union: &Union, docs: &Docs) {
|
||||
self.deps.pyimport("typing", "Union");
|
||||
self.source.comment(docs);
|
||||
for case in union.cases.iter() {
|
||||
self.source.comment(&case.docs);
|
||||
}
|
||||
self.source.push_str(&name.to_camel_case());
|
||||
self.source.push_str(" = Union[");
|
||||
let mut first = true;
|
||||
for case in union.cases.iter() {
|
||||
if !first {
|
||||
self.source.push_str(",");
|
||||
}
|
||||
self.print_ty(&case.ty, true);
|
||||
first = false;
|
||||
}
|
||||
self.source.push_str("]\n\n");
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s, 'd, 'i> std::ops::Deref for SourceBuilder<'s, 'd, 'i> {
|
||||
type Target = Source;
|
||||
fn deref(&self) -> &Source {
|
||||
&self.source
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s, 'd, 'i> std::ops::DerefMut for SourceBuilder<'s, 'd, 'i> {
|
||||
fn deref_mut(&mut self) -> &mut Source {
|
||||
&mut self.source
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn simple_append() {
|
||||
let mut s = Source::default();
|
||||
s.push_str("x");
|
||||
assert_eq!(s.s, "x");
|
||||
s.push_str("y");
|
||||
assert_eq!(s.s, "xy");
|
||||
s.push_str("z ");
|
||||
assert_eq!(s.s, "xyz ");
|
||||
s.push_str(" a ");
|
||||
assert_eq!(s.s, "xyz a ");
|
||||
s.push_str("\na");
|
||||
assert_eq!(s.s, "xyz a \na");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn trim_ws() {
|
||||
let mut s = Source::default();
|
||||
s.push_str("def foo():\n return 1\n");
|
||||
assert_eq!(s.s, "def foo():\n return 1\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn print_ty_forward_ref() {
|
||||
let mut deps = Dependencies::default();
|
||||
let mut iface = Interface::default();
|
||||
// Set up a Resource type to refer to
|
||||
let resource_id = iface.resources.alloc(Resource {
|
||||
docs: Docs::default(),
|
||||
name: "foo".into(),
|
||||
supertype: None,
|
||||
foreign_module: None,
|
||||
});
|
||||
iface.resource_lookup.insert("foo".into(), resource_id);
|
||||
let handle_ty = Type::Handle(resource_id);
|
||||
// ForwardRef usage can be controlled by an argument to print_ty
|
||||
let mut s1 = Source::default();
|
||||
let mut builder = s1.builder(&mut deps, &iface);
|
||||
builder.print_ty(&handle_ty, true);
|
||||
drop(builder);
|
||||
assert_eq!(s1.s, "'Foo'");
|
||||
|
||||
let mut s2 = Source::default();
|
||||
let mut builder = s2.builder(&mut deps, &iface);
|
||||
builder.print_ty(&handle_ty, false);
|
||||
drop(builder);
|
||||
assert_eq!(s2.s, "Foo");
|
||||
|
||||
// ForwardRef is used for any types within other types
|
||||
// Even if the outer type is itself not allowed to be one
|
||||
let option_id = iface.types.alloc(TypeDef {
|
||||
docs: Docs::default(),
|
||||
kind: TypeDefKind::Option(handle_ty),
|
||||
name: None,
|
||||
foreign_module: None,
|
||||
});
|
||||
let option_ty = Type::Id(option_id);
|
||||
let mut s3 = Source::default();
|
||||
let mut builder = s3.builder(&mut deps, &iface);
|
||||
builder.print_ty(&option_ty, false);
|
||||
drop(builder);
|
||||
assert_eq!(s3.s, "Optional['Foo']");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn print_list_bytes() {
|
||||
// If the element type is u8, it is interpreted as `bytes`
|
||||
let mut deps = Dependencies::default();
|
||||
let iface = Interface::default();
|
||||
let mut source = Source::default();
|
||||
let mut builder = source.builder(&mut deps, &iface);
|
||||
builder.print_list(&Type::U8);
|
||||
drop(builder);
|
||||
assert_eq!(source.s, "bytes");
|
||||
assert_eq!(deps.pyimports, BTreeMap::default());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn print_list_non_bytes() {
|
||||
// If the element type is u8, it is interpreted as `bytes`
|
||||
let mut deps = Dependencies::default();
|
||||
let iface = Interface::default();
|
||||
let mut source = Source::default();
|
||||
let mut builder = source.builder(&mut deps, &iface);
|
||||
builder.print_list(&Type::Float32);
|
||||
drop(builder);
|
||||
assert_eq!(source.s, "List[float]");
|
||||
assert_eq!(
|
||||
deps.pyimports,
|
||||
BTreeMap::from([("typing".into(), Some(BTreeSet::from(["List".into()])))])
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
use std::path::Path;
|
||||
use std::process::Command;
|
||||
|
||||
mod exports {
|
||||
test_helpers::codegen_py_export!(
|
||||
"*.wit"
|
||||
|
||||
// TODO: implement async support
|
||||
"!async-functions.wit"
|
||||
);
|
||||
}
|
||||
|
||||
mod imports {
|
||||
test_helpers::codegen_py_import!(
|
||||
"*.wit"
|
||||
|
||||
// TODO: implement async support
|
||||
"!async-functions.wit"
|
||||
|
||||
// This uses buffers, which we don't support in imports just yet
|
||||
// TODO: should support this
|
||||
"!wasi-next.wit"
|
||||
"!host.wit"
|
||||
);
|
||||
}
|
||||
|
||||
fn verify(dir: &str, _name: &str) {
|
||||
let output = Command::new("mypy")
|
||||
.arg(Path::new(dir).join("bindings.py"))
|
||||
.arg("--config-file")
|
||||
.arg("mypy.ini")
|
||||
.output()
|
||||
.expect("failed to run `mypy`; do you have it installed?");
|
||||
if output.status.success() {
|
||||
return;
|
||||
}
|
||||
panic!(
|
||||
"mypy failed
|
||||
|
||||
status: {status}
|
||||
|
||||
stdout ---
|
||||
{stdout}
|
||||
|
||||
stderr ---
|
||||
{stderr}",
|
||||
status = output.status,
|
||||
stdout = String::from_utf8_lossy(&output.stdout).replace("\n", "\n\t"),
|
||||
stderr = String::from_utf8_lossy(&output.stderr).replace("\n", "\n\t"),
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
use std::env;
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::Command;
|
||||
use wit_bindgen_gen_core::Generator;
|
||||
|
||||
test_helpers::runtime_tests!("py");
|
||||
|
||||
fn execute(name: &str, wasm: &Path, py: &Path, imports: &Path, exports: &Path) {
|
||||
let out_dir = PathBuf::from(env!("OUT_DIR"));
|
||||
let dir = out_dir.join(name);
|
||||
drop(fs::remove_dir_all(&dir));
|
||||
fs::create_dir_all(&dir).unwrap();
|
||||
fs::create_dir_all(&dir.join("imports")).unwrap();
|
||||
fs::create_dir_all(&dir.join("exports")).unwrap();
|
||||
|
||||
println!("OUT_DIR = {:?}", dir);
|
||||
println!("Generating bindings...");
|
||||
// We call `generate_all` with exports from the imports.wit file, and
|
||||
// imports from the exports.wit wit file. It's reversed because we're
|
||||
// implementing the host side of these APIs.
|
||||
let iface = wit_bindgen_gen_core::wit_parser::Interface::parse_file(imports).unwrap();
|
||||
let mut files = Default::default();
|
||||
wit_bindgen_gen_wasmtime_py::Opts::default()
|
||||
.build()
|
||||
.generate_all(&[], &[iface], &mut files);
|
||||
for (file, contents) in files.iter() {
|
||||
fs::write(dir.join("imports").join(file), contents).unwrap();
|
||||
}
|
||||
fs::write(dir.join("imports").join("__init__.py"), "").unwrap();
|
||||
|
||||
let iface = wit_bindgen_gen_core::wit_parser::Interface::parse_file(exports).unwrap();
|
||||
let mut files = Default::default();
|
||||
wit_bindgen_gen_wasmtime_py::Opts::default()
|
||||
.build()
|
||||
.generate_all(&[iface], &[], &mut files);
|
||||
for (file, contents) in files.iter() {
|
||||
fs::write(dir.join("exports").join(file), contents).unwrap();
|
||||
}
|
||||
fs::write(dir.join("exports").join("__init__.py"), "").unwrap();
|
||||
|
||||
println!("Running mypy...");
|
||||
exec(
|
||||
Command::new("mypy")
|
||||
.env("MYPYPATH", &dir)
|
||||
.arg(py)
|
||||
.arg("--cache-dir")
|
||||
.arg(out_dir.join("mypycache").join(name)),
|
||||
);
|
||||
|
||||
exec(
|
||||
Command::new("python3")
|
||||
.env("PYTHONPATH", &dir)
|
||||
.arg(py)
|
||||
.arg(wasm),
|
||||
);
|
||||
}
|
||||
|
||||
fn exec(cmd: &mut Command) {
|
||||
println!("{:?}", cmd);
|
||||
let output = cmd.output().unwrap();
|
||||
if output.status.success() {
|
||||
return;
|
||||
}
|
||||
println!("status: {}", output.status);
|
||||
println!(
|
||||
"stdout ---\n {}",
|
||||
String::from_utf8_lossy(&output.stdout).replace("\n", "\n ")
|
||||
);
|
||||
println!(
|
||||
"stderr ---\n {}",
|
||||
String::from_utf8_lossy(&output.stderr).replace("\n", "\n ")
|
||||
);
|
||||
panic!("no success");
|
||||
}
|
||||
Reference in New Issue
Block a user