Skip to content

Commit 5d3f6a3

Browse files
committed
refactor: move Bindings struct to lib; begin refactoring codegen
This starts the refactoring of codegen parts into the library, where it can be tested and more easily changed. It moves the `Bindings` struct into the library, and adds a new `Wasm` struct responsible for generating code to embed or inline the WebAssembly file. As part of this, I needed `GoIdentifier` to be an owned type, so I changed it to hold strings instead of references. This doesn't really affect performance because we were always passing references to the output of `format!()` which would allocate a new string anyway.
1 parent 810bd40 commit 5d3f6a3

6 files changed

Lines changed: 276 additions & 172 deletions

File tree

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
use genco::{prelude::*, tokens::Tokens};
2+
use wit_bindgen_core::wit_parser::{Record, Resolve, Type, TypeDef, TypeDefKind};
3+
4+
use crate::{
5+
codegen::wasm::{Wasm, WasmData},
6+
go::*,
7+
resolve_type,
8+
};
9+
10+
/// The WIT bindings for a world.
11+
pub struct Bindings {
12+
/// The cumulative output tokens containing the Go bindings.
13+
// TODO(#16): Don't use the internal bindings.out field
14+
pub out: Tokens<Go>,
15+
16+
/// The identifier of the Go variable containing the WebAssembly bytes.
17+
raw_wasm_var: GoIdentifier,
18+
}
19+
20+
impl Bindings {
21+
/// Creates a new bindings generator for the selected world.
22+
pub fn new(world: &str) -> Self {
23+
let wasm_var = GoIdentifier::private(format!("wasm-file-{world}"));
24+
Self {
25+
// world,
26+
out: Tokens::new(),
27+
raw_wasm_var: wasm_var,
28+
}
29+
}
30+
31+
/// Adds the given Wasm to the bindings.
32+
pub fn include_wasm(&mut self, wasm: WasmData) {
33+
Wasm::new(&self.raw_wasm_var, wasm).format_into(&mut self.out)
34+
}
35+
36+
pub fn define_type(&mut self, typ_def: &TypeDef, resolve: &Resolve) {
37+
let TypeDef { name, kind, .. } = typ_def;
38+
match kind {
39+
TypeDefKind::Record(Record { fields }) => {
40+
let name = GoIdentifier::public(name.as_deref().expect("record to have a name"));
41+
let fields = fields.iter().map(|field| {
42+
(
43+
GoIdentifier::public(&field.name),
44+
resolve_type(&field.ty, resolve),
45+
)
46+
});
47+
48+
quote_in! { self.out =>
49+
$['\n']
50+
type $name struct {
51+
$(for (name, typ) in fields join ($['\r']) => $name $typ)
52+
}
53+
}
54+
}
55+
TypeDefKind::Resource => todo!("TODO(#5): implement resources"),
56+
TypeDefKind::Handle(_) => todo!("TODO(#5): implement resources"),
57+
TypeDefKind::Flags(_) => todo!("TODO(#4):generate flags type definition"),
58+
TypeDefKind::Tuple(_) => todo!("TODO(#4):generate tuple type definition"),
59+
TypeDefKind::Variant(_) => {
60+
// TODO(#4): Generate aliases if the variant name doesn't match the struct name
61+
}
62+
TypeDefKind::Enum(inner) => {
63+
let name = name.clone().expect("enum to have a name");
64+
let enum_type = &GoIdentifier::private(&name);
65+
66+
let enum_interface = GoIdentifier::public(&name);
67+
68+
let enum_function = &GoIdentifier::private(format!("is-{}", &name));
69+
70+
let variants = inner
71+
.cases
72+
.iter()
73+
.map(|variant| GoIdentifier::public(&variant.name));
74+
75+
quote_in! { self.out =>
76+
$['\n']
77+
type $enum_interface interface {
78+
$enum_function()
79+
}
80+
81+
type $enum_type int
82+
83+
func ($enum_type) $enum_function() {}
84+
85+
const (
86+
$(for name in variants join ($['\r']) => $name $enum_type = iota)
87+
)
88+
}
89+
}
90+
TypeDefKind::Option(_) => todo!("TODO(#4): generate option type definition"),
91+
TypeDefKind::Result(_) => todo!("TODO(#4): generate result type definition"),
92+
TypeDefKind::List(_) => todo!("TODO(#4): generate list type definition"),
93+
TypeDefKind::Future(_) => todo!("TODO(#4): generate future type definition"),
94+
TypeDefKind::Stream(_) => todo!("TODO(#4): generate stream type definition"),
95+
TypeDefKind::Type(Type::Id(_)) => {
96+
// TODO(#4): Only skip this if we have already generated the type
97+
}
98+
TypeDefKind::Type(Type::Bool) => todo!("TODO(#4): generate bool type alias"),
99+
TypeDefKind::Type(Type::U8) => todo!("TODO(#4): generate u8 type alias"),
100+
TypeDefKind::Type(Type::U16) => todo!("TODO(#4): generate u16 type alias"),
101+
TypeDefKind::Type(Type::U32) => todo!("TODO(#4): generate u32 type alias"),
102+
TypeDefKind::Type(Type::U64) => todo!("TODO(#4): generate u64 type alias"),
103+
TypeDefKind::Type(Type::S8) => todo!("TODO(#4): generate s8 type alias"),
104+
TypeDefKind::Type(Type::S16) => todo!("TODO(#4): generate s16 type alias"),
105+
TypeDefKind::Type(Type::S32) => todo!("TODO(#4): generate s32 type alias"),
106+
TypeDefKind::Type(Type::S64) => todo!("TODO(#4): generate s64 type alias"),
107+
TypeDefKind::Type(Type::F32) => todo!("TODO(#4): generate f32 type alias"),
108+
TypeDefKind::Type(Type::F64) => todo!("TODO(#4): generate f64 type alias"),
109+
TypeDefKind::Type(Type::Char) => todo!("TODO(#4): generate char type alias"),
110+
TypeDefKind::Type(Type::String) => {
111+
let name =
112+
GoIdentifier::public(name.as_deref().expect("string alias to have a name"));
113+
// TODO(#4): We might want a Type Definition (newtype) instead of Type Alias here
114+
quote_in! { self.out =>
115+
$['\n']
116+
type $name = string
117+
}
118+
}
119+
TypeDefKind::Type(Type::ErrorContext) => {
120+
todo!("TODO(#4): generate error context definition")
121+
}
122+
TypeDefKind::FixedSizeList(_, _) => {
123+
todo!("TODO(#4): generate fixed size list definition")
124+
}
125+
TypeDefKind::Unknown => panic!("cannot generate Unknown type"),
126+
}
127+
}
128+
}

