diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml index bf2ca72..47ff0bc 100644 --- a/crates/core/Cargo.toml +++ b/crates/core/Cargo.toml @@ -9,4 +9,6 @@ rust-version.workspace = true [dependencies] wit-parser.workspace = true -proc-macro2 = "1.0.56" \ No newline at end of file +proc-macro2 = "1.0.56" +bitflags.workspace = true +log.workspace = true diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs index 30c7dca..e9ce0b1 100644 --- a/crates/core/src/lib.rs +++ b/crates/core/src/lib.rs @@ -1,14 +1,14 @@ use proc_macro2::TokenStream; -use std::path::PathBuf; -use wit_parser::Interface; +use std::{path::PathBuf, collections::HashMap, ops::Index}; +use wit_parser::{Interface, TypeDefArena, Type, TypeDefId, FunctionResult, TypeDefKind, FlagsField, Int}; pub trait GeneratorBuilder { fn build(self, interface: Interface) -> Box; } pub trait Generate { - fn to_file(&self) -> (PathBuf, String); - fn to_tokens(&self) -> TokenStream { + fn to_file(&mut self) -> (PathBuf, String); + fn to_tokens(&mut self) -> TokenStream { unimplemented!("to_tokens is not implemented for this generator") } } @@ -48,3 +48,141 @@ where Ok(()) } + +bitflags::bitflags! { + #[derive(Debug, Clone, Copy)] + pub struct TypeInfo: u32 { + /// Whether or not this type is ever used (transitively) within the + /// parameter of a function. + const PARAM = 0b0000_0001; + /// Whether or not this type is ever used (transitively) within the + /// result of a function. + const RESULT = 0b0000_0010; + /// Whether or not this type (transitively) has a list. + const HAS_LIST = 0b0000_1000; + } +} + +#[derive(Debug, Default)] +pub struct TypeInfos { + infos: HashMap, +} + +impl TypeInfos { + #[must_use] + pub fn new() -> Self { + TypeInfos::default() + } + + pub fn collect_param_info(&mut self, typedefs: &TypeDefArena, params: &[(String, Type)]) { + for (_, ty) in params { + self.collect_type_info(typedefs, ty, TypeInfo::PARAM); + } + } + + pub fn collect_result_info(&mut self, typedefs: &TypeDefArena, result: &FunctionResult) { + match result { + FunctionResult::Anon(ty) => { + self.collect_type_info(typedefs, ty, TypeInfo::RESULT); + } + FunctionResult::Named(results) => { + for (_, ty) in results { + self.collect_type_info(typedefs, ty, TypeInfo::RESULT); + } + } + } + } + + fn collect_typedef_info( + &mut self, + typedefs: &TypeDefArena, + id: TypeDefId, + base_info: TypeInfo, + ) -> TypeInfo { + let mut info = base_info; + + match &typedefs[id].kind { + TypeDefKind::Alias(ty) => { + info |= self.collect_type_info(typedefs, ty, base_info); + } + TypeDefKind::Record(fields) => { + for field in fields { + info |= self.collect_type_info(typedefs, &field.ty, base_info); + } + } + TypeDefKind::Variant(cases) => { + for case in cases { + if let Some(ty) = &case.ty { + info |= self.collect_type_info(typedefs, ty, base_info); + } + } + } + TypeDefKind::Union(cases) => { + for case in cases { + info |= self.collect_type_info(typedefs, &case.ty, base_info); + } + } + _ => {} + } + + log::debug!("collected info for {:?}: {:?}", typedefs[id].ident, info,); + + self.infos + .entry(id) + .and_modify(|i| *i |= info) + .or_insert(info); + + info + } + + fn collect_type_info( + &mut self, + typedefs: &TypeDefArena, + ty: &Type, + base_info: TypeInfo, + ) -> TypeInfo { + match ty { + Type::String => base_info | TypeInfo::HAS_LIST, + Type::List(ty) => self.collect_type_info(typedefs, ty, base_info) | TypeInfo::HAS_LIST, + Type::Option(ty) => self.collect_type_info(typedefs, ty, base_info), + Type::Tuple(types) => { + let mut info = base_info; + for ty in types { + info |= self.collect_type_info(typedefs, ty, base_info); + } + info + } + Type::Result { ok, err } => { + let mut info = base_info; + if let Some(ty) = &ok { + info |= self.collect_type_info(typedefs, ty, base_info); + } + if let Some(ty) = &err { + info |= self.collect_type_info(typedefs, ty, base_info); + } + info + } + Type::Id(id) => base_info | self.collect_typedef_info(typedefs, *id, base_info), + _ => base_info, + } + } +} + +impl Index for TypeInfos { + type Output = TypeInfo; + + fn index(&self, id: TypeDefId) -> &Self::Output { + &self.infos[&id] + } +} + +#[must_use] +pub fn flags_repr(fields: &[FlagsField]) -> Int { + match fields.len() { + n if n <= 8 => Int::U8, + n if n <= 16 => Int::U16, + n if n <= 32 => Int::U32, + n if n <= 64 => Int::U64, + _ => panic!("too many flags to fit in a repr"), + } +} \ No newline at end of file diff --git a/crates/gen-guest-js/Cargo.toml b/crates/gen-guest-js/Cargo.toml index e452adf..55dae00 100644 --- a/crates/gen-guest-js/Cargo.toml +++ b/crates/gen-guest-js/Cargo.toml @@ -10,6 +10,8 @@ tauri-bindgen-core.workspace = true wit-parser.workspace = true heck.workspace = true clap = { workspace = true, optional = true } +log.workspace = true +bitflags.workspace = true [dev-dependencies] pretty_assertions = "1.3.0" diff --git a/crates/gen-guest-js/src/lib.rs b/crates/gen-guest-js/src/lib.rs index dc85e15..2a7771a 100644 --- a/crates/gen-guest-js/src/lib.rs +++ b/crates/gen-guest-js/src/lib.rs @@ -1,9 +1,14 @@ #![allow(clippy::must_use_candidate, clippy::unused_self)] -use heck::{ToKebabCase, ToLowerCamelCase, ToUpperCamelCase, ToSnakeCase}; -use std::path::PathBuf; -use tauri_bindgen_core::{postprocess, Generate, GeneratorBuilder}; -use wit_parser::{Function, FunctionResult, Interface, Type, TypeDefKind}; +use heck::{ToKebabCase, ToLowerCamelCase, ToSnakeCase, ToUpperCamelCase}; +use std::{collections::HashSet, fmt::Write, path::PathBuf, sync::atomic::{AtomicU32, Ordering}}; +use tauri_bindgen_core::{ + flags_repr, postprocess, Generate, GeneratorBuilder, TypeInfo, TypeInfos, +}; +use wit_parser::{ + EnumCase, FlagsField, Function, FunctionResult, Interface, RecordField, Type, TypeDefId, + TypeDefKind, UnionCase, VariantCase, +}; #[derive(Debug, Clone, Default)] #[cfg_attr(feature = "clap", derive(clap::Args))] @@ -22,9 +27,24 @@ pub struct Builder { impl GeneratorBuilder for Builder { fn build(self, interface: Interface) -> Box { + let mut infos = TypeInfos::new(); + + for func in &interface.functions { + infos.collect_param_info(&interface.typedefs, &func.params); + if let Some(result) = &func.result { + infos.collect_result_info(&interface.typedefs, result); + } + } + + for (id, typedef) in &interface.typedefs { + log::debug!("type info: {} {:#?}", typedef.ident, infos[id]); + } + Box::new(JavaScript { opts: self, interface, + infos, + serde_utils: 0.into(), }) } } @@ -33,6 +53,9 @@ impl GeneratorBuilder for Builder { pub struct JavaScript { opts: Builder, interface: Interface, + infos: TypeInfos, + // TODO: this is really awkward + serde_utils: AtomicU32, } impl JavaScript { @@ -42,17 +65,67 @@ impl JavaScript { let name = func.ident.to_snake_case(); let params = print_function_params(&func.params); + let deserialize_result = func + .result + .as_ref() + .map(|res| self.print_deserialize_function_result(&res)) + .unwrap_or_default(); + format!( r#" {docs} export async function {ident} ({params}) {{ - return fetch('ipc://localhost/{intf_name}/{name}', {{ method: "POST", body: JSON.stringify([{params}]) }}).then(r => r.json()) + return fetch('ipc://localhost/{intf_name}/{name}', {{ method: "POST", body: JSON.stringify([{params}]) }}){deserialize_result} }} "# ) } - fn print_resource(&self, docs: &str, ident: &str, functions: &[Function]) -> String { + fn print_deserialize_function_result(&self, result: &FunctionResult) -> String { + match result.len() { + 0 => String::new(), + 1 => { + let inner = self.print_deserialize_ty(result.types().next().unwrap()); + + format!( + " + .then(r => r.arrayBuffer()) + .then(bytes => {{ + const de = new Deserializer(Uint8Array.from(bytes)) + + return {inner} + }})" + ) + } + _ => { + let tys = result + .types() + .map(|ty| self.print_deserialize_ty(ty)) + .collect::>() + .join(", "); + + format!( + " + .then(r => r.arrayBuffer()) + .then(bytes => {{ + const de = new Deserializer(Uint8Array.from(bytes)) + + return [{tys}] + }})" + ) + } + } + } + + fn print_resource( + &self, + docs: &str, + ident: &str, + functions: &[Function], + info: TypeInfo, + ) -> String { + let ident = ident.to_upper_camel_case(); + let functions: String = functions .iter() .map(|func| { @@ -70,11 +143,23 @@ impl JavaScript { }) .collect(); + let deserialize = if info.contains(TypeInfo::RESULT) { + format!( + "deserialize(de) {{ + const self = new {ident}(); + self.#id = deserializeU64(de); + return self + }}" + ) + } else { + String::new() + }; + format!( "{docs}\nclass {ident} {{ - #id: number; - + #id; {functions} + {deserialize} }}" ) } @@ -186,10 +271,271 @@ impl JavaScript { | Type::String => None, } } + + fn print_deserialize_ty(&self, ty: &Type) -> String { + match ty { + Type::Bool => { + self.serde_utils.fetch_or(SerdeUtils::DE_BOOl.bits(), Ordering::Relaxed); + "deserializeBoolean(de)".to_string() + } + Type::U8 => { + self.serde_utils.fetch_or(SerdeUtils::DE_U8.bits(), Ordering::Relaxed); + "deserializeU8(de)".to_string() + } + Type::U16 => { + self.serde_utils.fetch_or(SerdeUtils::DE_U16.bits(), Ordering::Relaxed); + "deserializeU16(de)".to_string() + } + Type::U32 => { + self.serde_utils.fetch_or(SerdeUtils::DE_U32.bits(), Ordering::Relaxed); + "deserializeU32(de)".to_string() + } + Type::U64 => { + self.serde_utils.fetch_or(SerdeUtils::DE_U64.bits(), Ordering::Relaxed); + "deserializeU64(de)".to_string() + } + Type::S8 => { + self.serde_utils.fetch_or(SerdeUtils::DE_S8.bits(), Ordering::Relaxed); + "deserializeS8(de)".to_string() + } + Type::S16 => { + self.serde_utils.fetch_or(SerdeUtils::DE_S16.bits(), Ordering::Relaxed); + "deserializeS16(de)".to_string() + } + Type::S32 => { + self.serde_utils.fetch_or(SerdeUtils::DE_S32.bits(), Ordering::Relaxed); + "deserializeS32(de)".to_string() + } + Type::S64 => { + self.serde_utils.fetch_or(SerdeUtils::DE_S64.bits(), Ordering::Relaxed); + "deserializeS64(de)".to_string() + } + Type::Float32 => { + self.serde_utils.fetch_or(SerdeUtils::DE_F32.bits(), Ordering::Relaxed); + "deserializeF32(de)".to_string() + } + Type::Float64 => { + self.serde_utils.fetch_or(SerdeUtils::DE_F64.bits(), Ordering::Relaxed); + "deserializeF64(de)".to_string() + } + Type::Char => { + self.serde_utils.fetch_or(SerdeUtils::DE_CHAR.bits(), Ordering::Relaxed); + "deserializeChar(de)".to_string() + } + Type::String => { + self.serde_utils.fetch_or(SerdeUtils::DE_STRING.bits(), Ordering::Relaxed); + "deserializeString(de)".to_string() + } + Type::Tuple(types) => { + let types = types + .iter() + .map(|ty| self.print_deserialize_ty(ty)) + .collect::>() + .join(", "); + + format!("[{types}]") + } + Type::List(ty) if **ty == Type::U8 => { + self.serde_utils.fetch_or(SerdeUtils::DE_BYTES.bits(), Ordering::Relaxed); + "deserializeBytes(de)".to_string() + } + Type::List(ty) => { + self.serde_utils.fetch_or(SerdeUtils::DE_LIST.bits(), Ordering::Relaxed); + let ty = self.print_deserialize_ty(ty); + format!("deserializeList(de, (de) => {ty})") + } + Type::Option(ty) => { + self.serde_utils.fetch_or(SerdeUtils::DE_OPTION.bits(), Ordering::Relaxed); + let ty = self.print_deserialize_ty(ty); + format!("deserializeOption(de, (de) => {ty})") + } + Type::Result { ok, err } => { + self.serde_utils.fetch_or(SerdeUtils::DE_RESULT.bits(), Ordering::Relaxed); + let ok = ok + .as_ref() + .map_or("() => {}".to_string(), |ty| self.print_deserialize_ty(ty)); + let err = err + .as_ref() + .map_or("() => {}".to_string(), |ty| self.print_deserialize_ty(ty)); + + format!("deserializeResult(de, {ok}, {err})") + } + Type::Id(id) => { + if let TypeDefKind::Resource(_) = self.interface.typedefs[*id].kind { + format!( + "{}.deserialize(de)", + self.interface.typedefs[*id].ident.to_upper_camel_case() + ) + } else { + format!( + "deserialize{}(de)", + self.interface.typedefs[*id].ident.to_upper_camel_case() + ) + } + } + } + } + + fn print_deserialize_typedef(&self, id: TypeDefId) -> String { + let typedef = &self.interface.typedefs[id]; + let ident = &typedef.ident.to_upper_camel_case(); + + match typedef.kind.clone() { + TypeDefKind::Alias(ty) => self.print_deserialize_alias(ident, ty), + TypeDefKind::Record(fields) => self.print_deserialize_record(ident, &fields), + TypeDefKind::Flags(fields) => self.print_deserialize_flags(ident, &fields), + TypeDefKind::Variant(cases) => self.print_deserialize_variant(ident, &cases), + TypeDefKind::Enum(cases) => self.print_deserialize_enum(ident, &cases), + TypeDefKind::Union(cases) => self.print_deserialize_union(ident, &cases), + TypeDefKind::Resource(_) => String::new(), + } + } + + fn print_deserialize_alias(&self, ident: &str, ty: Type) -> String { + let inner = self.print_deserialize_ty(&ty); + + format!( + r#"function deserialize{ident}(de) {{ + return {inner} + }}"# + ) + } + + fn print_deserialize_record(&self, ident: &str, fields: &[RecordField]) -> String { + let fields = fields + .iter() + .map(|field| { + let ident = field.ident.to_lower_camel_case(); + + format!("{ident}: {}", self.print_deserialize_ty(&field.ty)) + }) + .collect::>() + .join(",\n"); + + format!( + r#"function deserialize{ident}(de) {{ + return {{ + {fields} + }} + }}"# + ) + } + + fn print_deserialize_flags(&self, ident: &str, fields: &[FlagsField]) -> String { + let inner = match flags_repr(fields) { + wit_parser::Int::U8 => "U8", + wit_parser::Int::U16 => "U16", + wit_parser::Int::U32 => "U32", + wit_parser::Int::U64 => "U64", + }; + + format!( + r#"function deserialize{ident}(de) {{ + return deserialize{inner}(de) + }}"# + ) + } + + fn print_deserialize_variant(&self, ident: &str, cases: &[VariantCase]) -> String { + let cases = cases + .iter() + .enumerate() + .map(|(tag, case)| { + let inner = case + .ty + .as_ref() + .map(|ty| self.print_deserialize_ty(&ty)) + .unwrap_or("null".to_string()); + + format!( + "case {tag}: + return {{ tag: {tag}, value: {inner} }} + " + ) + }) + .collect::(); + + format!( + r#"function deserialize{ident}(de) {{ + const disc = deserializeU32(de) + + switch (disc) {{ + {cases} + default: + throw new Error("unknown variant case") + }} + }}"# + ) + } + + fn print_deserialize_enum(&self, ident: &str, cases: &[EnumCase]) -> String { + let cases = cases + .iter() + .enumerate() + .map(|(tag, case)| { + let ident = case.ident.to_upper_camel_case(); + format!( + "case {tag}: + return \"{ident}\" + " + ) + }) + .collect::(); + + format!( + r#"function deserialize{ident}(de) {{ + const disc = deserializeU32(de) + + switch (disc) {{ + {cases} + default: + throw new Error("unknown enum case") + }} + }}"# + ) + } + + fn print_deserialize_union(&self, ident: &str, cases: &[UnionCase]) -> String { + let cases = cases + .iter() + .enumerate() + .map(|(tag, case)| { + let inner = self.print_deserialize_ty(&case.ty); + + format!( + "case {tag}: + return {inner} + " + ) + }) + .collect::(); + + format!( + r#"function deserialize{ident}(de) {{ + const disc = deserializeU32(de) + + switch (disc) {{ + {cases} + default: + throw new Error("unknown union case") + }} + }}"# + ) + } } impl Generate for JavaScript { - fn to_file(&self) -> (std::path::PathBuf, String) { + fn to_file(&mut self) -> (std::path::PathBuf, String) { + + let mut deserializers = String::new(); + for (id, _) in self.interface.typedefs.iter() { + let info = self.infos[id]; + + if info.contains(TypeInfo::RESULT) { + deserializers.push_str(&self.print_deserialize_typedef(id)) + } + } + let functions: String = self .interface .functions @@ -201,16 +547,23 @@ impl Generate for JavaScript { .interface .typedefs .iter() - .filter_map(|(_, typedef)| { + .filter_map(|(id, typedef)| { + let info = self.infos[id]; if let TypeDefKind::Resource(functions) = &typedef.kind { - Some(self.print_resource(&typedef.docs, &typedef.ident, functions)) + Some(self.print_resource(&typedef.docs, &typedef.ident, functions, info)) } else { None } }) .collect(); - let mut contents = format!("{functions}\n{resources}"); + let bits = self.serde_utils.load(Ordering::Acquire); + + println!("{} {:?}", bits, SerdeUtils::from_bits_retain(bits)); + + let serde_util = print_serde_utils(&SerdeUtils::from_bits_retain(bits)).unwrap(); + + let mut contents = format!("{serde_util}{deserializers}\n{functions}\n{resources}"); if self.opts.prettier { postprocess(&mut contents, "prettier", ["--parser=babel"]) @@ -238,3 +591,338 @@ fn print_function_params(params: &[(String, Type)]) -> String { .collect::>() .join(", ") } + +bitflags::bitflags! { + #[derive(Debug)] + struct SerdeUtils: u32 { + const VARINT_TYPE = 1 << 0; + const VARINT_MAX = 1 << 1; + const MAX_OF_LAST_BYTE = 1 << 2; + const _TRY_TAKE_VARINT = 1 << 3; + const DE_BOOl = 1 << 4; + const DE_U8 = 1 << 5; + const _DE_U16 = 1 << 6; + const _DE_U32 = 1 << 7; + const _DE_U64 = 1 << 8; + const DE_S8 = 1 << 9; + const _DE_S16 = 1 << 10; + const _DE_S32 = 1 << 11; + const _DE_S64 = 1 << 12; + const DE_F32 = 1 << 13; + const DE_F64 = 1 << 14; + const _DE_CHAR = 1 << 15; + const _DE_STRING = 1 << 16; + const _DE_BYTES = 1 << 17; + const DE_OPTION = 1 << 18; + const DE_RESULT = 1 << 19; + const _DE_LIST = 1 << 20; + + const TRY_TAKE_VARINT = Self::_TRY_TAKE_VARINT.bits() | Self::VARINT_TYPE.bits() | Self::VARINT_MAX.bits() | Self::MAX_OF_LAST_BYTE.bits(); + const DE_U16 = Self::_DE_U16.bits() | Self::TRY_TAKE_VARINT.bits(); + const DE_U32 = Self::_DE_U32.bits() | Self::TRY_TAKE_VARINT.bits(); + const DE_U64 = Self::_DE_U64.bits() | Self::TRY_TAKE_VARINT.bits(); + const DE_S16 = Self::_DE_S16.bits() | Self::TRY_TAKE_VARINT.bits(); + const DE_S32 = Self::_DE_S32.bits() | Self::TRY_TAKE_VARINT.bits(); + const DE_S64 = Self::_DE_S64.bits() | Self::TRY_TAKE_VARINT.bits(); + + const DE_CHAR = Self::_DE_CHAR.bits() | Self::DE_U64.bits(); + const DE_STRING = Self::_DE_STRING.bits() | Self::DE_U64.bits(); + const DE_BYTES = Self::_DE_BYTES.bits() | Self::DE_U64.bits(); + const DE_LIST = Self::_DE_LIST.bits() | Self::DE_U64.bits(); + } +} + +fn print_serde_utils(serde_utils: &SerdeUtils) -> Result { + let mut out = "export class Deserializer { + constructor(bytes) { + this.source = bytes + this.offset = 0 + } + + pop() { + return this.source[this.offset++] + } + + try_take_n(len) { + const out = this.source.slice(this.offset, this.offset + len) + this.offset += len + return out + } + } + " + .to_string(); + + if serde_utils.contains(SerdeUtils::VARINT_MAX) { + out.write_str( + "function varint_max(type) { + const BITS_PER_BYTE = 8; + const BITS_PER_VARINT_BYTE = 7; + + const bits = type * BITS_PER_BYTE; + + const roundup_bits = bits + (BITS_PER_BYTE - 1); + + return Math.floor(roundup_bits / BITS_PER_VARINT_BYTE); + } + ", + )?; + } + + if serde_utils.contains(SerdeUtils::MAX_OF_LAST_BYTE) { + out.write_str( + "function max_of_last_byte(type) { + let extra_bits = type % 7; + return (1 << extra_bits) - 1; + } + ", + )?; + } + + if serde_utils.contains(SerdeUtils::_TRY_TAKE_VARINT) { + out.write_str( + "function try_take_varint(de, type) { + let out = 0n; + + for (let i = 0; i < varint_max(type); i++) { + const val = de.pop(); + const carry = BigInt(val & 0x7F); + out |= carry << (7n * BigInt(i)); + + if ((val & 0x80) === 0) { + if (i === varint_max(type) - 1 && val > max_of_last_byte(type)) { + throw new Error('deserialize bad variant') + } else { + return out + } + } + } + + throw new Error('deserialize bad variant') + } + ", + )?; + } + + if serde_utils.contains(SerdeUtils::DE_BOOl) { + out.write_str( + "function deserializeBool(de) { + const val = de.pop(); + + return val != 0 + } + ", + )?; + } + + if serde_utils.contains(SerdeUtils::DE_U8) { + out.write_str( + "function deserializeU8(de) { + return de.pop() + } + ", + )?; + } + + if serde_utils.contains(SerdeUtils::_DE_U16) { + out.write_str( + "function deserializeU16(de) { + return try_take_varint(de, 16) + } + ", + )?; + } + + if serde_utils.contains(SerdeUtils::_DE_U32) { + out.write_str( + "function deserializeU32(de) { + return try_take_varint(de, 32) + } + ", + )?; + } + + if serde_utils.contains(SerdeUtils::_DE_U64) { + out.write_str( + "function deserializeU64(de) { + return try_take_varint(de, 64) + } + ", + )?; + } + + if serde_utils.contains(SerdeUtils::DE_S8) { + out.write_str( + "function deserializeI8(de) { + return de.pop() + } + ", + )?; + } + + if serde_utils.contains(SerdeUtils::_DE_S16) { + out.write_str( + "function deserializeI16(de) { + const n = try_take_varint(de, 16) + + return Number(((n >> 1n) & 0xFFFFn) ^ (-((n & 0b1n) & 0xFFFFn))) + } + ", + )?; + } + + if serde_utils.contains(SerdeUtils::_DE_S32) { + out.write_str( + "function deserializeI32(de) { + const n = try_take_varint(de, 32) + + return Number(((n >> 1n) as & 0xFFFFFFFFn) ^ (-((n & 0b1n) as & 0xFFFFFFFFn))) + } + ", + )?; + } + + if serde_utils.contains(SerdeUtils::_DE_S64) { + out.write_str( + "function deserializeI64(de) { + const n = try_take_varint(de, u64) + + return Number(((n >> 1n) & 0xFFFFFFFFFFFFFFFFn) ^ (-((n & 0b1n) & 0xFFFFFFFFFFFFFFFFn))) + } + ", + )?; + } + + if serde_utils.contains(SerdeUtils::DE_F32) { + out.write_str( + "function deserializeF32(de) { + const bytes = de.try_take_n(4); + + const buf = new ArrayBuffer(4); + const view = new DataView(buf); + + bytes.reverse().forEach((v, i) => view.setUint8(i, v)); + + return view.getFloat32(0); + } + ", + )?; + } + + if serde_utils.contains(SerdeUtils::DE_F64) { + out.write_str( + "function deserializeF64(de) { + const bytes = de.try_take_n(8); + + const buf = new ArrayBuffer(8); + const view = new DataView(buf); + + bytes.reverse().forEach((v, i) => view.setUint8(i, v)); + + return view.getFloat64(0); + } + ", + )?; + } + + if serde_utils.contains(SerdeUtils::_DE_CHAR) { + out.write_str( + r#"function deserializeChar(de) { + const sz = deserializeU64(de); + if (sz > 4) { + throw new Error("Deserialize bad char"); + } + const bytes = de.try_take_n(Number(sz)); + + const decoder = new TextDecoder('utf-8'); + + return decoder.decode(bytes); + } + "#, + )?; + } + + if serde_utils.contains(SerdeUtils::_DE_STRING) { + out.write_str( + "function deserializeString(de) { + const sz = deserializeU64(de); + + let bytes = de.try_take_n(Number(sz)); + + const decoder = new TextDecoder('utf-8'); + + return decoder.decode(bytes); + } + ", + )?; + } + + if serde_utils.contains(SerdeUtils::_DE_BYTES) { + out.write_str( + "function deserializeBytes(de) { + const sz = deserializeU64(de); + + let bytes = de.try_take_n(Number(sz)); + + const decoder = new TextDecoder('utf-8'); + + return decoder.decode(bytes); + } + ", + )?; + } + + if serde_utils.contains(SerdeUtils::DE_OPTION) { + out.write_str( + "function deserializeOption(de, inner) { + const disc = de.pop() + + switch (disc) { + case 0: + return null + case 1: + return inner(de) + default: + throw new Error('Deserialize bad option') + } + } + ", + )?; + } + + if serde_utils.contains(SerdeUtils::DE_RESULT) { + out.write_str( + "function function deserializeResult(de, ok, err) { + const disc = de.pop() + + switch (disc) { + case 0: + return ok(de) + case 1: + return err(de) + default: + throw new Error('Deserialize bad result') + } + } + ", + )?; + } + + if serde_utils.contains(SerdeUtils::_DE_LIST) { + out.write_str( + "function deserializeList(de, inner) { + const len = deserializeU64(de); + + let out: T[] = []; + + for (let i = 0; i < len; i++) { + out.push(inner(de)); + } + + return out; + } + ", + )?; + } + + Ok(out) +} diff --git a/crates/gen-guest-js/tests/chars.js b/crates/gen-guest-js/tests/chars.js index 910e5bf..c2e53da 100644 --- a/crates/gen-guest-js/tests/chars.js +++ b/crates/gen-guest-js/tests/chars.js @@ -1,10 +1,77 @@ +export class Deserializer { + source + offset + + constructor(bytes) { + this.source = bytes + this.offset = 0 + } + + pop() { + return this.source[this.offset++] + } + + try_take_n(len) { + const out = this.source.slice(this.offset, this.offset + len) + this.offset += len + return out + } + } + function varint_max(type) { + const BITS_PER_BYTE = 8; + const BITS_PER_VARINT_BYTE = 7; + + const bits = type * BITS_PER_BYTE; + + const roundup_bits = bits + (BITS_PER_BYTE - 1); + + return Math.floor(roundup_bits / BITS_PER_VARINT_BYTE); + } + function max_of_last_byte(type) { + let extra_bits = type % 7; + return (1 << extra_bits) - 1; + } + function try_take_varint(de, type) { + let out = 0n; + + for (let i = 0; i < varint_max(type); i++) { + const val = de.pop(); + const carry = BigInt(val & 0x7F); + out |= carry << (7n * BigInt(i)); + + if ((val & 0x80) === 0) { + if (i === varint_max(type) - 1 && val > max_of_last_byte(type)) { + throw new Error('deserialize bad variant') + } else { + return out + } + } + } + + throw new Error('deserialize bad variant') + } + function deserializeU64(de) { + return try_take_varint(de, 64) + } + function deserializeChar(de) { + const sz = deserializeU64(de); + if (sz > 4) { + throw new Error("Deserialize bad char"); + } + const bytes = de.try_take_n(Number(sz)); + + const decoder = new TextDecoder('utf-8'); + + return decoder.decode(bytes); + } + /** * A function that accepts a character * @param {string} x */ export async function takeChar (x) { - return fetch('ipc://localhost/chars/take_char', { method: "POST", body: JSON.stringify([x]) }).then(r => r.json()) + return fetch('ipc://localhost/chars/take_char', { method: "POST", body: JSON.stringify([x]) }) } /** @@ -12,6 +79,12 @@ * @returns {Promise} */ export async function returnChar () { - return fetch('ipc://localhost/chars/return_char', { method: "POST", body: JSON.stringify([]) }).then(r => r.json()) + return fetch('ipc://localhost/chars/return_char', { method: "POST", body: JSON.stringify([]) }) + .then(r => r.arrayBuffer()) + .then(bytes => { + const de = new Deserializer(Uint8Array.from(bytes)) + + return deserializeChar(de) + }) } diff --git a/crates/gen-guest-js/tests/codegen.rs b/crates/gen-guest-js/tests/codegen.rs index c98bbb8..e15cad0 100644 --- a/crates/gen-guest-js/tests/codegen.rs +++ b/crates/gen-guest-js/tests/codegen.rs @@ -11,7 +11,7 @@ fn gen_interface( ) -> (String, String) { let iface = wit_parser::parse_and_resolve_str(&input, |_| false).unwrap(); - let gen = opts.build(iface); + let mut gen = opts.build(iface); let (filename, contents) = gen.to_file(); (filename.to_str().unwrap().to_string(), contents) diff --git a/crates/gen-guest-js/tests/conventions.js b/crates/gen-guest-js/tests/conventions.js index afe74dd..2a1dc85 100644 --- a/crates/gen-guest-js/tests/conventions.js +++ b/crates/gen-guest-js/tests/conventions.js @@ -1,74 +1,94 @@ +export class Deserializer { + source + offset + + constructor(bytes) { + this.source = bytes + this.offset = 0 + } + + pop() { + return this.source[this.offset++] + } + + try_take_n(len) { + const out = this.source.slice(this.offset, this.offset + len) + this.offset += len + return out + } + } + /** */ export async function kebabCase () { - return fetch('ipc://localhost/conventions/kebab_case', { method: "POST", body: JSON.stringify([]) }).then(r => r.json()) + return fetch('ipc://localhost/conventions/kebab_case', { method: "POST", body: JSON.stringify([]) }) } /** * @param {LudicrousSpeed} x */ export async function foo (x) { - return fetch('ipc://localhost/conventions/foo', { method: "POST", body: JSON.stringify([x]) }).then(r => r.json()) + return fetch('ipc://localhost/conventions/foo', { method: "POST", body: JSON.stringify([x]) }) } /** */ export async function functionWithUnderscores () { - return fetch('ipc://localhost/conventions/function_with_underscores', { method: "POST", body: JSON.stringify([]) }).then(r => r.json()) + return fetch('ipc://localhost/conventions/function_with_underscores', { method: "POST", body: JSON.stringify([]) }) } /** */ export async function functionWithNoWeirdCharacters () { - return fetch('ipc://localhost/conventions/function_with_no_weird_characters', { method: "POST", body: JSON.stringify([]) }).then(r => r.json()) + return fetch('ipc://localhost/conventions/function_with_no_weird_characters', { method: "POST", body: JSON.stringify([]) }) } /** */ export async function apple () { - return fetch('ipc://localhost/conventions/apple', { method: "POST", body: JSON.stringify([]) }).then(r => r.json()) + return fetch('ipc://localhost/conventions/apple', { method: "POST", body: JSON.stringify([]) }) } /** */ export async function applePear () { - return fetch('ipc://localhost/conventions/apple_pear', { method: "POST", body: JSON.stringify([]) }).then(r => r.json()) + return fetch('ipc://localhost/conventions/apple_pear', { method: "POST", body: JSON.stringify([]) }) } /** */ export async function applePearGrape () { - return fetch('ipc://localhost/conventions/apple_pear_grape', { method: "POST", body: JSON.stringify([]) }).then(r => r.json()) + return fetch('ipc://localhost/conventions/apple_pear_grape', { method: "POST", body: JSON.stringify([]) }) } /** */ export async function a0 () { - return fetch('ipc://localhost/conventions/a0', { method: "POST", body: JSON.stringify([]) }).then(r => r.json()) + return fetch('ipc://localhost/conventions/a0', { method: "POST", body: JSON.stringify([]) }) } /** */ export async function isXml () { - return fetch('ipc://localhost/conventions/is_xml', { method: "POST", body: JSON.stringify([]) }).then(r => r.json()) + return fetch('ipc://localhost/conventions/is_xml', { method: "POST", body: JSON.stringify([]) }) } /** */ export async function explicit () { - return fetch('ipc://localhost/conventions/explicit', { method: "POST", body: JSON.stringify([]) }).then(r => r.json()) + return fetch('ipc://localhost/conventions/explicit', { method: "POST", body: JSON.stringify([]) }) } /** */ export async function explicitSnake () { - return fetch('ipc://localhost/conventions/explicit_snake', { method: "POST", body: JSON.stringify([]) }).then(r => r.json()) + return fetch('ipc://localhost/conventions/explicit_snake', { method: "POST", body: JSON.stringify([]) }) } /** */ export async function bool () { - return fetch('ipc://localhost/conventions/bool', { method: "POST", body: JSON.stringify([]) }).then(r => r.json()) + return fetch('ipc://localhost/conventions/bool', { method: "POST", body: JSON.stringify([]) }) } diff --git a/crates/gen-guest-js/tests/empty.js b/crates/gen-guest-js/tests/empty.js index 8b13789..229b1d1 100644 --- a/crates/gen-guest-js/tests/empty.js +++ b/crates/gen-guest-js/tests/empty.js @@ -1 +1,21 @@ +export class Deserializer { + source + offset + + constructor(bytes) { + this.source = bytes + this.offset = 0 + } + + pop() { + return this.source[this.offset++] + } + + try_take_n(len) { + const out = this.source.slice(this.offset, this.offset + len) + this.offset += len + return out + } + } + diff --git a/crates/gen-guest-js/tests/flegs.js b/crates/gen-guest-js/tests/flegs.js index 0d730c2..ca2b348 100644 --- a/crates/gen-guest-js/tests/flegs.js +++ b/crates/gen-guest-js/tests/flegs.js @@ -1,10 +1,50 @@ +export class Deserializer { + source + offset + + constructor(bytes) { + this.source = bytes + this.offset = 0 + } + + pop() { + return this.source[this.offset++] + } + + try_take_n(len) { + const out = this.source.slice(this.offset, this.offset + len) + this.offset += len + return out + } + } + function deserializeFlag1(de) { + return deserializeU8(de) + }function deserializeFlag2(de) { + return deserializeU8(de) + }function deserializeFlag4(de) { + return deserializeU8(de) + }function deserializeFlag8(de) { + return deserializeU8(de) + }function deserializeFlag16(de) { + return deserializeU16(de) + }function deserializeFlag32(de) { + return deserializeU32(de) + }function deserializeFlag64(de) { + return deserializeU64(de) + } /** * @param {Flag1} x * @returns {Promise} */ export async function roundtripFlag1 (x) { - return fetch('ipc://localhost/flegs/roundtrip_flag1', { method: "POST", body: JSON.stringify([x]) }).then(r => r.json()) + return fetch('ipc://localhost/flegs/roundtrip_flag1', { method: "POST", body: JSON.stringify([x]) }) + .then(r => r.arrayBuffer()) + .then(bytes => { + const de = new Deserializer(Uint8Array.from(bytes)) + + return deserializeFlag1(de) + }) } /** @@ -12,7 +52,13 @@ * @returns {Promise} */ export async function roundtripFlag2 (x) { - return fetch('ipc://localhost/flegs/roundtrip_flag2', { method: "POST", body: JSON.stringify([x]) }).then(r => r.json()) + return fetch('ipc://localhost/flegs/roundtrip_flag2', { method: "POST", body: JSON.stringify([x]) }) + .then(r => r.arrayBuffer()) + .then(bytes => { + const de = new Deserializer(Uint8Array.from(bytes)) + + return deserializeFlag2(de) + }) } /** @@ -20,7 +66,13 @@ * @returns {Promise} */ export async function roundtripFlag4 (x) { - return fetch('ipc://localhost/flegs/roundtrip_flag4', { method: "POST", body: JSON.stringify([x]) }).then(r => r.json()) + return fetch('ipc://localhost/flegs/roundtrip_flag4', { method: "POST", body: JSON.stringify([x]) }) + .then(r => r.arrayBuffer()) + .then(bytes => { + const de = new Deserializer(Uint8Array.from(bytes)) + + return deserializeFlag4(de) + }) } /** @@ -28,7 +80,13 @@ * @returns {Promise} */ export async function roundtripFlag8 (x) { - return fetch('ipc://localhost/flegs/roundtrip_flag8', { method: "POST", body: JSON.stringify([x]) }).then(r => r.json()) + return fetch('ipc://localhost/flegs/roundtrip_flag8', { method: "POST", body: JSON.stringify([x]) }) + .then(r => r.arrayBuffer()) + .then(bytes => { + const de = new Deserializer(Uint8Array.from(bytes)) + + return deserializeFlag8(de) + }) } /** @@ -36,7 +94,13 @@ * @returns {Promise} */ export async function roundtripFlag16 (x) { - return fetch('ipc://localhost/flegs/roundtrip_flag16', { method: "POST", body: JSON.stringify([x]) }).then(r => r.json()) + return fetch('ipc://localhost/flegs/roundtrip_flag16', { method: "POST", body: JSON.stringify([x]) }) + .then(r => r.arrayBuffer()) + .then(bytes => { + const de = new Deserializer(Uint8Array.from(bytes)) + + return deserializeFlag16(de) + }) } /** @@ -44,7 +108,13 @@ * @returns {Promise} */ export async function roundtripFlag32 (x) { - return fetch('ipc://localhost/flegs/roundtrip_flag32', { method: "POST", body: JSON.stringify([x]) }).then(r => r.json()) + return fetch('ipc://localhost/flegs/roundtrip_flag32', { method: "POST", body: JSON.stringify([x]) }) + .then(r => r.arrayBuffer()) + .then(bytes => { + const de = new Deserializer(Uint8Array.from(bytes)) + + return deserializeFlag32(de) + }) } /** @@ -52,6 +122,12 @@ * @returns {Promise} */ export async function roundtripFlag64 (x) { - return fetch('ipc://localhost/flegs/roundtrip_flag64', { method: "POST", body: JSON.stringify([x]) }).then(r => r.json()) + return fetch('ipc://localhost/flegs/roundtrip_flag64', { method: "POST", body: JSON.stringify([x]) }) + .then(r => r.arrayBuffer()) + .then(bytes => { + const de = new Deserializer(Uint8Array.from(bytes)) + + return deserializeFlag64(de) + }) } diff --git a/crates/gen-guest-js/tests/floats.js b/crates/gen-guest-js/tests/floats.js index 9178850..09eef47 100644 --- a/crates/gen-guest-js/tests/floats.js +++ b/crates/gen-guest-js/tests/floats.js @@ -1,29 +1,81 @@ +export class Deserializer { + source + offset + + constructor(bytes) { + this.source = bytes + this.offset = 0 + } + + pop() { + return this.source[this.offset++] + } + + try_take_n(len) { + const out = this.source.slice(this.offset, this.offset + len) + this.offset += len + return out + } + } + function deserializeF32(de) { + const bytes = de.try_take_n(4); + + const buf = new ArrayBuffer(4); + const view = new DataView(buf); + + bytes.reverse().forEach((v, i) => view.setUint8(i, v)); + + return view.getFloat32(0); + } + function deserializeF64(de) { + const bytes = de.try_take_n(8); + + const buf = new ArrayBuffer(8); + const view = new DataView(buf); + + bytes.reverse().forEach((v, i) => view.setUint8(i, v)); + + return view.getFloat64(0); + } + /** * @param {number} x */ export async function float32Param (x) { - return fetch('ipc://localhost/floats/float32_param', { method: "POST", body: JSON.stringify([x]) }).then(r => r.json()) + return fetch('ipc://localhost/floats/float32_param', { method: "POST", body: JSON.stringify([x]) }) } /** * @param {number} x */ export async function float64Param (x) { - return fetch('ipc://localhost/floats/float64_param', { method: "POST", body: JSON.stringify([x]) }).then(r => r.json()) + return fetch('ipc://localhost/floats/float64_param', { method: "POST", body: JSON.stringify([x]) }) } /** * @returns {Promise} */ export async function float32Result () { - return fetch('ipc://localhost/floats/float32_result', { method: "POST", body: JSON.stringify([]) }).then(r => r.json()) + return fetch('ipc://localhost/floats/float32_result', { method: "POST", body: JSON.stringify([]) }) + .then(r => r.arrayBuffer()) + .then(bytes => { + const de = new Deserializer(Uint8Array.from(bytes)) + + return deserializeF32(de) + }) } /** * @returns {Promise} */ export async function float64Result () { - return fetch('ipc://localhost/floats/float64_result', { method: "POST", body: JSON.stringify([]) }).then(r => r.json()) + return fetch('ipc://localhost/floats/float64_result', { method: "POST", body: JSON.stringify([]) }) + .then(r => r.arrayBuffer()) + .then(bytes => { + const de = new Deserializer(Uint8Array.from(bytes)) + + return deserializeF64(de) + }) } diff --git a/crates/gen-guest-js/tests/integers.js b/crates/gen-guest-js/tests/integers.js index 022e0a4..f56137e 100644 --- a/crates/gen-guest-js/tests/integers.js +++ b/crates/gen-guest-js/tests/integers.js @@ -1,58 +1,141 @@ +export class Deserializer { + source + offset + + constructor(bytes) { + this.source = bytes + this.offset = 0 + } + + pop() { + return this.source[this.offset++] + } + + try_take_n(len) { + const out = this.source.slice(this.offset, this.offset + len) + this.offset += len + return out + } + } + function varint_max(type) { + const BITS_PER_BYTE = 8; + const BITS_PER_VARINT_BYTE = 7; + + const bits = type * BITS_PER_BYTE; + + const roundup_bits = bits + (BITS_PER_BYTE - 1); + + return Math.floor(roundup_bits / BITS_PER_VARINT_BYTE); + } + function max_of_last_byte(type) { + let extra_bits = type % 7; + return (1 << extra_bits) - 1; + } + function try_take_varint(de, type) { + let out = 0n; + + for (let i = 0; i < varint_max(type); i++) { + const val = de.pop(); + const carry = BigInt(val & 0x7F); + out |= carry << (7n * BigInt(i)); + + if ((val & 0x80) === 0) { + if (i === varint_max(type) - 1 && val > max_of_last_byte(type)) { + throw new Error('deserialize bad variant') + } else { + return out + } + } + } + + throw new Error('deserialize bad variant') + } + function deserializeU8(de) { + return de.pop() + } + function deserializeU16(de) { + return try_take_varint(de, 16) + } + function deserializeU32(de) { + return try_take_varint(de, 32) + } + function deserializeU64(de) { + return try_take_varint(de, 64) + } + function deserializeI8(de) { + return de.pop() + } + function deserializeI16(de) { + const n = try_take_varint(de, 16) + + return Number(((n >> 1n) & 0xFFFFn) ^ (-((n & 0b1n) & 0xFFFFn))) + } + function deserializeI32(de) { + const n = try_take_varint(de, 32) + + return Number(((n >> 1n) as & 0xFFFFFFFFn) ^ (-((n & 0b1n) as & 0xFFFFFFFFn))) + } + function deserializeI64(de) { + const n = try_take_varint(de, u64) + + return Number(((n >> 1n) & 0xFFFFFFFFFFFFFFFFn) ^ (-((n & 0b1n) & 0xFFFFFFFFFFFFFFFFn))) + } + /** * @param {number} x */ export async function a1 (x) { - return fetch('ipc://localhost/integers/a1', { method: "POST", body: JSON.stringify([x]) }).then(r => r.json()) + return fetch('ipc://localhost/integers/a1', { method: "POST", body: JSON.stringify([x]) }) } /** * @param {number} x */ export async function a2 (x) { - return fetch('ipc://localhost/integers/a2', { method: "POST", body: JSON.stringify([x]) }).then(r => r.json()) + return fetch('ipc://localhost/integers/a2', { method: "POST", body: JSON.stringify([x]) }) } /** * @param {number} x */ export async function a3 (x) { - return fetch('ipc://localhost/integers/a3', { method: "POST", body: JSON.stringify([x]) }).then(r => r.json()) + return fetch('ipc://localhost/integers/a3', { method: "POST", body: JSON.stringify([x]) }) } /** * @param {number} x */ export async function a4 (x) { - return fetch('ipc://localhost/integers/a4', { method: "POST", body: JSON.stringify([x]) }).then(r => r.json()) + return fetch('ipc://localhost/integers/a4', { method: "POST", body: JSON.stringify([x]) }) } /** * @param {number} x */ export async function a5 (x) { - return fetch('ipc://localhost/integers/a5', { method: "POST", body: JSON.stringify([x]) }).then(r => r.json()) + return fetch('ipc://localhost/integers/a5', { method: "POST", body: JSON.stringify([x]) }) } /** * @param {number} x */ export async function a6 (x) { - return fetch('ipc://localhost/integers/a6', { method: "POST", body: JSON.stringify([x]) }).then(r => r.json()) + return fetch('ipc://localhost/integers/a6', { method: "POST", body: JSON.stringify([x]) }) } /** * @param {bigint} x */ export async function a7 (x) { - return fetch('ipc://localhost/integers/a7', { method: "POST", body: JSON.stringify([x]) }).then(r => r.json()) + return fetch('ipc://localhost/integers/a7', { method: "POST", body: JSON.stringify([x]) }) } /** * @param {bigint} x */ export async function a8 (x) { - return fetch('ipc://localhost/integers/a8', { method: "POST", body: JSON.stringify([x]) }).then(r => r.json()) + return fetch('ipc://localhost/integers/a8', { method: "POST", body: JSON.stringify([x]) }) } /** @@ -66,69 +149,123 @@ * @param {bigint} p8 */ export async function a9 (p1, p2, p3, p4, p5, p6, p7, p8) { - return fetch('ipc://localhost/integers/a9', { method: "POST", body: JSON.stringify([p1, p2, p3, p4, p5, p6, p7, p8]) }).then(r => r.json()) + return fetch('ipc://localhost/integers/a9', { method: "POST", body: JSON.stringify([p1, p2, p3, p4, p5, p6, p7, p8]) }) } /** * @returns {Promise} */ export async function r1 () { - return fetch('ipc://localhost/integers/r1', { method: "POST", body: JSON.stringify([]) }).then(r => r.json()) + return fetch('ipc://localhost/integers/r1', { method: "POST", body: JSON.stringify([]) }) + .then(r => r.arrayBuffer()) + .then(bytes => { + const de = new Deserializer(Uint8Array.from(bytes)) + + return deserializeU8(de) + }) } /** * @returns {Promise} */ export async function r2 () { - return fetch('ipc://localhost/integers/r2', { method: "POST", body: JSON.stringify([]) }).then(r => r.json()) + return fetch('ipc://localhost/integers/r2', { method: "POST", body: JSON.stringify([]) }) + .then(r => r.arrayBuffer()) + .then(bytes => { + const de = new Deserializer(Uint8Array.from(bytes)) + + return deserializeS8(de) + }) } /** * @returns {Promise} */ export async function r3 () { - return fetch('ipc://localhost/integers/r3', { method: "POST", body: JSON.stringify([]) }).then(r => r.json()) + return fetch('ipc://localhost/integers/r3', { method: "POST", body: JSON.stringify([]) }) + .then(r => r.arrayBuffer()) + .then(bytes => { + const de = new Deserializer(Uint8Array.from(bytes)) + + return deserializeU16(de) + }) } /** * @returns {Promise} */ export async function r4 () { - return fetch('ipc://localhost/integers/r4', { method: "POST", body: JSON.stringify([]) }).then(r => r.json()) + return fetch('ipc://localhost/integers/r4', { method: "POST", body: JSON.stringify([]) }) + .then(r => r.arrayBuffer()) + .then(bytes => { + const de = new Deserializer(Uint8Array.from(bytes)) + + return deserializeS16(de) + }) } /** * @returns {Promise} */ export async function r5 () { - return fetch('ipc://localhost/integers/r5', { method: "POST", body: JSON.stringify([]) }).then(r => r.json()) + return fetch('ipc://localhost/integers/r5', { method: "POST", body: JSON.stringify([]) }) + .then(r => r.arrayBuffer()) + .then(bytes => { + const de = new Deserializer(Uint8Array.from(bytes)) + + return deserializeU32(de) + }) } /** * @returns {Promise} */ export async function r6 () { - return fetch('ipc://localhost/integers/r6', { method: "POST", body: JSON.stringify([]) }).then(r => r.json()) + return fetch('ipc://localhost/integers/r6', { method: "POST", body: JSON.stringify([]) }) + .then(r => r.arrayBuffer()) + .then(bytes => { + const de = new Deserializer(Uint8Array.from(bytes)) + + return deserializeS32(de) + }) } /** * @returns {Promise} */ export async function r7 () { - return fetch('ipc://localhost/integers/r7', { method: "POST", body: JSON.stringify([]) }).then(r => r.json()) + return fetch('ipc://localhost/integers/r7', { method: "POST", body: JSON.stringify([]) }) + .then(r => r.arrayBuffer()) + .then(bytes => { + const de = new Deserializer(Uint8Array.from(bytes)) + + return deserializeU64(de) + }) } /** * @returns {Promise} */ export async function r8 () { - return fetch('ipc://localhost/integers/r8', { method: "POST", body: JSON.stringify([]) }).then(r => r.json()) + return fetch('ipc://localhost/integers/r8', { method: "POST", body: JSON.stringify([]) }) + .then(r => r.arrayBuffer()) + .then(bytes => { + const de = new Deserializer(Uint8Array.from(bytes)) + + return deserializeS64(de) + }) } /** * @returns {Promise<[bigint, number]>} */ export async function pairRet () { - return fetch('ipc://localhost/integers/pair_ret', { method: "POST", body: JSON.stringify([]) }).then(r => r.json()) + return fetch('ipc://localhost/integers/pair_ret', { method: "POST", body: JSON.stringify([]) }) + .then(r => r.arrayBuffer()) + .then(bytes => { + const de = new Deserializer(Uint8Array.from(bytes)) + + return [deserializeS64(de), deserializeU8(de)] + }) } diff --git a/crates/gen-guest-js/tests/lists.js b/crates/gen-guest-js/tests/lists.js index 835dd11..602a45c 100644 --- a/crates/gen-guest-js/tests/lists.js +++ b/crates/gen-guest-js/tests/lists.js @@ -1,142 +1,380 @@ +export class Deserializer { + source + offset + + constructor(bytes) { + this.source = bytes + this.offset = 0 + } + + pop() { + return this.source[this.offset++] + } + + try_take_n(len) { + const out = this.source.slice(this.offset, this.offset + len) + this.offset += len + return out + } + } + function varint_max(type) { + const BITS_PER_BYTE = 8; + const BITS_PER_VARINT_BYTE = 7; + + const bits = type * BITS_PER_BYTE; + + const roundup_bits = bits + (BITS_PER_BYTE - 1); + + return Math.floor(roundup_bits / BITS_PER_VARINT_BYTE); + } + function max_of_last_byte(type) { + let extra_bits = type % 7; + return (1 << extra_bits) - 1; + } + function try_take_varint(de, type) { + let out = 0n; + + for (let i = 0; i < varint_max(type); i++) { + const val = de.pop(); + const carry = BigInt(val & 0x7F); + out |= carry << (7n * BigInt(i)); + + if ((val & 0x80) === 0) { + if (i === varint_max(type) - 1 && val > max_of_last_byte(type)) { + throw new Error('deserialize bad variant') + } else { + return out + } + } + } + + throw new Error('deserialize bad variant') + } + function deserializeU8(de) { + return de.pop() + } + function deserializeU16(de) { + return try_take_varint(de, 16) + } + function deserializeU32(de) { + return try_take_varint(de, 32) + } + function deserializeU64(de) { + return try_take_varint(de, 64) + } + function deserializeI8(de) { + return de.pop() + } + function deserializeI16(de) { + const n = try_take_varint(de, 16) + + return Number(((n >> 1n) & 0xFFFFn) ^ (-((n & 0b1n) & 0xFFFFn))) + } + function deserializeI32(de) { + const n = try_take_varint(de, 32) + + return Number(((n >> 1n) as & 0xFFFFFFFFn) ^ (-((n & 0b1n) as & 0xFFFFFFFFn))) + } + function deserializeI64(de) { + const n = try_take_varint(de, u64) + + return Number(((n >> 1n) & 0xFFFFFFFFFFFFFFFFn) ^ (-((n & 0b1n) & 0xFFFFFFFFFFFFFFFFn))) + } + function deserializeF32(de) { + const bytes = de.try_take_n(4); + + const buf = new ArrayBuffer(4); + const view = new DataView(buf); + + bytes.reverse().forEach((v, i) => view.setUint8(i, v)); + + return view.getFloat32(0); + } + function deserializeF64(de) { + const bytes = de.try_take_n(8); + + const buf = new ArrayBuffer(8); + const view = new DataView(buf); + + bytes.reverse().forEach((v, i) => view.setUint8(i, v)); + + return view.getFloat64(0); + } + function deserializeChar(de) { + const sz = deserializeU64(de); + if (sz > 4) { + throw new Error("Deserialize bad char"); + } + const bytes = de.try_take_n(Number(sz)); + + const decoder = new TextDecoder('utf-8'); + + return decoder.decode(bytes); + } + function deserializeString(de) { + const sz = deserializeU64(de); + + let bytes = de.try_take_n(Number(sz)); + + const decoder = new TextDecoder('utf-8'); + + return decoder.decode(bytes); + } + function deserializeBytes(de) { + const sz = deserializeU64(de); + + let bytes = de.try_take_n(Number(sz)); + + const decoder = new TextDecoder('utf-8'); + + return decoder.decode(bytes); + } + function deserializeList(de, inner) { + const len = deserializeU64(de); + + let out: T[] = []; + + for (let i = 0; i < len; i++) { + out.push(inner(de)); + } + + return out; + } + function deserializeOtherRecord(de) { + return { + a1: deserializeU32(de), +a2: deserializeU64(de), +a3: deserializeS32(de), +a4: deserializeS64(de), +b: deserializeString(de), +c: deserializeBytes(de) + } + }function deserializeSomeRecord(de) { + return { + x: deserializeString(de), +y: deserializeOtherRecord(de), +z: deserializeList(de, (de) => deserializeOtherRecord(de)), +c1: deserializeU32(de), +c2: deserializeU64(de), +c3: deserializeS32(de), +c4: deserializeS64(de) + } + }function deserializeOtherVariant(de) { + const disc = deserializeU32(de) + + switch (disc) { + case 0: + return { tag: 0, value: null } + case 1: + return { tag: 1, value: deserializeU32(de) } + case 2: + return { tag: 2, value: deserializeString(de) } + + default: + throw new Error("unknown variant case") + } + }function deserializeLoadStoreAllSizes(de) { + return deserializeList(de, (de) => [deserializeString(de), deserializeU8(de), deserializeS8(de), deserializeU16(de), deserializeS16(de), deserializeU32(de), deserializeS32(de), deserializeU64(de), deserializeS64(de), deserializeF32(de), deserializeF64(de), deserializeChar(de)]) + } /** * @param {Uint8Array[]} x */ export async function listU8Param (x) { - return fetch('ipc://localhost/lists/list_u8_param', { method: "POST", body: JSON.stringify([x]) }).then(r => r.json()) + return fetch('ipc://localhost/lists/list_u8_param', { method: "POST", body: JSON.stringify([x]) }) } /** * @param {Uint16Array[]} x */ export async function listU16Param (x) { - return fetch('ipc://localhost/lists/list_u16_param', { method: "POST", body: JSON.stringify([x]) }).then(r => r.json()) + return fetch('ipc://localhost/lists/list_u16_param', { method: "POST", body: JSON.stringify([x]) }) } /** * @param {Uint32Array[]} x */ export async function listU32Param (x) { - return fetch('ipc://localhost/lists/list_u32_param', { method: "POST", body: JSON.stringify([x]) }).then(r => r.json()) + return fetch('ipc://localhost/lists/list_u32_param', { method: "POST", body: JSON.stringify([x]) }) } /** * @param {BigUint64Array[]} x */ export async function listU64Param (x) { - return fetch('ipc://localhost/lists/list_u64_param', { method: "POST", body: JSON.stringify([x]) }).then(r => r.json()) + return fetch('ipc://localhost/lists/list_u64_param', { method: "POST", body: JSON.stringify([x]) }) } /** * @param {Int8Array[]} x */ export async function listS8Param (x) { - return fetch('ipc://localhost/lists/list_s8_param', { method: "POST", body: JSON.stringify([x]) }).then(r => r.json()) + return fetch('ipc://localhost/lists/list_s8_param', { method: "POST", body: JSON.stringify([x]) }) } /** * @param {Int16Array[]} x */ export async function listS16Param (x) { - return fetch('ipc://localhost/lists/list_s16_param', { method: "POST", body: JSON.stringify([x]) }).then(r => r.json()) + return fetch('ipc://localhost/lists/list_s16_param', { method: "POST", body: JSON.stringify([x]) }) } /** * @param {Int32Array[]} x */ export async function listS32Param (x) { - return fetch('ipc://localhost/lists/list_s32_param', { method: "POST", body: JSON.stringify([x]) }).then(r => r.json()) + return fetch('ipc://localhost/lists/list_s32_param', { method: "POST", body: JSON.stringify([x]) }) } /** * @param {BigInt64Array[]} x */ export async function listS64Param (x) { - return fetch('ipc://localhost/lists/list_s64_param', { method: "POST", body: JSON.stringify([x]) }).then(r => r.json()) + return fetch('ipc://localhost/lists/list_s64_param', { method: "POST", body: JSON.stringify([x]) }) } /** * @param {Float32Array[]} x */ export async function listFloat32Param (x) { - return fetch('ipc://localhost/lists/list_float32_param', { method: "POST", body: JSON.stringify([x]) }).then(r => r.json()) + return fetch('ipc://localhost/lists/list_float32_param', { method: "POST", body: JSON.stringify([x]) }) } /** * @param {Float64Array[]} x */ export async function listFloat64Param (x) { - return fetch('ipc://localhost/lists/list_float64_param', { method: "POST", body: JSON.stringify([x]) }).then(r => r.json()) + return fetch('ipc://localhost/lists/list_float64_param', { method: "POST", body: JSON.stringify([x]) }) } /** * @returns {Promise} */ export async function listU8Ret () { - return fetch('ipc://localhost/lists/list_u8_ret', { method: "POST", body: JSON.stringify([]) }).then(r => r.json()) + return fetch('ipc://localhost/lists/list_u8_ret', { method: "POST", body: JSON.stringify([]) }) + .then(r => r.arrayBuffer()) + .then(bytes => { + const de = new Deserializer(Uint8Array.from(bytes)) + + return deserializeBytes(de) + }) } /** * @returns {Promise} */ export async function listU16Ret () { - return fetch('ipc://localhost/lists/list_u16_ret', { method: "POST", body: JSON.stringify([]) }).then(r => r.json()) + return fetch('ipc://localhost/lists/list_u16_ret', { method: "POST", body: JSON.stringify([]) }) + .then(r => r.arrayBuffer()) + .then(bytes => { + const de = new Deserializer(Uint8Array.from(bytes)) + + return deserializeList(de, (de) => deserializeU16(de)) + }) } /** * @returns {Promise} */ export async function listU32Ret () { - return fetch('ipc://localhost/lists/list_u32_ret', { method: "POST", body: JSON.stringify([]) }).then(r => r.json()) + return fetch('ipc://localhost/lists/list_u32_ret', { method: "POST", body: JSON.stringify([]) }) + .then(r => r.arrayBuffer()) + .then(bytes => { + const de = new Deserializer(Uint8Array.from(bytes)) + + return deserializeList(de, (de) => deserializeU32(de)) + }) } /** * @returns {Promise} */ export async function listU64Ret () { - return fetch('ipc://localhost/lists/list_u64_ret', { method: "POST", body: JSON.stringify([]) }).then(r => r.json()) + return fetch('ipc://localhost/lists/list_u64_ret', { method: "POST", body: JSON.stringify([]) }) + .then(r => r.arrayBuffer()) + .then(bytes => { + const de = new Deserializer(Uint8Array.from(bytes)) + + return deserializeList(de, (de) => deserializeU64(de)) + }) } /** * @returns {Promise} */ export async function listS8Ret () { - return fetch('ipc://localhost/lists/list_s8_ret', { method: "POST", body: JSON.stringify([]) }).then(r => r.json()) + return fetch('ipc://localhost/lists/list_s8_ret', { method: "POST", body: JSON.stringify([]) }) + .then(r => r.arrayBuffer()) + .then(bytes => { + const de = new Deserializer(Uint8Array.from(bytes)) + + return deserializeList(de, (de) => deserializeS8(de)) + }) } /** * @returns {Promise} */ export async function listS16Ret () { - return fetch('ipc://localhost/lists/list_s16_ret', { method: "POST", body: JSON.stringify([]) }).then(r => r.json()) + return fetch('ipc://localhost/lists/list_s16_ret', { method: "POST", body: JSON.stringify([]) }) + .then(r => r.arrayBuffer()) + .then(bytes => { + const de = new Deserializer(Uint8Array.from(bytes)) + + return deserializeList(de, (de) => deserializeS16(de)) + }) } /** * @returns {Promise} */ export async function listS32Ret () { - return fetch('ipc://localhost/lists/list_s32_ret', { method: "POST", body: JSON.stringify([]) }).then(r => r.json()) + return fetch('ipc://localhost/lists/list_s32_ret', { method: "POST", body: JSON.stringify([]) }) + .then(r => r.arrayBuffer()) + .then(bytes => { + const de = new Deserializer(Uint8Array.from(bytes)) + + return deserializeList(de, (de) => deserializeS32(de)) + }) } /** * @returns {Promise} */ export async function listS64Ret () { - return fetch('ipc://localhost/lists/list_s64_ret', { method: "POST", body: JSON.stringify([]) }).then(r => r.json()) + return fetch('ipc://localhost/lists/list_s64_ret', { method: "POST", body: JSON.stringify([]) }) + .then(r => r.arrayBuffer()) + .then(bytes => { + const de = new Deserializer(Uint8Array.from(bytes)) + + return deserializeList(de, (de) => deserializeS64(de)) + }) } /** * @returns {Promise} */ export async function listFloat32Ret () { - return fetch('ipc://localhost/lists/list_float32_ret', { method: "POST", body: JSON.stringify([]) }).then(r => r.json()) + return fetch('ipc://localhost/lists/list_float32_ret', { method: "POST", body: JSON.stringify([]) }) + .then(r => r.arrayBuffer()) + .then(bytes => { + const de = new Deserializer(Uint8Array.from(bytes)) + + return deserializeList(de, (de) => deserializeF32(de)) + }) } /** * @returns {Promise} */ export async function listFloat64Ret () { - return fetch('ipc://localhost/lists/list_float64_ret', { method: "POST", body: JSON.stringify([]) }).then(r => r.json()) + return fetch('ipc://localhost/lists/list_float64_ret', { method: "POST", body: JSON.stringify([]) }) + .then(r => r.arrayBuffer()) + .then(bytes => { + const de = new Deserializer(Uint8Array.from(bytes)) + + return deserializeList(de, (de) => deserializeF64(de)) + }) } /** @@ -144,21 +382,33 @@ * @returns {Promise<[bigint, number][]>} */ export async function tupleList (x) { - return fetch('ipc://localhost/lists/tuple_list', { method: "POST", body: JSON.stringify([x]) }).then(r => r.json()) + return fetch('ipc://localhost/lists/tuple_list', { method: "POST", body: JSON.stringify([x]) }) + .then(r => r.arrayBuffer()) + .then(bytes => { + const de = new Deserializer(Uint8Array.from(bytes)) + + return deserializeList(de, (de) => [deserializeS64(de), deserializeU32(de)]) + }) } /** * @param {string[]} a */ export async function stringListArg (a) { - return fetch('ipc://localhost/lists/string_list_arg', { method: "POST", body: JSON.stringify([a]) }).then(r => r.json()) + return fetch('ipc://localhost/lists/string_list_arg', { method: "POST", body: JSON.stringify([a]) }) } /** * @returns {Promise} */ export async function stringListRet () { - return fetch('ipc://localhost/lists/string_list_ret', { method: "POST", body: JSON.stringify([]) }).then(r => r.json()) + return fetch('ipc://localhost/lists/string_list_ret', { method: "POST", body: JSON.stringify([]) }) + .then(r => r.arrayBuffer()) + .then(bytes => { + const de = new Deserializer(Uint8Array.from(bytes)) + + return deserializeList(de, (de) => deserializeString(de)) + }) } /** @@ -166,7 +416,13 @@ * @returns {Promise<[string, number][]>} */ export async function tupleStringList (x) { - return fetch('ipc://localhost/lists/tuple_string_list', { method: "POST", body: JSON.stringify([x]) }).then(r => r.json()) + return fetch('ipc://localhost/lists/tuple_string_list', { method: "POST", body: JSON.stringify([x]) }) + .then(r => r.arrayBuffer()) + .then(bytes => { + const de = new Deserializer(Uint8Array.from(bytes)) + + return deserializeList(de, (de) => [deserializeString(de), deserializeU8(de)]) + }) } /** @@ -174,7 +430,13 @@ * @returns {Promise} */ export async function stringList (x) { - return fetch('ipc://localhost/lists/string_list', { method: "POST", body: JSON.stringify([x]) }).then(r => r.json()) + return fetch('ipc://localhost/lists/string_list', { method: "POST", body: JSON.stringify([x]) }) + .then(r => r.arrayBuffer()) + .then(bytes => { + const de = new Deserializer(Uint8Array.from(bytes)) + + return deserializeList(de, (de) => deserializeString(de)) + }) } /** @@ -182,7 +444,13 @@ * @returns {Promise} */ export async function recordList (x) { - return fetch('ipc://localhost/lists/record_list', { method: "POST", body: JSON.stringify([x]) }).then(r => r.json()) + return fetch('ipc://localhost/lists/record_list', { method: "POST", body: JSON.stringify([x]) }) + .then(r => r.arrayBuffer()) + .then(bytes => { + const de = new Deserializer(Uint8Array.from(bytes)) + + return deserializeList(de, (de) => deserializeOtherRecord(de)) + }) } /** @@ -190,7 +458,13 @@ * @returns {Promise} */ export async function recordListReverse (x) { - return fetch('ipc://localhost/lists/record_list_reverse', { method: "POST", body: JSON.stringify([x]) }).then(r => r.json()) + return fetch('ipc://localhost/lists/record_list_reverse', { method: "POST", body: JSON.stringify([x]) }) + .then(r => r.arrayBuffer()) + .then(bytes => { + const de = new Deserializer(Uint8Array.from(bytes)) + + return deserializeList(de, (de) => deserializeSomeRecord(de)) + }) } /** @@ -198,7 +472,13 @@ * @returns {Promise} */ export async function variantList (x) { - return fetch('ipc://localhost/lists/variant_list', { method: "POST", body: JSON.stringify([x]) }).then(r => r.json()) + return fetch('ipc://localhost/lists/variant_list', { method: "POST", body: JSON.stringify([x]) }) + .then(r => r.arrayBuffer()) + .then(bytes => { + const de = new Deserializer(Uint8Array.from(bytes)) + + return deserializeList(de, (de) => deserializeOtherVariant(de)) + }) } /** @@ -206,6 +486,12 @@ * @returns {Promise} */ export async function loadStoreEverything (a) { - return fetch('ipc://localhost/lists/load_store_everything', { method: "POST", body: JSON.stringify([a]) }).then(r => r.json()) + return fetch('ipc://localhost/lists/load_store_everything', { method: "POST", body: JSON.stringify([a]) }) + .then(r => r.arrayBuffer()) + .then(bytes => { + const de = new Deserializer(Uint8Array.from(bytes)) + + return deserializeLoadStoreAllSizes(de) + }) } diff --git a/crates/gen-guest-js/tests/many-arguments.js b/crates/gen-guest-js/tests/many-arguments.js index 92dec9b..1b55793 100644 --- a/crates/gen-guest-js/tests/many-arguments.js +++ b/crates/gen-guest-js/tests/many-arguments.js @@ -1,3 +1,23 @@ +export class Deserializer { + source + offset + + constructor(bytes) { + this.source = bytes + this.offset = 0 + } + + pop() { + return this.source[this.offset++] + } + + try_take_n(len) { + const out = this.source.slice(this.offset, this.offset + len) + this.offset += len + return out + } + } + /** * @param {bigint} a1 @@ -18,13 +38,13 @@ * @param {bigint} a16 */ export async function manyArgs (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16) { - return fetch('ipc://localhost/many_arguments/many_args', { method: "POST", body: JSON.stringify([a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16]) }).then(r => r.json()) + return fetch('ipc://localhost/many_arguments/many_args', { method: "POST", body: JSON.stringify([a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16]) }) } /** * @param {BigStruct} x */ export async function bigArgument (x) { - return fetch('ipc://localhost/many_arguments/big_argument', { method: "POST", body: JSON.stringify([x]) }).then(r => r.json()) + return fetch('ipc://localhost/many_arguments/big_argument', { method: "POST", body: JSON.stringify([x]) }) } diff --git a/crates/gen-guest-js/tests/multi-return.js b/crates/gen-guest-js/tests/multi-return.js index 11cca45..1498b7f 100644 --- a/crates/gen-guest-js/tests/multi-return.js +++ b/crates/gen-guest-js/tests/multi-return.js @@ -1,35 +1,119 @@ +export class Deserializer { + source + offset + + constructor(bytes) { + this.source = bytes + this.offset = 0 + } + + pop() { + return this.source[this.offset++] + } + + try_take_n(len) { + const out = this.source.slice(this.offset, this.offset + len) + this.offset += len + return out + } + } + function varint_max(type) { + const BITS_PER_BYTE = 8; + const BITS_PER_VARINT_BYTE = 7; + + const bits = type * BITS_PER_BYTE; + + const roundup_bits = bits + (BITS_PER_BYTE - 1); + + return Math.floor(roundup_bits / BITS_PER_VARINT_BYTE); + } + function max_of_last_byte(type) { + let extra_bits = type % 7; + return (1 << extra_bits) - 1; + } + function try_take_varint(de, type) { + let out = 0n; + + for (let i = 0; i < varint_max(type); i++) { + const val = de.pop(); + const carry = BigInt(val & 0x7F); + out |= carry << (7n * BigInt(i)); + + if ((val & 0x80) === 0) { + if (i === varint_max(type) - 1 && val > max_of_last_byte(type)) { + throw new Error('deserialize bad variant') + } else { + return out + } + } + } + + throw new Error('deserialize bad variant') + } + function deserializeU32(de) { + return try_take_varint(de, 32) + } + function deserializeF32(de) { + const bytes = de.try_take_n(4); + + const buf = new ArrayBuffer(4); + const view = new DataView(buf); + + bytes.reverse().forEach((v, i) => view.setUint8(i, v)); + + return view.getFloat32(0); + } + /** */ export async function mra () { - return fetch('ipc://localhost/multi_return/mra', { method: "POST", body: JSON.stringify([]) }).then(r => r.json()) + return fetch('ipc://localhost/multi_return/mra', { method: "POST", body: JSON.stringify([]) }) } /** * @returns {Promise<[]>} */ export async function mrb () { - return fetch('ipc://localhost/multi_return/mrb', { method: "POST", body: JSON.stringify([]) }).then(r => r.json()) + return fetch('ipc://localhost/multi_return/mrb', { method: "POST", body: JSON.stringify([]) }) } /** * @returns {Promise} */ export async function mrc () { - return fetch('ipc://localhost/multi_return/mrc', { method: "POST", body: JSON.stringify([]) }).then(r => r.json()) + return fetch('ipc://localhost/multi_return/mrc', { method: "POST", body: JSON.stringify([]) }) + .then(r => r.arrayBuffer()) + .then(bytes => { + const de = new Deserializer(Uint8Array.from(bytes)) + + return deserializeU32(de) + }) } /** * @returns {Promise<[number]>} */ export async function mrd () { - return fetch('ipc://localhost/multi_return/mrd', { method: "POST", body: JSON.stringify([]) }).then(r => r.json()) + return fetch('ipc://localhost/multi_return/mrd', { method: "POST", body: JSON.stringify([]) }) + .then(r => r.arrayBuffer()) + .then(bytes => { + const de = new Deserializer(Uint8Array.from(bytes)) + + return deserializeU32(de) + }) } /** * @returns {Promise<[number, number]>} */ export async function mre () { - return fetch('ipc://localhost/multi_return/mre', { method: "POST", body: JSON.stringify([]) }).then(r => r.json()) + return fetch('ipc://localhost/multi_return/mre', { method: "POST", body: JSON.stringify([]) }) + .then(r => r.arrayBuffer()) + .then(bytes => { + const de = new Deserializer(Uint8Array.from(bytes)) + + return [deserializeU32(de), deserializeF32(de)] + }) } diff --git a/crates/gen-guest-js/tests/records.js b/crates/gen-guest-js/tests/records.js index 2c27634..586e481 100644 --- a/crates/gen-guest-js/tests/records.js +++ b/crates/gen-guest-js/tests/records.js @@ -1,72 +1,220 @@ +export class Deserializer { + source + offset + + constructor(bytes) { + this.source = bytes + this.offset = 0 + } + + pop() { + return this.source[this.offset++] + } + + try_take_n(len) { + const out = this.source.slice(this.offset, this.offset + len) + this.offset += len + return out + } + } + function varint_max(type) { + const BITS_PER_BYTE = 8; + const BITS_PER_VARINT_BYTE = 7; + + const bits = type * BITS_PER_BYTE; + + const roundup_bits = bits + (BITS_PER_BYTE - 1); + + return Math.floor(roundup_bits / BITS_PER_VARINT_BYTE); + } + function max_of_last_byte(type) { + let extra_bits = type % 7; + return (1 << extra_bits) - 1; + } + function try_take_varint(de, type) { + let out = 0n; + + for (let i = 0; i < varint_max(type); i++) { + const val = de.pop(); + const carry = BigInt(val & 0x7F); + out |= carry << (7n * BigInt(i)); + + if ((val & 0x80) === 0) { + if (i === varint_max(type) - 1 && val > max_of_last_byte(type)) { + throw new Error('deserialize bad variant') + } else { + return out + } + } + } + + throw new Error('deserialize bad variant') + } + function deserializeBool(de) { + const val = de.pop(); + + return val != 0 + } + function deserializeU32(de) { + return try_take_varint(de, 32) + } + function deserializeU64(de) { + return try_take_varint(de, 64) + } + function deserializeI32(de) { + const n = try_take_varint(de, 32) + + return Number(((n >> 1n) as & 0xFFFFFFFFn) ^ (-((n & 0b1n) as & 0xFFFFFFFFn))) + } + function deserializeChar(de) { + const sz = deserializeU64(de); + if (sz > 4) { + throw new Error("Deserialize bad char"); + } + const bytes = de.try_take_n(Number(sz)); + + const decoder = new TextDecoder('utf-8'); + + return decoder.decode(bytes); + } + function deserializeString(de) { + const sz = deserializeU64(de); + + let bytes = de.try_take_n(Number(sz)); + + const decoder = new TextDecoder('utf-8'); + + return decoder.decode(bytes); + } + function deserializeEmpty(de) { + return { + + } + }function deserializeScalars(de) { + return { + a: deserializeU32(de), +b: deserializeU32(de) + } + }function deserializeReallyFlags(de) { + return { + a: deserializeBoolean(de), +b: deserializeBoolean(de), +c: deserializeBoolean(de), +d: deserializeBoolean(de), +e: deserializeBoolean(de), +f: deserializeBoolean(de), +g: deserializeBoolean(de), +h: deserializeBoolean(de), +i: deserializeBoolean(de) + } + }function deserializeAggregates(de) { + return { + a: deserializeScalars(de), +b: deserializeU32(de), +c: deserializeEmpty(de), +d: deserializeString(de), +e: deserializeReallyFlags(de) + } + } /** * @param {[string, number]} x */ export async function tupleArg (x) { - return fetch('ipc://localhost/records/tuple_arg', { method: "POST", body: JSON.stringify([x]) }).then(r => r.json()) + return fetch('ipc://localhost/records/tuple_arg', { method: "POST", body: JSON.stringify([x]) }) } /** * @returns {Promise<[string, number]>} */ export async function tupleResult () { - return fetch('ipc://localhost/records/tuple_result', { method: "POST", body: JSON.stringify([]) }).then(r => r.json()) + return fetch('ipc://localhost/records/tuple_result', { method: "POST", body: JSON.stringify([]) }) + .then(r => r.arrayBuffer()) + .then(bytes => { + const de = new Deserializer(Uint8Array.from(bytes)) + + return [deserializeChar(de), deserializeU32(de)] + }) } /** * @param {Empty} x */ export async function emptyArg (x) { - return fetch('ipc://localhost/records/empty_arg', { method: "POST", body: JSON.stringify([x]) }).then(r => r.json()) + return fetch('ipc://localhost/records/empty_arg', { method: "POST", body: JSON.stringify([x]) }) } /** * @returns {Promise} */ export async function emptyResult () { - return fetch('ipc://localhost/records/empty_result', { method: "POST", body: JSON.stringify([]) }).then(r => r.json()) + return fetch('ipc://localhost/records/empty_result', { method: "POST", body: JSON.stringify([]) }) + .then(r => r.arrayBuffer()) + .then(bytes => { + const de = new Deserializer(Uint8Array.from(bytes)) + + return deserializeEmpty(de) + }) } /** * @param {Scalars} x */ export async function scalarArg (x) { - return fetch('ipc://localhost/records/scalar_arg', { method: "POST", body: JSON.stringify([x]) }).then(r => r.json()) + return fetch('ipc://localhost/records/scalar_arg', { method: "POST", body: JSON.stringify([x]) }) } /** * @returns {Promise} */ export async function scalarResult () { - return fetch('ipc://localhost/records/scalar_result', { method: "POST", body: JSON.stringify([]) }).then(r => r.json()) + return fetch('ipc://localhost/records/scalar_result', { method: "POST", body: JSON.stringify([]) }) + .then(r => r.arrayBuffer()) + .then(bytes => { + const de = new Deserializer(Uint8Array.from(bytes)) + + return deserializeScalars(de) + }) } /** * @param {ReallyFlags} x */ export async function flagsArg (x) { - return fetch('ipc://localhost/records/flags_arg', { method: "POST", body: JSON.stringify([x]) }).then(r => r.json()) + return fetch('ipc://localhost/records/flags_arg', { method: "POST", body: JSON.stringify([x]) }) } /** * @returns {Promise} */ export async function flagsResult () { - return fetch('ipc://localhost/records/flags_result', { method: "POST", body: JSON.stringify([]) }).then(r => r.json()) + return fetch('ipc://localhost/records/flags_result', { method: "POST", body: JSON.stringify([]) }) + .then(r => r.arrayBuffer()) + .then(bytes => { + const de = new Deserializer(Uint8Array.from(bytes)) + + return deserializeReallyFlags(de) + }) } /** * @param {Aggregates} x */ export async function aggregateArg (x) { - return fetch('ipc://localhost/records/aggregate_arg', { method: "POST", body: JSON.stringify([x]) }).then(r => r.json()) + return fetch('ipc://localhost/records/aggregate_arg', { method: "POST", body: JSON.stringify([x]) }) } /** * @returns {Promise} */ export async function aggregateResult () { - return fetch('ipc://localhost/records/aggregate_result', { method: "POST", body: JSON.stringify([]) }).then(r => r.json()) + return fetch('ipc://localhost/records/aggregate_result', { method: "POST", body: JSON.stringify([]) }) + .then(r => r.arrayBuffer()) + .then(bytes => { + const de = new Deserializer(Uint8Array.from(bytes)) + + return deserializeAggregates(de) + }) } /** @@ -74,6 +222,12 @@ * @returns {Promise} */ export async function typedefInout (e) { - return fetch('ipc://localhost/records/typedef_inout', { method: "POST", body: JSON.stringify([e]) }).then(r => r.json()) + return fetch('ipc://localhost/records/typedef_inout', { method: "POST", body: JSON.stringify([e]) }) + .then(r => r.arrayBuffer()) + .then(bytes => { + const de = new Deserializer(Uint8Array.from(bytes)) + + return deserializeS32(de) + }) } diff --git a/crates/gen-guest-js/tests/resources.js b/crates/gen-guest-js/tests/resources.js index 75a88f6..98a1d90 100644 --- a/crates/gen-guest-js/tests/resources.js +++ b/crates/gen-guest-js/tests/resources.js @@ -1,22 +1,53 @@ +export class Deserializer { + source + offset + + constructor(bytes) { + this.source = bytes + this.offset = 0 + } + + pop() { + return this.source[this.offset++] + } + + try_take_n(len) { + const out = this.source.slice(this.offset, this.offset + len) + this.offset += len + return out + } + } + /** * @returns {Promise} */ export async function constructorA () { - return fetch('ipc://localhost/resources/constructor_a', { method: "POST", body: JSON.stringify([]) }).then(r => r.json()) + return fetch('ipc://localhost/resources/constructor_a', { method: "POST", body: JSON.stringify([]) }) + .then(r => r.arrayBuffer()) + .then(bytes => { + const de = new Deserializer(Uint8Array.from(bytes)) + + return A.deserialize(de) + }) } /** * @returns {Promise} */ export async function constructorB () { - return fetch('ipc://localhost/resources/constructor_b', { method: "POST", body: JSON.stringify([]) }).then(r => r.json()) + return fetch('ipc://localhost/resources/constructor_b', { method: "POST", body: JSON.stringify([]) }) + .then(r => r.arrayBuffer()) + .then(bytes => { + const de = new Deserializer(Uint8Array.from(bytes)) + + return B.deserialize(de) + }) } -class a { - #id: number; - +class A { + #id; /** */ @@ -36,10 +67,14 @@ class a { async f3 (a, b) { } + deserialize(de) { + const self = new A(); + self.#id = deserializeU64(de); + return self + } } -class b { - #id: number; - +class B { + #id; /** * @returns {Promise} @@ -61,4 +96,9 @@ class b { async f3 (x) { } + deserialize(de) { + const self = new B(); + self.#id = deserializeU64(de); + return self + } } \ No newline at end of file diff --git a/crates/gen-guest-js/tests/simple-functions.js b/crates/gen-guest-js/tests/simple-functions.js index ad19d0c..335ca58 100644 --- a/crates/gen-guest-js/tests/simple-functions.js +++ b/crates/gen-guest-js/tests/simple-functions.js @@ -1,15 +1,71 @@ +export class Deserializer { + source + offset + + constructor(bytes) { + this.source = bytes + this.offset = 0 + } + + pop() { + return this.source[this.offset++] + } + + try_take_n(len) { + const out = this.source.slice(this.offset, this.offset + len) + this.offset += len + return out + } + } + function varint_max(type) { + const BITS_PER_BYTE = 8; + const BITS_PER_VARINT_BYTE = 7; + + const bits = type * BITS_PER_BYTE; + + const roundup_bits = bits + (BITS_PER_BYTE - 1); + + return Math.floor(roundup_bits / BITS_PER_VARINT_BYTE); + } + function max_of_last_byte(type) { + let extra_bits = type % 7; + return (1 << extra_bits) - 1; + } + function try_take_varint(de, type) { + let out = 0n; + + for (let i = 0; i < varint_max(type); i++) { + const val = de.pop(); + const carry = BigInt(val & 0x7F); + out |= carry << (7n * BigInt(i)); + + if ((val & 0x80) === 0) { + if (i === varint_max(type) - 1 && val > max_of_last_byte(type)) { + throw new Error('deserialize bad variant') + } else { + return out + } + } + } + + throw new Error('deserialize bad variant') + } + function deserializeU32(de) { + return try_take_varint(de, 32) + } + /** */ export async function f1 () { - return fetch('ipc://localhost/simple_functions/f1', { method: "POST", body: JSON.stringify([]) }).then(r => r.json()) + return fetch('ipc://localhost/simple_functions/f1', { method: "POST", body: JSON.stringify([]) }) } /** * @param {number} a */ export async function f2 (a) { - return fetch('ipc://localhost/simple_functions/f2', { method: "POST", body: JSON.stringify([a]) }).then(r => r.json()) + return fetch('ipc://localhost/simple_functions/f2', { method: "POST", body: JSON.stringify([a]) }) } /** @@ -17,21 +73,33 @@ * @param {number} b */ export async function f3 (a, b) { - return fetch('ipc://localhost/simple_functions/f3', { method: "POST", body: JSON.stringify([a, b]) }).then(r => r.json()) + return fetch('ipc://localhost/simple_functions/f3', { method: "POST", body: JSON.stringify([a, b]) }) } /** * @returns {Promise} */ export async function f4 () { - return fetch('ipc://localhost/simple_functions/f4', { method: "POST", body: JSON.stringify([]) }).then(r => r.json()) + return fetch('ipc://localhost/simple_functions/f4', { method: "POST", body: JSON.stringify([]) }) + .then(r => r.arrayBuffer()) + .then(bytes => { + const de = new Deserializer(Uint8Array.from(bytes)) + + return deserializeU32(de) + }) } /** * @returns {Promise<[number, number]>} */ export async function f5 () { - return fetch('ipc://localhost/simple_functions/f5', { method: "POST", body: JSON.stringify([]) }).then(r => r.json()) + return fetch('ipc://localhost/simple_functions/f5', { method: "POST", body: JSON.stringify([]) }) + .then(r => r.arrayBuffer()) + .then(bytes => { + const de = new Deserializer(Uint8Array.from(bytes)) + + return [deserializeU32(de), deserializeU32(de)] + }) } /** @@ -41,6 +109,12 @@ * @returns {Promise<[number, number, number]>} */ export async function f6 (a, b, c) { - return fetch('ipc://localhost/simple_functions/f6', { method: "POST", body: JSON.stringify([a, b, c]) }).then(r => r.json()) + return fetch('ipc://localhost/simple_functions/f6', { method: "POST", body: JSON.stringify([a, b, c]) }) + .then(r => r.arrayBuffer()) + .then(bytes => { + const de = new Deserializer(Uint8Array.from(bytes)) + + return [deserializeU32(de), deserializeU32(de), deserializeU32(de)] + }) } diff --git a/crates/gen-guest-js/tests/simple-lists.js b/crates/gen-guest-js/tests/simple-lists.js index 64b7284..5aa8f75 100644 --- a/crates/gen-guest-js/tests/simple-lists.js +++ b/crates/gen-guest-js/tests/simple-lists.js @@ -1,16 +1,92 @@ +export class Deserializer { + source + offset + + constructor(bytes) { + this.source = bytes + this.offset = 0 + } + + pop() { + return this.source[this.offset++] + } + + try_take_n(len) { + const out = this.source.slice(this.offset, this.offset + len) + this.offset += len + return out + } + } + function varint_max(type) { + const BITS_PER_BYTE = 8; + const BITS_PER_VARINT_BYTE = 7; + + const bits = type * BITS_PER_BYTE; + + const roundup_bits = bits + (BITS_PER_BYTE - 1); + + return Math.floor(roundup_bits / BITS_PER_VARINT_BYTE); + } + function max_of_last_byte(type) { + let extra_bits = type % 7; + return (1 << extra_bits) - 1; + } + function try_take_varint(de, type) { + let out = 0n; + + for (let i = 0; i < varint_max(type); i++) { + const val = de.pop(); + const carry = BigInt(val & 0x7F); + out |= carry << (7n * BigInt(i)); + + if ((val & 0x80) === 0) { + if (i === varint_max(type) - 1 && val > max_of_last_byte(type)) { + throw new Error('deserialize bad variant') + } else { + return out + } + } + } + + throw new Error('deserialize bad variant') + } + function deserializeU32(de) { + return try_take_varint(de, 32) + } + function deserializeU64(de) { + return try_take_varint(de, 64) + } + function deserializeList(de, inner) { + const len = deserializeU64(de); + + let out: T[] = []; + + for (let i = 0; i < len; i++) { + out.push(inner(de)); + } + + return out; + } + /** * @param {Uint32Array[]} l */ export async function simpleList1 (l) { - return fetch('ipc://localhost/simple_lists/simple_list1', { method: "POST", body: JSON.stringify([l]) }).then(r => r.json()) + return fetch('ipc://localhost/simple_lists/simple_list1', { method: "POST", body: JSON.stringify([l]) }) } /** * @returns {Promise} */ export async function simpleList2 () { - return fetch('ipc://localhost/simple_lists/simple_list2', { method: "POST", body: JSON.stringify([]) }).then(r => r.json()) + return fetch('ipc://localhost/simple_lists/simple_list2', { method: "POST", body: JSON.stringify([]) }) + .then(r => r.arrayBuffer()) + .then(bytes => { + const de = new Deserializer(Uint8Array.from(bytes)) + + return deserializeList(de, (de) => deserializeU32(de)) + }) } /** @@ -19,7 +95,13 @@ * @returns {Promise<[Uint32Array[], Uint32Array[]]>} */ export async function simpleList3 (a, b) { - return fetch('ipc://localhost/simple_lists/simple_list3', { method: "POST", body: JSON.stringify([a, b]) }).then(r => r.json()) + return fetch('ipc://localhost/simple_lists/simple_list3', { method: "POST", body: JSON.stringify([a, b]) }) + .then(r => r.arrayBuffer()) + .then(bytes => { + const de = new Deserializer(Uint8Array.from(bytes)) + + return [deserializeList(de, (de) => deserializeU32(de)), deserializeList(de, (de) => deserializeU32(de))] + }) } /** @@ -27,6 +109,12 @@ * @returns {Promise} */ export async function simpleList4 (l) { - return fetch('ipc://localhost/simple_lists/simple_list4', { method: "POST", body: JSON.stringify([l]) }).then(r => r.json()) + return fetch('ipc://localhost/simple_lists/simple_list4', { method: "POST", body: JSON.stringify([l]) }) + .then(r => r.arrayBuffer()) + .then(bytes => { + const de = new Deserializer(Uint8Array.from(bytes)) + + return deserializeList(de, (de) => deserializeList(de, (de) => deserializeU32(de))) + }) } diff --git a/crates/gen-guest-js/tests/small-anonymous.js b/crates/gen-guest-js/tests/small-anonymous.js index abe4979..420ede5 100644 --- a/crates/gen-guest-js/tests/small-anonymous.js +++ b/crates/gen-guest-js/tests/small-anonymous.js @@ -1,8 +1,115 @@ +export class Deserializer { + source + offset + + constructor(bytes) { + this.source = bytes + this.offset = 0 + } + + pop() { + return this.source[this.offset++] + } + + try_take_n(len) { + const out = this.source.slice(this.offset, this.offset + len) + this.offset += len + return out + } + } + function varint_max(type) { + const BITS_PER_BYTE = 8; + const BITS_PER_VARINT_BYTE = 7; + + const bits = type * BITS_PER_BYTE; + + const roundup_bits = bits + (BITS_PER_BYTE - 1); + + return Math.floor(roundup_bits / BITS_PER_VARINT_BYTE); + } + function max_of_last_byte(type) { + let extra_bits = type % 7; + return (1 << extra_bits) - 1; + } + function try_take_varint(de, type) { + let out = 0n; + + for (let i = 0; i < varint_max(type); i++) { + const val = de.pop(); + const carry = BigInt(val & 0x7F); + out |= carry << (7n * BigInt(i)); + + if ((val & 0x80) === 0) { + if (i === varint_max(type) - 1 && val > max_of_last_byte(type)) { + throw new Error('deserialize bad variant') + } else { + return out + } + } + } + + throw new Error('deserialize bad variant') + } + function deserializeU64(de) { + return try_take_varint(de, 64) + } + function deserializeString(de) { + const sz = deserializeU64(de); + + let bytes = de.try_take_n(Number(sz)); + + const decoder = new TextDecoder('utf-8'); + + return decoder.decode(bytes); + } + function deserializeOption(de, inner) { + const disc = de.pop() + + switch (disc) { + case 0: + return null + case 1: + return inner(de) + default: + throw new Error('Deserialize bad option') + } + } + function function deserializeResult(de, ok, err) { + const disc = de.pop() + + switch (disc) { + case 0: + return ok(de) + case 1: + return err(de) + default: + throw new Error('Deserialize bad result') + } + } + function deserializeError(de) { + const disc = deserializeU32(de) + + switch (disc) { + case 0: + return "Success" + case 1: + return "Failure" + + default: + throw new Error("unknown enum case") + } + } /** * @returns {Promise>} */ export async function optionTest () { - return fetch('ipc://localhost/small_anonymous/option_test', { method: "POST", body: JSON.stringify([]) }).then(r => r.json()) + return fetch('ipc://localhost/small_anonymous/option_test', { method: "POST", body: JSON.stringify([]) }) + .then(r => r.arrayBuffer()) + .then(bytes => { + const de = new Deserializer(Uint8Array.from(bytes)) + + return deserializeResult(de, deserializeOption(de, (de) => deserializeString(de)), deserializeError(de)) + }) } diff --git a/crates/gen-guest-js/tests/strings.js b/crates/gen-guest-js/tests/strings.js index 7e3ab02..a2acf6f 100644 --- a/crates/gen-guest-js/tests/strings.js +++ b/crates/gen-guest-js/tests/strings.js @@ -1,16 +1,87 @@ +export class Deserializer { + source + offset + + constructor(bytes) { + this.source = bytes + this.offset = 0 + } + + pop() { + return this.source[this.offset++] + } + + try_take_n(len) { + const out = this.source.slice(this.offset, this.offset + len) + this.offset += len + return out + } + } + function varint_max(type) { + const BITS_PER_BYTE = 8; + const BITS_PER_VARINT_BYTE = 7; + + const bits = type * BITS_PER_BYTE; + + const roundup_bits = bits + (BITS_PER_BYTE - 1); + + return Math.floor(roundup_bits / BITS_PER_VARINT_BYTE); + } + function max_of_last_byte(type) { + let extra_bits = type % 7; + return (1 << extra_bits) - 1; + } + function try_take_varint(de, type) { + let out = 0n; + + for (let i = 0; i < varint_max(type); i++) { + const val = de.pop(); + const carry = BigInt(val & 0x7F); + out |= carry << (7n * BigInt(i)); + + if ((val & 0x80) === 0) { + if (i === varint_max(type) - 1 && val > max_of_last_byte(type)) { + throw new Error('deserialize bad variant') + } else { + return out + } + } + } + + throw new Error('deserialize bad variant') + } + function deserializeU64(de) { + return try_take_varint(de, 64) + } + function deserializeString(de) { + const sz = deserializeU64(de); + + let bytes = de.try_take_n(Number(sz)); + + const decoder = new TextDecoder('utf-8'); + + return decoder.decode(bytes); + } + /** * @param {string} x */ export async function a (x) { - return fetch('ipc://localhost/strings/a', { method: "POST", body: JSON.stringify([x]) }).then(r => r.json()) + return fetch('ipc://localhost/strings/a', { method: "POST", body: JSON.stringify([x]) }) } /** * @returns {Promise} */ export async function b () { - return fetch('ipc://localhost/strings/b', { method: "POST", body: JSON.stringify([]) }).then(r => r.json()) + return fetch('ipc://localhost/strings/b', { method: "POST", body: JSON.stringify([]) }) + .then(r => r.arrayBuffer()) + .then(bytes => { + const de = new Deserializer(Uint8Array.from(bytes)) + + return deserializeString(de) + }) } /** @@ -19,6 +90,12 @@ * @returns {Promise} */ export async function c (a, b) { - return fetch('ipc://localhost/strings/c', { method: "POST", body: JSON.stringify([a, b]) }).then(r => r.json()) + return fetch('ipc://localhost/strings/c', { method: "POST", body: JSON.stringify([a, b]) }) + .then(r => r.arrayBuffer()) + .then(bytes => { + const de = new Deserializer(Uint8Array.from(bytes)) + + return deserializeString(de) + }) } diff --git a/crates/gen-guest-js/tests/unions.js b/crates/gen-guest-js/tests/unions.js index 8ab3637..23eba3c 100644 --- a/crates/gen-guest-js/tests/unions.js +++ b/crates/gen-guest-js/tests/unions.js @@ -1,10 +1,220 @@ +export class Deserializer { + source + offset + + constructor(bytes) { + this.source = bytes + this.offset = 0 + } + + pop() { + return this.source[this.offset++] + } + + try_take_n(len) { + const out = this.source.slice(this.offset, this.offset + len) + this.offset += len + return out + } + } + function varint_max(type) { + const BITS_PER_BYTE = 8; + const BITS_PER_VARINT_BYTE = 7; + + const bits = type * BITS_PER_BYTE; + + const roundup_bits = bits + (BITS_PER_BYTE - 1); + + return Math.floor(roundup_bits / BITS_PER_VARINT_BYTE); + } + function max_of_last_byte(type) { + let extra_bits = type % 7; + return (1 << extra_bits) - 1; + } + function try_take_varint(de, type) { + let out = 0n; + + for (let i = 0; i < varint_max(type); i++) { + const val = de.pop(); + const carry = BigInt(val & 0x7F); + out |= carry << (7n * BigInt(i)); + + if ((val & 0x80) === 0) { + if (i === varint_max(type) - 1 && val > max_of_last_byte(type)) { + throw new Error('deserialize bad variant') + } else { + return out + } + } + } + + throw new Error('deserialize bad variant') + } + function deserializeBool(de) { + const val = de.pop(); + + return val != 0 + } + function deserializeU8(de) { + return de.pop() + } + function deserializeU16(de) { + return try_take_varint(de, 16) + } + function deserializeU32(de) { + return try_take_varint(de, 32) + } + function deserializeU64(de) { + return try_take_varint(de, 64) + } + function deserializeI8(de) { + return de.pop() + } + function deserializeI16(de) { + const n = try_take_varint(de, 16) + + return Number(((n >> 1n) & 0xFFFFn) ^ (-((n & 0b1n) & 0xFFFFn))) + } + function deserializeI32(de) { + const n = try_take_varint(de, 32) + + return Number(((n >> 1n) as & 0xFFFFFFFFn) ^ (-((n & 0b1n) as & 0xFFFFFFFFn))) + } + function deserializeI64(de) { + const n = try_take_varint(de, u64) + + return Number(((n >> 1n) & 0xFFFFFFFFFFFFFFFFn) ^ (-((n & 0b1n) & 0xFFFFFFFFFFFFFFFFn))) + } + function deserializeF32(de) { + const bytes = de.try_take_n(4); + + const buf = new ArrayBuffer(4); + const view = new DataView(buf); + + bytes.reverse().forEach((v, i) => view.setUint8(i, v)); + + return view.getFloat32(0); + } + function deserializeF64(de) { + const bytes = de.try_take_n(8); + + const buf = new ArrayBuffer(8); + const view = new DataView(buf); + + bytes.reverse().forEach((v, i) => view.setUint8(i, v)); + + return view.getFloat64(0); + } + function deserializeChar(de) { + const sz = deserializeU64(de); + if (sz > 4) { + throw new Error("Deserialize bad char"); + } + const bytes = de.try_take_n(Number(sz)); + + const decoder = new TextDecoder('utf-8'); + + return decoder.decode(bytes); + } + function deserializeString(de) { + const sz = deserializeU64(de); + + let bytes = de.try_take_n(Number(sz)); + + const decoder = new TextDecoder('utf-8'); + + return decoder.decode(bytes); + } + function deserializeAllIntegers(de) { + const disc = deserializeU32(de) + + switch (disc) { + case 0: + return deserializeBoolean(de) + case 1: + return deserializeU8(de) + case 2: + return deserializeU16(de) + case 3: + return deserializeU32(de) + case 4: + return deserializeU64(de) + case 5: + return deserializeS8(de) + case 6: + return deserializeS16(de) + case 7: + return deserializeS32(de) + case 8: + return deserializeS64(de) + + default: + throw new Error("unknown union case") + } + }function deserializeAllFloats(de) { + const disc = deserializeU32(de) + + switch (disc) { + case 0: + return deserializeF32(de) + case 1: + return deserializeF64(de) + + default: + throw new Error("unknown union case") + } + }function deserializeAllText(de) { + const disc = deserializeU32(de) + + switch (disc) { + case 0: + return deserializeChar(de) + case 1: + return deserializeString(de) + + default: + throw new Error("unknown union case") + } + }function deserializeDuplicatedS32(de) { + const disc = deserializeU32(de) + + switch (disc) { + case 0: + return deserializeS32(de) + case 1: + return deserializeS32(de) + case 2: + return deserializeS32(de) + + default: + throw new Error("unknown union case") + } + }function deserializeDistinguishableNum(de) { + const disc = deserializeU32(de) + + switch (disc) { + case 0: + return deserializeF64(de) + case 1: + return deserializeS64(de) + + default: + throw new Error("unknown union case") + } + } /** * @param {AllIntegers} num * @returns {Promise} */ export async function addOneInteger (num) { - return fetch('ipc://localhost/unions/add_one_integer', { method: "POST", body: JSON.stringify([num]) }).then(r => r.json()) + return fetch('ipc://localhost/unions/add_one_integer', { method: "POST", body: JSON.stringify([num]) }) + .then(r => r.arrayBuffer()) + .then(bytes => { + const de = new Deserializer(Uint8Array.from(bytes)) + + return deserializeAllIntegers(de) + }) } /** @@ -12,7 +222,13 @@ * @returns {Promise} */ export async function addOneFloat (num) { - return fetch('ipc://localhost/unions/add_one_float', { method: "POST", body: JSON.stringify([num]) }).then(r => r.json()) + return fetch('ipc://localhost/unions/add_one_float', { method: "POST", body: JSON.stringify([num]) }) + .then(r => r.arrayBuffer()) + .then(bytes => { + const de = new Deserializer(Uint8Array.from(bytes)) + + return deserializeAllFloats(de) + }) } /** @@ -21,7 +237,13 @@ * @returns {Promise} */ export async function replaceFirstChar (text, letter) { - return fetch('ipc://localhost/unions/replace_first_char', { method: "POST", body: JSON.stringify([text, letter]) }).then(r => r.json()) + return fetch('ipc://localhost/unions/replace_first_char', { method: "POST", body: JSON.stringify([text, letter]) }) + .then(r => r.arrayBuffer()) + .then(bytes => { + const de = new Deserializer(Uint8Array.from(bytes)) + + return deserializeAllText(de) + }) } /** @@ -29,7 +251,13 @@ * @returns {Promise} */ export async function identifyInteger (num) { - return fetch('ipc://localhost/unions/identify_integer', { method: "POST", body: JSON.stringify([num]) }).then(r => r.json()) + return fetch('ipc://localhost/unions/identify_integer', { method: "POST", body: JSON.stringify([num]) }) + .then(r => r.arrayBuffer()) + .then(bytes => { + const de = new Deserializer(Uint8Array.from(bytes)) + + return deserializeU8(de) + }) } /** @@ -37,7 +265,13 @@ * @returns {Promise} */ export async function identifyFloat (num) { - return fetch('ipc://localhost/unions/identify_float', { method: "POST", body: JSON.stringify([num]) }).then(r => r.json()) + return fetch('ipc://localhost/unions/identify_float', { method: "POST", body: JSON.stringify([num]) }) + .then(r => r.arrayBuffer()) + .then(bytes => { + const de = new Deserializer(Uint8Array.from(bytes)) + + return deserializeU8(de) + }) } /** @@ -45,7 +279,13 @@ * @returns {Promise} */ export async function identifyText (text) { - return fetch('ipc://localhost/unions/identify_text', { method: "POST", body: JSON.stringify([text]) }).then(r => r.json()) + return fetch('ipc://localhost/unions/identify_text', { method: "POST", body: JSON.stringify([text]) }) + .then(r => r.arrayBuffer()) + .then(bytes => { + const de = new Deserializer(Uint8Array.from(bytes)) + + return deserializeU8(de) + }) } /** @@ -53,7 +293,13 @@ * @returns {Promise} */ export async function addOneDuplicated (num) { - return fetch('ipc://localhost/unions/add_one_duplicated', { method: "POST", body: JSON.stringify([num]) }).then(r => r.json()) + return fetch('ipc://localhost/unions/add_one_duplicated', { method: "POST", body: JSON.stringify([num]) }) + .then(r => r.arrayBuffer()) + .then(bytes => { + const de = new Deserializer(Uint8Array.from(bytes)) + + return deserializeDuplicatedS32(de) + }) } /** @@ -61,7 +307,13 @@ * @returns {Promise} */ export async function identifyDuplicated (num) { - return fetch('ipc://localhost/unions/identify_duplicated', { method: "POST", body: JSON.stringify([num]) }).then(r => r.json()) + return fetch('ipc://localhost/unions/identify_duplicated', { method: "POST", body: JSON.stringify([num]) }) + .then(r => r.arrayBuffer()) + .then(bytes => { + const de = new Deserializer(Uint8Array.from(bytes)) + + return deserializeU8(de) + }) } /** @@ -69,7 +321,13 @@ * @returns {Promise} */ export async function addOneDistinguishableNum (num) { - return fetch('ipc://localhost/unions/add_one_distinguishable_num', { method: "POST", body: JSON.stringify([num]) }).then(r => r.json()) + return fetch('ipc://localhost/unions/add_one_distinguishable_num', { method: "POST", body: JSON.stringify([num]) }) + .then(r => r.arrayBuffer()) + .then(bytes => { + const de = new Deserializer(Uint8Array.from(bytes)) + + return deserializeDistinguishableNum(de) + }) } /** @@ -77,6 +335,12 @@ * @returns {Promise} */ export async function identifyDistinguishableNum (num) { - return fetch('ipc://localhost/unions/identify_distinguishable_num', { method: "POST", body: JSON.stringify([num]) }).then(r => r.json()) + return fetch('ipc://localhost/unions/identify_distinguishable_num', { method: "POST", body: JSON.stringify([num]) }) + .then(r => r.arrayBuffer()) + .then(bytes => { + const de = new Deserializer(Uint8Array.from(bytes)) + + return deserializeU8(de) + }) } diff --git a/crates/gen-guest-js/tests/variants.js b/crates/gen-guest-js/tests/variants.js index e597c4f..22fabd2 100644 --- a/crates/gen-guest-js/tests/variants.js +++ b/crates/gen-guest-js/tests/variants.js @@ -1,58 +1,357 @@ +export class Deserializer { + source + offset + + constructor(bytes) { + this.source = bytes + this.offset = 0 + } + + pop() { + return this.source[this.offset++] + } + + try_take_n(len) { + const out = this.source.slice(this.offset, this.offset + len) + this.offset += len + return out + } + } + function varint_max(type) { + const BITS_PER_BYTE = 8; + const BITS_PER_VARINT_BYTE = 7; + + const bits = type * BITS_PER_BYTE; + + const roundup_bits = bits + (BITS_PER_BYTE - 1); + + return Math.floor(roundup_bits / BITS_PER_VARINT_BYTE); + } + function max_of_last_byte(type) { + let extra_bits = type % 7; + return (1 << extra_bits) - 1; + } + function try_take_varint(de, type) { + let out = 0n; + + for (let i = 0; i < varint_max(type); i++) { + const val = de.pop(); + const carry = BigInt(val & 0x7F); + out |= carry << (7n * BigInt(i)); + + if ((val & 0x80) === 0) { + if (i === varint_max(type) - 1 && val > max_of_last_byte(type)) { + throw new Error('deserialize bad variant') + } else { + return out + } + } + } + + throw new Error('deserialize bad variant') + } + function deserializeBool(de) { + const val = de.pop(); + + return val != 0 + } + function deserializeU8(de) { + return de.pop() + } + function deserializeU32(de) { + return try_take_varint(de, 32) + } + function deserializeU64(de) { + return try_take_varint(de, 64) + } + function deserializeI32(de) { + const n = try_take_varint(de, 32) + + return Number(((n >> 1n) as & 0xFFFFFFFFn) ^ (-((n & 0b1n) as & 0xFFFFFFFFn))) + } + function deserializeI64(de) { + const n = try_take_varint(de, u64) + + return Number(((n >> 1n) & 0xFFFFFFFFFFFFFFFFn) ^ (-((n & 0b1n) & 0xFFFFFFFFFFFFFFFFn))) + } + function deserializeF32(de) { + const bytes = de.try_take_n(4); + + const buf = new ArrayBuffer(4); + const view = new DataView(buf); + + bytes.reverse().forEach((v, i) => view.setUint8(i, v)); + + return view.getFloat32(0); + } + function deserializeF64(de) { + const bytes = de.try_take_n(8); + + const buf = new ArrayBuffer(8); + const view = new DataView(buf); + + bytes.reverse().forEach((v, i) => view.setUint8(i, v)); + + return view.getFloat64(0); + } + function deserializeString(de) { + const sz = deserializeU64(de); + + let bytes = de.try_take_n(Number(sz)); + + const decoder = new TextDecoder('utf-8'); + + return decoder.decode(bytes); + } + function deserializeBytes(de) { + const sz = deserializeU64(de); + + let bytes = de.try_take_n(Number(sz)); + + const decoder = new TextDecoder('utf-8'); + + return decoder.decode(bytes); + } + function deserializeOption(de, inner) { + const disc = de.pop() + + switch (disc) { + case 0: + return null + case 1: + return inner(de) + default: + throw new Error('Deserialize bad option') + } + } + function function deserializeResult(de, ok, err) { + const disc = de.pop() + + switch (disc) { + case 0: + return ok(de) + case 1: + return err(de) + default: + throw new Error('Deserialize bad result') + } + } + function deserializeE1(de) { + const disc = deserializeU32(de) + + switch (disc) { + case 0: + return "A" + + default: + throw new Error("unknown enum case") + } + }function deserializeU1(de) { + const disc = deserializeU32(de) + + switch (disc) { + case 0: + return deserializeU32(de) + case 1: + return deserializeF32(de) + + default: + throw new Error("unknown union case") + } + }function deserializeEmpty(de) { + return { + + } + }function deserializeV1(de) { + const disc = deserializeU32(de) + + switch (disc) { + case 0: + return { tag: 0, value: null } + case 1: + return { tag: 1, value: deserializeU1(de) } + case 2: + return { tag: 2, value: deserializeE1(de) } + case 3: + return { tag: 3, value: deserializeString(de) } + case 4: + return { tag: 4, value: deserializeEmpty(de) } + case 5: + return { tag: 5, value: null } + case 6: + return { tag: 6, value: deserializeU32(de) } + + default: + throw new Error("unknown variant case") + } + }function deserializeCasts1(de) { + const disc = deserializeU32(de) + + switch (disc) { + case 0: + return { tag: 0, value: deserializeS32(de) } + case 1: + return { tag: 1, value: deserializeF32(de) } + + default: + throw new Error("unknown variant case") + } + }function deserializeCasts2(de) { + const disc = deserializeU32(de) + + switch (disc) { + case 0: + return { tag: 0, value: deserializeF64(de) } + case 1: + return { tag: 1, value: deserializeF32(de) } + + default: + throw new Error("unknown variant case") + } + }function deserializeCasts3(de) { + const disc = deserializeU32(de) + + switch (disc) { + case 0: + return { tag: 0, value: deserializeF64(de) } + case 1: + return { tag: 1, value: deserializeU64(de) } + + default: + throw new Error("unknown variant case") + } + }function deserializeCasts4(de) { + const disc = deserializeU32(de) + + switch (disc) { + case 0: + return { tag: 0, value: deserializeU32(de) } + case 1: + return { tag: 1, value: deserializeS64(de) } + + default: + throw new Error("unknown variant case") + } + }function deserializeCasts5(de) { + const disc = deserializeU32(de) + + switch (disc) { + case 0: + return { tag: 0, value: deserializeF32(de) } + case 1: + return { tag: 1, value: deserializeS64(de) } + + default: + throw new Error("unknown variant case") + } + }function deserializeCasts6(de) { + const disc = deserializeU32(de) + + switch (disc) { + case 0: + return { tag: 0, value: [deserializeF32(de), deserializeU32(de)] } + case 1: + return { tag: 1, value: [deserializeU32(de), deserializeU32(de)] } + + default: + throw new Error("unknown variant case") + } + }function deserializeMyErrno(de) { + const disc = deserializeU32(de) + + switch (disc) { + case 0: + return "Bad1" + case 1: + return "Bad2" + + default: + throw new Error("unknown enum case") + } + }function deserializeIsClone(de) { + return { + v1: deserializeV1(de) + } + } /** * @param {E1} x */ export async function e1Arg (x) { - return fetch('ipc://localhost/variants/e1_arg', { method: "POST", body: JSON.stringify([x]) }).then(r => r.json()) + return fetch('ipc://localhost/variants/e1_arg', { method: "POST", body: JSON.stringify([x]) }) } /** * @returns {Promise} */ export async function e1Result () { - return fetch('ipc://localhost/variants/e1_result', { method: "POST", body: JSON.stringify([]) }).then(r => r.json()) + return fetch('ipc://localhost/variants/e1_result', { method: "POST", body: JSON.stringify([]) }) + .then(r => r.arrayBuffer()) + .then(bytes => { + const de = new Deserializer(Uint8Array.from(bytes)) + + return deserializeE1(de) + }) } /** * @param {U1} x */ export async function u1Arg (x) { - return fetch('ipc://localhost/variants/u1_arg', { method: "POST", body: JSON.stringify([x]) }).then(r => r.json()) + return fetch('ipc://localhost/variants/u1_arg', { method: "POST", body: JSON.stringify([x]) }) } /** * @returns {Promise} */ export async function u1Result () { - return fetch('ipc://localhost/variants/u1_result', { method: "POST", body: JSON.stringify([]) }).then(r => r.json()) + return fetch('ipc://localhost/variants/u1_result', { method: "POST", body: JSON.stringify([]) }) + .then(r => r.arrayBuffer()) + .then(bytes => { + const de = new Deserializer(Uint8Array.from(bytes)) + + return deserializeU1(de) + }) } /** * @param {V1} x */ export async function v1Arg (x) { - return fetch('ipc://localhost/variants/v1_arg', { method: "POST", body: JSON.stringify([x]) }).then(r => r.json()) + return fetch('ipc://localhost/variants/v1_arg', { method: "POST", body: JSON.stringify([x]) }) } /** * @returns {Promise} */ export async function v1Result () { - return fetch('ipc://localhost/variants/v1_result', { method: "POST", body: JSON.stringify([]) }).then(r => r.json()) + return fetch('ipc://localhost/variants/v1_result', { method: "POST", body: JSON.stringify([]) }) + .then(r => r.arrayBuffer()) + .then(bytes => { + const de = new Deserializer(Uint8Array.from(bytes)) + + return deserializeV1(de) + }) } /** * @param {boolean} x */ export async function boolArg (x) { - return fetch('ipc://localhost/variants/bool_arg', { method: "POST", body: JSON.stringify([x]) }).then(r => r.json()) + return fetch('ipc://localhost/variants/bool_arg', { method: "POST", body: JSON.stringify([x]) }) } /** * @returns {Promise} */ export async function boolResult () { - return fetch('ipc://localhost/variants/bool_result', { method: "POST", body: JSON.stringify([]) }).then(r => r.json()) + return fetch('ipc://localhost/variants/bool_result', { method: "POST", body: JSON.stringify([]) }) + .then(r => r.arrayBuffer()) + .then(bytes => { + const de = new Deserializer(Uint8Array.from(bytes)) + + return deserializeBoolean(de) + }) } /** @@ -65,14 +364,20 @@ * @param {boolean | null | null} g */ export async function optionArg (a, b, c, d, e, f, g) { - return fetch('ipc://localhost/variants/option_arg', { method: "POST", body: JSON.stringify([a, b, c, d, e, f, g]) }).then(r => r.json()) + return fetch('ipc://localhost/variants/option_arg', { method: "POST", body: JSON.stringify([a, b, c, d, e, f, g]) }) } /** * @returns {Promise<[boolean | null, [] | null, number | null, E1 | null, number | null, U1 | null, boolean | null | null]>} */ export async function optionResult () { - return fetch('ipc://localhost/variants/option_result', { method: "POST", body: JSON.stringify([]) }).then(r => r.json()) + return fetch('ipc://localhost/variants/option_result', { method: "POST", body: JSON.stringify([]) }) + .then(r => r.arrayBuffer()) + .then(bytes => { + const de = new Deserializer(Uint8Array.from(bytes)) + + return [deserializeOption(de, (de) => deserializeBoolean(de)), deserializeOption(de, (de) => []), deserializeOption(de, (de) => deserializeU32(de)), deserializeOption(de, (de) => deserializeE1(de)), deserializeOption(de, (de) => deserializeF32(de)), deserializeOption(de, (de) => deserializeU1(de)), deserializeOption(de, (de) => deserializeOption(de, (de) => deserializeBoolean(de)))] + }) } /** @@ -85,7 +390,13 @@ * @returns {Promise<[Casts1, Casts2, Casts3, Casts4, Casts5, Casts6]>} */ export async function casts (a, b, c, d, e, f) { - return fetch('ipc://localhost/variants/casts', { method: "POST", body: JSON.stringify([a, b, c, d, e, f]) }).then(r => r.json()) + return fetch('ipc://localhost/variants/casts', { method: "POST", body: JSON.stringify([a, b, c, d, e, f]) }) + .then(r => r.arrayBuffer()) + .then(bytes => { + const de = new Deserializer(Uint8Array.from(bytes)) + + return [deserializeCasts1(de), deserializeCasts2(de), deserializeCasts3(de), deserializeCasts4(de), deserializeCasts5(de), deserializeCasts6(de)] + }) } /** @@ -97,90 +408,156 @@ * @param {Result} f */ export async function resultArg (a, b, c, d, e, f) { - return fetch('ipc://localhost/variants/result_arg', { method: "POST", body: JSON.stringify([a, b, c, d, e, f]) }).then(r => r.json()) + return fetch('ipc://localhost/variants/result_arg', { method: "POST", body: JSON.stringify([a, b, c, d, e, f]) }) } /** * @returns {Promise<[Result<_, _>, Result<_, E1>, Result, Result<[], []>, Result, Result]>} */ export async function resultResult () { - return fetch('ipc://localhost/variants/result_result', { method: "POST", body: JSON.stringify([]) }).then(r => r.json()) + return fetch('ipc://localhost/variants/result_result', { method: "POST", body: JSON.stringify([]) }) + .then(r => r.arrayBuffer()) + .then(bytes => { + const de = new Deserializer(Uint8Array.from(bytes)) + + return [deserializeResult(de, () => {}, () => {}), deserializeResult(de, () => {}, deserializeE1(de)), deserializeResult(de, deserializeE1(de), () => {}), deserializeResult(de, [], []), deserializeResult(de, deserializeU32(de), deserializeV1(de)), deserializeResult(de, deserializeString(de), deserializeBytes(de))] + }) } /** * @returns {Promise>} */ export async function returnResultSugar () { - return fetch('ipc://localhost/variants/return_result_sugar', { method: "POST", body: JSON.stringify([]) }).then(r => r.json()) + return fetch('ipc://localhost/variants/return_result_sugar', { method: "POST", body: JSON.stringify([]) }) + .then(r => r.arrayBuffer()) + .then(bytes => { + const de = new Deserializer(Uint8Array.from(bytes)) + + return deserializeResult(de, deserializeS32(de), deserializeMyErrno(de)) + }) } /** * @returns {Promise>} */ export async function returnResultSugar2 () { - return fetch('ipc://localhost/variants/return_result_sugar2', { method: "POST", body: JSON.stringify([]) }).then(r => r.json()) + return fetch('ipc://localhost/variants/return_result_sugar2', { method: "POST", body: JSON.stringify([]) }) + .then(r => r.arrayBuffer()) + .then(bytes => { + const de = new Deserializer(Uint8Array.from(bytes)) + + return deserializeResult(de, () => {}, deserializeMyErrno(de)) + }) } /** * @returns {Promise>} */ export async function returnResultSugar3 () { - return fetch('ipc://localhost/variants/return_result_sugar3', { method: "POST", body: JSON.stringify([]) }).then(r => r.json()) + return fetch('ipc://localhost/variants/return_result_sugar3', { method: "POST", body: JSON.stringify([]) }) + .then(r => r.arrayBuffer()) + .then(bytes => { + const de = new Deserializer(Uint8Array.from(bytes)) + + return deserializeResult(de, deserializeMyErrno(de), deserializeMyErrno(de)) + }) } /** * @returns {Promise>} */ export async function returnResultSugar4 () { - return fetch('ipc://localhost/variants/return_result_sugar4', { method: "POST", body: JSON.stringify([]) }).then(r => r.json()) + return fetch('ipc://localhost/variants/return_result_sugar4', { method: "POST", body: JSON.stringify([]) }) + .then(r => r.arrayBuffer()) + .then(bytes => { + const de = new Deserializer(Uint8Array.from(bytes)) + + return deserializeResult(de, [deserializeS32(de), deserializeU32(de)], deserializeMyErrno(de)) + }) } /** * @returns {Promise} */ export async function returnOptionSugar () { - return fetch('ipc://localhost/variants/return_option_sugar', { method: "POST", body: JSON.stringify([]) }).then(r => r.json()) + return fetch('ipc://localhost/variants/return_option_sugar', { method: "POST", body: JSON.stringify([]) }) + .then(r => r.arrayBuffer()) + .then(bytes => { + const de = new Deserializer(Uint8Array.from(bytes)) + + return deserializeOption(de, (de) => deserializeS32(de)) + }) } /** * @returns {Promise} */ export async function returnOptionSugar2 () { - return fetch('ipc://localhost/variants/return_option_sugar2', { method: "POST", body: JSON.stringify([]) }).then(r => r.json()) + return fetch('ipc://localhost/variants/return_option_sugar2', { method: "POST", body: JSON.stringify([]) }) + .then(r => r.arrayBuffer()) + .then(bytes => { + const de = new Deserializer(Uint8Array.from(bytes)) + + return deserializeOption(de, (de) => deserializeMyErrno(de)) + }) } /** * @returns {Promise>} */ export async function resultSimple () { - return fetch('ipc://localhost/variants/result_simple', { method: "POST", body: JSON.stringify([]) }).then(r => r.json()) + return fetch('ipc://localhost/variants/result_simple', { method: "POST", body: JSON.stringify([]) }) + .then(r => r.arrayBuffer()) + .then(bytes => { + const de = new Deserializer(Uint8Array.from(bytes)) + + return deserializeResult(de, deserializeU32(de), deserializeS32(de)) + }) } /** * @param {IsClone} a */ export async function isCloneArg (a) { - return fetch('ipc://localhost/variants/is_clone_arg', { method: "POST", body: JSON.stringify([a]) }).then(r => r.json()) + return fetch('ipc://localhost/variants/is_clone_arg', { method: "POST", body: JSON.stringify([a]) }) } /** * @returns {Promise} */ export async function isCloneReturn () { - return fetch('ipc://localhost/variants/is_clone_return', { method: "POST", body: JSON.stringify([]) }).then(r => r.json()) + return fetch('ipc://localhost/variants/is_clone_return', { method: "POST", body: JSON.stringify([]) }) + .then(r => r.arrayBuffer()) + .then(bytes => { + const de = new Deserializer(Uint8Array.from(bytes)) + + return deserializeIsClone(de) + }) } /** * @returns {Promise<[number | null]>} */ export async function returnNamedOption () { - return fetch('ipc://localhost/variants/return_named_option', { method: "POST", body: JSON.stringify([]) }).then(r => r.json()) + return fetch('ipc://localhost/variants/return_named_option', { method: "POST", body: JSON.stringify([]) }) + .then(r => r.arrayBuffer()) + .then(bytes => { + const de = new Deserializer(Uint8Array.from(bytes)) + + return deserializeOption(de, (de) => deserializeU8(de)) + }) } /** * @returns {Promise<[Result]>} */ export async function returnNamedResult () { - return fetch('ipc://localhost/variants/return_named_result', { method: "POST", body: JSON.stringify([]) }).then(r => r.json()) + return fetch('ipc://localhost/variants/return_named_result', { method: "POST", body: JSON.stringify([]) }) + .then(r => r.arrayBuffer()) + .then(bytes => { + const de = new Deserializer(Uint8Array.from(bytes)) + + return deserializeResult(de, deserializeU8(de), deserializeMyErrno(de)) + }) } diff --git a/crates/gen-guest-rust/src/lib.rs b/crates/gen-guest-rust/src/lib.rs index 6f4c366..9d7c461 100644 --- a/crates/gen-guest-rust/src/lib.rs +++ b/crates/gen-guest-rust/src/lib.rs @@ -10,8 +10,10 @@ use quote::quote; use syn::parse_quote; use tauri_bindgen_core::Generate; use tauri_bindgen_core::GeneratorBuilder; +use tauri_bindgen_core::TypeInfo; +use tauri_bindgen_core::TypeInfos; use tauri_bindgen_gen_rust::FnSig; -use tauri_bindgen_gen_rust::{BorrowMode, RustGenerator, TypeInfo, TypeInfos}; +use tauri_bindgen_gen_rust::{BorrowMode, RustGenerator}; use wit_parser::{Function, Interface}; #[derive(Default, Debug, Clone)] @@ -170,7 +172,7 @@ impl RustGenerator for RustWasm { } impl tauri_bindgen_core::Generate for RustWasm { - fn to_tokens(&self) -> TokenStream { + fn to_tokens(&mut self) -> TokenStream { let docs = self.print_docs(&self.interface.docs); let ident = format_ident!("{}", self.interface.ident.to_snake_case()); @@ -200,7 +202,7 @@ impl tauri_bindgen_core::Generate for RustWasm { } } - fn to_file(&self) -> (PathBuf, String) { + fn to_file(&mut self) -> (PathBuf, String) { let mut filename = PathBuf::from(self.interface.ident.to_kebab_case()); filename.set_extension("rs"); diff --git a/crates/gen-guest-rust/tests/codegen.rs b/crates/gen-guest-rust/tests/codegen.rs index 141c91a..fb81215 100644 --- a/crates/gen-guest-rust/tests/codegen.rs +++ b/crates/gen-guest-rust/tests/codegen.rs @@ -11,7 +11,7 @@ fn gen_interface( ) -> (String, String) { let iface = wit_parser::parse_and_resolve_str(&input, |_| false).unwrap(); - let gen = opts.build(iface); + let mut gen = opts.build(iface); let (filename, contents) = gen.to_file(); (filename.to_str().unwrap().to_string(), contents) diff --git a/crates/gen-guest-ts/src/lib.rs b/crates/gen-guest-ts/src/lib.rs index e3b86ed..c5920b9 100644 --- a/crates/gen-guest-ts/src/lib.rs +++ b/crates/gen-guest-ts/src/lib.rs @@ -342,7 +342,7 @@ fn print_docs(docs: &str) -> String { } impl Generate for TypeScript { - fn to_file(&self) -> (std::path::PathBuf, String) { + fn to_file(&mut self) -> (std::path::PathBuf, String) { let result_ty = self .interface .functions diff --git a/crates/gen-guest-ts/tests/codegen.rs b/crates/gen-guest-ts/tests/codegen.rs index 0c53635..78c2f53 100644 --- a/crates/gen-guest-ts/tests/codegen.rs +++ b/crates/gen-guest-ts/tests/codegen.rs @@ -8,7 +8,7 @@ fn gen_interface( ) -> (String, String) { let iface = wit_parser::parse_and_resolve_str(&input, |_| false).unwrap(); - let gen = opts.build(iface); + let mut gen = opts.build(iface); let (filename, contents) = gen.to_file(); (filename.to_str().unwrap().to_string(), contents) diff --git a/crates/gen-host/src/lib.rs b/crates/gen-host/src/lib.rs index fa5fbd0..5b1b379 100644 --- a/crates/gen-host/src/lib.rs +++ b/crates/gen-host/src/lib.rs @@ -13,9 +13,9 @@ use heck::{ToSnakeCase, ToUpperCamelCase}; use proc_macro2::TokenStream; use quote::format_ident; use quote::quote; -use tauri_bindgen_core::{Generate, GeneratorBuilder}; +use tauri_bindgen_core::{Generate, GeneratorBuilder, TypeInfos, TypeInfo}; use tauri_bindgen_gen_rust::{ - print_generics, BorrowMode, FnSig, RustGenerator, TypeInfo, TypeInfos, + print_generics, BorrowMode, FnSig, RustGenerator, }; use wit_parser::{Function, Interface, Type, TypeDefKind, FunctionResult}; @@ -233,7 +233,7 @@ impl RustGenerator for Host { } impl Generate for Host { - fn to_tokens(&self) -> TokenStream { + fn to_tokens(&mut self) -> TokenStream { let docs = self.print_docs(&self.interface.docs); let ident = format_ident!("{}", self.interface.ident.to_snake_case()); @@ -286,7 +286,7 @@ impl Generate for Host { } } - fn to_file(&self) -> (PathBuf, String) { + fn to_file(&mut self) -> (PathBuf, String) { let mut filename = PathBuf::from(self.interface.ident.to_kebab_case()); filename.set_extension("rs"); diff --git a/crates/gen-host/tests/codegen.rs b/crates/gen-host/tests/codegen.rs index cf087ff..8b3da7d 100644 --- a/crates/gen-host/tests/codegen.rs +++ b/crates/gen-host/tests/codegen.rs @@ -11,7 +11,7 @@ fn gen_interface( ) -> (String, String) { let iface = wit_parser::parse_and_resolve_str(&input, |_| false).unwrap(); - let gen = opts.build(iface); + let mut gen = opts.build(iface); let (filename, contents) = gen.to_file(); (filename.to_str().unwrap().to_string(), contents) diff --git a/crates/gen-markdown/src/lib.rs b/crates/gen-markdown/src/lib.rs index 9d94b85..762d1a0 100644 --- a/crates/gen-markdown/src/lib.rs +++ b/crates/gen-markdown/src/lib.rs @@ -222,7 +222,7 @@ fn print_docs(docs: &str) -> String { } impl Generate for Markdown { - fn to_file(&self) -> (std::path::PathBuf, String) { + fn to_file(&mut self) -> (std::path::PathBuf, String) { let ident = &self.interface.ident; let docs = print_docs(&self.interface.docs); let typedefs = self diff --git a/crates/gen-rust/Cargo.toml b/crates/gen-rust/Cargo.toml index 26d19bb..5113c60 100644 --- a/crates/gen-rust/Cargo.toml +++ b/crates/gen-rust/Cargo.toml @@ -8,10 +8,10 @@ rust-version.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +tauri-bindgen-core.workspace = true wit-parser.workspace = true heck.workspace = true quote.workspace = true proc-macro2.workspace = true syn.workspace = true -bitflags.workspace = true log.workspace = true diff --git a/crates/gen-rust/src/lib.rs b/crates/gen-rust/src/lib.rs index d83f199..2e84c1f 100644 --- a/crates/gen-rust/src/lib.rs +++ b/crates/gen-rust/src/lib.rs @@ -5,6 +5,7 @@ use proc_macro2::{Ident, Literal, TokenStream}; use quote::{format_ident, quote}; use std::{collections::HashMap, ops::Index}; use syn::Lifetime; +use tauri_bindgen_core::{TypeInfos, TypeInfo, flags_repr}; use wit_parser::{ EnumCase, FlagsField, Function, FunctionResult, Int, Interface, RecordField, Type, @@ -618,17 +619,6 @@ pub fn print_generics(info: TypeInfo, mode: &BorrowMode) -> Option }) } -#[must_use] -pub fn flags_repr(fields: &[FlagsField]) -> Int { - match fields.len() { - n if n <= 8 => Int::U8, - n if n <= 16 => Int::U16, - n if n <= 32 => Int::U32, - n if n <= 64 => Int::U64, - _ => panic!("too many flags to fit in a repr"), - } -} - #[derive(Clone, PartialEq, Eq)] pub enum BorrowMode { Owned, @@ -651,136 +641,3 @@ pub struct TypeVariant { pub ident: Ident, pub borrow_mode: BorrowMode, } - -// #[must_use] -// pub fn uses_two_names(info: TypeInfo) -> bool { - -// // info.contains(TypeInfo::HAS_LIST) && info.contains(TypeInfo::PARAM | TypeInfo::RESULT) -// } - -bitflags::bitflags! { - #[derive(Debug, Clone, Copy)] - pub struct TypeInfo: u32 { - /// Whether or not this type is ever used (transitively) within the - /// parameter of a function. - const PARAM = 0b0000_0001; - /// Whether or not this type is ever used (transitively) within the - /// result of a function. - const RESULT = 0b0000_0010; - /// Whether or not this type (transitively) has a list. - const HAS_LIST = 0b0000_1000; - } -} - -#[derive(Debug, Default)] -pub struct TypeInfos { - infos: HashMap, -} - -impl TypeInfos { - #[must_use] - pub fn new() -> Self { - TypeInfos::default() - } - - pub fn collect_param_info(&mut self, typedefs: &TypeDefArena, params: &[(String, Type)]) { - for (_, ty) in params { - self.collect_type_info(typedefs, ty, TypeInfo::PARAM); - } - } - - pub fn collect_result_info(&mut self, typedefs: &TypeDefArena, result: &FunctionResult) { - match result { - FunctionResult::Anon(ty) => { - self.collect_type_info(typedefs, ty, TypeInfo::RESULT); - } - FunctionResult::Named(results) => { - for (_, ty) in results { - self.collect_type_info(typedefs, ty, TypeInfo::RESULT); - } - } - } - } - - fn collect_typedef_info( - &mut self, - typedefs: &TypeDefArena, - id: TypeDefId, - base_info: TypeInfo, - ) -> TypeInfo { - let mut info = base_info; - - match &typedefs[id].kind { - TypeDefKind::Alias(ty) => { - info |= self.collect_type_info(typedefs, ty, base_info); - } - TypeDefKind::Record(fields) => { - for field in fields { - info |= self.collect_type_info(typedefs, &field.ty, base_info); - } - } - TypeDefKind::Variant(cases) => { - for case in cases { - if let Some(ty) = &case.ty { - info |= self.collect_type_info(typedefs, ty, base_info); - } - } - } - TypeDefKind::Union(cases) => { - for case in cases { - info |= self.collect_type_info(typedefs, &case.ty, base_info); - } - } - _ => {} - } - - log::debug!("collected info for {:?}: {:?}", typedefs[id].ident, info,); - - self.infos - .entry(id) - .and_modify(|i| *i |= info) - .or_insert(info); - - info - } - - fn collect_type_info( - &mut self, - typedefs: &TypeDefArena, - ty: &Type, - base_info: TypeInfo, - ) -> TypeInfo { - match ty { - Type::String => base_info | TypeInfo::HAS_LIST, - Type::List(ty) => self.collect_type_info(typedefs, ty, base_info) | TypeInfo::HAS_LIST, - Type::Option(ty) => self.collect_type_info(typedefs, ty, base_info), - Type::Tuple(types) => { - let mut info = base_info; - for ty in types { - info |= self.collect_type_info(typedefs, ty, base_info); - } - info - } - Type::Result { ok, err } => { - let mut info = base_info; - if let Some(ty) = &ok { - info |= self.collect_type_info(typedefs, ty, base_info); - } - if let Some(ty) = &err { - info |= self.collect_type_info(typedefs, ty, base_info); - } - info - } - Type::Id(id) => base_info | self.collect_typedef_info(typedefs, *id, base_info), - _ => base_info, - } - } -} - -impl Index for TypeInfos { - type Output = TypeInfo; - - fn index(&self, id: TypeDefId) -> &Self::Output { - &self.infos[&id] - } -} diff --git a/crates/rust-macro-shared/src/lib.rs b/crates/rust-macro-shared/src/lib.rs index 2bd9631..317ec82 100644 --- a/crates/rust-macro-shared/src/lib.rs +++ b/crates/rust-macro-shared/src/lib.rs @@ -22,7 +22,7 @@ where let iface = wit_parser::parse_and_resolve_file(&input.file, |t| input.skip.contains(t)).unwrap(); - let gen = input.builder.build(iface); + let mut gen = input.builder.build(iface); let mut tokens = gen.to_tokens(); let filepath = input.file.to_string_lossy(); diff --git a/src/main.rs b/src/main.rs index 8c33563..5d099b8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -184,7 +184,7 @@ where let iface = wit_parser::parse_and_resolve_file(&opts.wit, |t| skipset.contains(t))?; - let gen = builder.build(iface); + let mut gen = builder.build(iface); Ok(gen.to_file()) }