cmd/gravity/src/codegen/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
mod bindings;
2+
mod wasm;
3+
4+
pub use bindings::*;
5+
pub use wasm::WasmData;

cmd/gravity/src/codegen/wasm.rs

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
use genco::prelude::*;
2+
3+
use crate::go::{GoIdentifier, embed};
4+
5+
/// The WebAssembly data for a world, either inline or embedded using go:embed.
6+
pub enum WasmData<'a> {
7+
/// The WebAssembly file is inlined as a byte array.
8+
Inline(&'a [u8]),
9+
/// The WebAssembly file is embedded using go:embed.
10+
Embedded(&'a str),
11+
}
12+
13+
pub(crate) struct Wasm<'a> {
14+
var: &'a GoIdentifier,
15+
data: WasmData<'a>,
16+
}
17+
18+
impl<'a> Wasm<'a> {
19+
pub(crate) fn new(var: &'a GoIdentifier, data: WasmData<'a>) -> Self {
20+
Self { var, data }
21+
}
22+
}
23+
24+
impl FormatInto<Go> for Wasm<'_> {
25+
fn format_into(self, tokens: &mut Tokens<Go>) {
26+
match self.data {
27+
WasmData::Inline(bytes) => {
28+
let hex_rows = bytes
29+
.chunks(16)
30+
.map(|bytes| {
31+
quote! {
32+
$(for b in bytes join ( ) => $(format!("0x{b:02x},")))
33+
}
34+
})
35+
.collect::<Vec<Tokens<Go>>>();
36+
37+
// TODO(#16): Don't use the internal bindings.out field
38+
quote_in! { *tokens =>
39+
var $(self.var) = []byte{
40+
$(for row in hex_rows join ($['\r']) => $row)
41+
}
42+
};
43+
}
44+
WasmData::Embedded(name) => {
45+
// TODO(#16): Don't use the internal bindings.out field
46+
quote_in! { *tokens =>
47+
import _ "embed"
48+
49+
$(embed(name))
50+
var $(self.var) []byte
51+
}
52+
}
53+
}
54+
}
55+
}
56+
57+
#[cfg(test)]
58+
mod tests {
59+
use genco::{prelude::*, tokens::Tokens};
60+
61+
use crate::{
62+
codegen::wasm::{Wasm, WasmData},
63+
go::GoIdentifier,
64+
};
65+
66+
#[test]
67+
fn test_inline_wasm() {
68+
let var = GoIdentifier::private("wasm");
69+
let wasm = WasmData::Inline(&[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]);
70+
let mut tokens = Tokens::<Go>::new();
71+
Wasm::new(&var, wasm).format_into(&mut tokens);
72+
assert_eq!(
73+
tokens.to_string().unwrap(),
74+
r#"var wasm = []byte{
75+
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10,
76+
}"#
77+
);
78+
}
79+
80+
#[test]
81+
fn test_embedded_wasm() {
82+
let var = GoIdentifier::private("wasm");
83+
let wasm = WasmData::Embedded("hello.wasm");
84+
let mut tokens = Tokens::<Go>::new();
85+
Wasm::new(&var, wasm).format_into(&mut tokens);
86+
assert_eq!(
87+
tokens.to_string().unwrap(),
88+
"import _ \"embed\"\n\n//go:embed hello.wasm\nvar wasm []byte"
89+
);
90+
}
91+
}

cmd/gravity/src/go/identifier.rs

Lines changed: 31 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,30 +8,39 @@ use genco::{prelude::*, tokens::ItemStr};
88
/// - Public identifiers start with uppercase (exported)
99
/// - Private identifiers start with lowercase (unexported)
1010
/// - Local identifiers are used as-is without transformation
11-
#[derive(Debug, Clone, Copy)]
12-
pub enum GoIdentifier<'a> {
11+
#[derive(Debug, Clone)]
12+
pub enum GoIdentifier {
1313
/// Public/exported identifier (will be converted to UpperCamelCase)
14-
Public { name: &'a str },
14+
Public { name: String },
1515
/// Private/unexported identifier (will be converted to lowerCamelCase)
16-
Private { name: &'a str },
16+
Private { name: String },
1717
/// Local identifier (will be converted to lowerCamelCase)
18-
Local { name: &'a str },
18+
Local { name: String },
1919
}
2020

21-
impl<'a> GoIdentifier<'a> {
21+
impl GoIdentifier {
2222
/// Creates a new public identifier.
23-
pub fn public(name: &'a str) -> Self {
24-
Self::Public { name }
23+
pub fn public<T>(name: T) -> Self
24+
where
25+
T: Into<String>,
26+
{
27+
Self::Public { name: name.into() }
2528
}
2629

2730
/// Creates a new private identifier.
28-
pub fn private(name: &'a str) -> Self {
29-
Self::Private { name }
31+
pub fn private<T>(name: T) -> Self
32+
where
33+
T: Into<String>,
34+
{
35+
Self::Private { name: name.into() }
3036
}
3137

3238
/// Creates a new local identifier.
33-
pub fn local(name: &'a str) -> Self {
34-
Self::Local { name }
39+
pub fn local<T>(name: T) -> Self
40+
where
41+
T: Into<String>,
42+
{
43+
Self::Local { name: name.into() }
3544
}
3645

3746
/// Returns an iterator over the characters of the underlying name.
@@ -40,7 +49,7 @@ impl<'a> GoIdentifier<'a> {
4049
///
4150
/// # Returns
4251
/// An iterator over the characters of the identifier's name.
43-
pub fn chars(&self) -> Chars<'a> {
52+
pub fn chars(&self) -> Chars<'_> {
4453
match self {
4554
GoIdentifier::Public { name } => name.chars(),
4655
GoIdentifier::Private { name } => name.chars(),
@@ -49,15 +58,21 @@ impl<'a> GoIdentifier<'a> {
4958
}
5059
}
5160

52-
impl From<GoIdentifier<'_>> for String {
61+
impl From<GoIdentifier> for String {
5362
fn from(value: GoIdentifier) -> Self {
63+
(&value).into()
64+
}
65+
}
66+
67+
impl From<&GoIdentifier> for String {
68+
fn from(value: &GoIdentifier) -> Self {
5469
let mut tokens: Tokens<Go> = Tokens::new();
5570
value.format_into(&mut tokens);
5671
tokens.to_string().expect("to format correctly")
5772
}
5873
}
5974

60-
impl FormatInto<Go> for &GoIdentifier<'_> {
75+
impl FormatInto<Go> for &GoIdentifier {
6176
fn format_into(self, tokens: &mut Tokens<Go>) {
6277
let mut chars = self.chars();
6378

@@ -83,7 +98,7 @@ impl FormatInto<Go> for &GoIdentifier<'_> {
8398
}
8499
}
85100
}
86-
impl FormatInto<Go> for GoIdentifier<'_> {
101+
impl FormatInto<Go> for GoIdentifier {
87102
fn format_into(self, tokens: &mut Tokens<Go>) {
88103
(&self).format_into(tokens)
89104
}

cmd/gravity/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
pub mod codegen;
12
pub mod go;
23

34
use crate::go::GoType;

0 commit comments

Comments
 (0)