Skip to content

Commit 2d4a3df

Browse files
committed
function exporting and callbacks
1 parent d509f11 commit 2d4a3df

File tree

5 files changed

+186
-16
lines changed

5 files changed

+186
-16
lines changed

example/main.zig

+20-1
Original file line numberDiff line numberDiff line change
@@ -135,11 +135,30 @@ export fn main() void {
135135

136136
logStr("\n============================= Handle vs ConstHandle =============================");
137137
{
138-
logStr("zjb.global and zjb.constString add their ConstHandle on first use, and rember for subsiquent uses. They can't be released.");
138+
logStr("zjb.global and zjb.constString add their ConstHandle on first use, and remember for subsiquent uses. They can't be released.");
139139
logStr("While zjb.string and Handle return values must be released after being used or they'll leak.");
140140
logStr("See that some string remain in handles, while others have been removed after use.");
141141
const handles = zjb.global("zjb").get("_handles", zjb.Handle);
142142
defer handles.release();
143143
log(handles);
144144
}
145+
146+
logStr("\n============================= Exporting functions (press a key for a callback) =============================");
147+
zjb.global("document").call("addEventListener", .{ zjb.constString("keydown"), zjb.fnHandle("keydownCallback", keydownCallback) }, void);
148+
}
149+
150+
fn keydownCallback(event: zjb.Handle) callconv(.C) void {
151+
defer event.release();
152+
153+
zjb.global("console").call("log", .{ zjb.constString("From keydown callback, event:"), event }, void);
154+
}
155+
156+
var value: i32 = 0;
157+
fn incrementAndGet(increment: i32) callconv(.C) i32 {
158+
value += increment;
159+
return value;
160+
}
161+
162+
comptime {
163+
zjb.exportFn("incrementAndGet", incrementAndGet);
145164
}

example/static/script.js

+3
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,8 @@ var zjb = new Zjb();
99
WebAssembly.instantiateStreaming(fetch("example.wasm"), {env: env, zjb: zjb.imports}).then(function (results) {
1010
zjb.instance = results.instance;
1111
results.instance.exports.main();
12+
console.log("calling zjb exports from javascript", zjb.exports.incrementAndGet(1));
13+
console.log("calling zjb exports from javascript", zjb.exports.incrementAndGet(1));
14+
console.log("calling zjb exports from javascript", zjb.exports.incrementAndGet(1));
1215
});
1316
})();

example/static/style.css

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@ html, body {
22
margin: 0px;
33
width: 100%;
44
height: 100%;
5-
//background-color: #111;
5+
background-color: #111;
66
}

src/generate_js.zig

+134-13
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@ pub fn main() !void {
1111
return ExtractError.BadArguments;
1212
}
1313

14-
var funcs = std.ArrayList([]const u8).init(alloc);
15-
defer funcs.deinit();
14+
var imports = std.ArrayList([]const u8).init(alloc);
15+
defer imports.deinit();
16+
var exports = std.ArrayList([]const u8).init(alloc);
17+
defer exports.deinit();
1618

1719
{
1820
var file = try std.fs.openFileAbsolute(args[3], .{});
@@ -54,7 +56,21 @@ pub fn main() !void {
5456
if (desc_type != 0) { // Not a function?
5557
return ExtractError.ImportTypeNotSupported;
5658
}
57-
try funcs.append(try alloc.dupe(u8, name));
59+
try imports.append(try alloc.dupe(u8, name));
60+
}
61+
}
62+
} else if (section_id == 7) {
63+
const export_count = try r.getU32();
64+
for (0..export_count) |_| {
65+
const name_length = try r.getU32();
66+
const name = try r.bytes(name_length);
67+
68+
const desc_type = try r.byte();
69+
const desc_index = try r.getU32();
70+
_ = desc_index;
71+
72+
if (desc_type == 0 and std.mem.startsWith(u8, name, "zjb_fn")) {
73+
try exports.append(try alloc.dupe(u8, name));
5874
}
5975
}
6076
} else {
@@ -63,7 +79,8 @@ pub fn main() !void {
6379
}
6480
}
6581

66-
std.sort.insertion([]const u8, funcs.items, {}, strBefore);
82+
std.sort.insertion([]const u8, imports.items, {}, strBefore);
83+
std.sort.insertion([]const u8, exports.items, {}, strBefore);
6784

6885
var out_file = try std.fs.createFileAbsolute(args[1], .{});
6986
defer out_file.close();
@@ -84,11 +101,6 @@ pub fn main() !void {
84101
\\ }
85102
\\ constructor() {
86103
\\ this._decoder = new TextDecoder();
87-
\\ this._handles = new Map();
88-
\\ this._handles.set(0, null);
89-
\\ this._handles.set(1, window);
90-
\\ this._handles.set(2, "");
91-
\\ this._next_handle = 3;
92104
\\ this.imports = {
93105
\\
94106
);
@@ -97,7 +109,7 @@ pub fn main() !void {
97109
var func_args = std.ArrayList(ArgType).init(alloc);
98110
defer func_args.deinit();
99111

100-
implement_functions: for (funcs.items) |func| {
112+
implement_functions: for (imports.items) |func| {
101113
if (std.mem.eql(u8, lastFunc, func)) {
102114
continue;
103115
}
@@ -256,10 +268,119 @@ pub fn main() !void {
256268

257269
try writer.writeAll(";\n },\n");
258270
}
271+
try writer.writeAll(" };\n"); // end imports
272+
273+
try writer.writeAll(" this.exports = {\n");
274+
275+
var export_names = std.ArrayList([]const u8).init(alloc);
276+
defer export_names.deinit();
277+
278+
for (exports.items) |func| {
279+
func_args.clearRetainingCapacity();
280+
281+
var np = NameParser{ .slice = func };
282+
try np.must("zjb_fn_");
283+
284+
while (!(np.maybe("_") or np.slice.len == 0)) {
285+
try func_args.append(try np.mustType());
286+
}
287+
288+
const ret_type = try np.mustType();
289+
try np.must("_");
290+
291+
const name = np.slice;
292+
try export_names.append(name);
293+
294+
//////////////////////////////////
295+
296+
try writer.writeAll(" \"");
297+
try writer.writeAll(name);
298+
try writer.writeAll("\": (");
299+
300+
for (0..func_args.items.len) |i| {
301+
if (i > 0) {
302+
try writer.writeAll(", ");
303+
}
304+
try writer.print("arg{d}", .{i});
305+
}
306+
307+
try writer.writeAll(") => {\n ");
308+
switch (ret_type) {
309+
.void => {},
310+
.bool => {
311+
try writer.writeAll("return Boolean(");
312+
},
313+
.object => {
314+
try writer.writeAll("return this._handles.get(");
315+
},
316+
.number => {
317+
try writer.writeAll("return ");
318+
},
319+
}
320+
321+
try writer.writeAll("this.instance.exports.");
322+
try writer.writeAll(func);
323+
try writer.writeAll("(");
324+
325+
for (func_args.items, 0..) |arg, i| {
326+
if (i > 0) {
327+
try writer.writeAll(", ");
328+
}
329+
switch (arg) {
330+
.void => {
331+
return ExtractError.InvalidExportedName;
332+
},
333+
.bool => {
334+
try writer.print("Boolean(arg{d})", .{i});
335+
},
336+
.object => {
337+
try writer.print("this.new_handle(arg{d})", .{i});
338+
},
339+
.number => {
340+
try writer.print("arg{d}", .{i});
341+
},
342+
}
343+
}
344+
345+
try writer.writeAll(")");
346+
347+
switch (ret_type) {
348+
.bool, .object => {
349+
try writer.writeAll(")");
350+
},
351+
.void, .number => {},
352+
}
353+
try writer.writeAll(";\n },\n");
354+
}
355+
try writer.writeAll(" };\n"); // end exports
356+
357+
try writer.writeAll(
358+
\\ this._export_reverse_handles = {};
359+
\\ this._handles = new Map();
360+
\\ this._handles.set(0, null);
361+
\\ this._handles.set(1, window);
362+
\\ this._handles.set(2, "");
363+
\\ this._handles.set(3, this.exports);
364+
\\ this._next_handle = 4;
365+
\\
366+
);
367+
368+
try writer.writeAll(
369+
\\ }
370+
\\};
371+
\\
372+
);
373+
374+
std.sort.insertion([]const u8, export_names.items, {}, strBefore);
375+
if (export_names.items.len > 1) {
376+
for (0..export_names.items.len - 1) |i| {
377+
if (std.mem.eql(u8, export_names.items[i], export_names.items[i + 1])) {
378+
std.debug.print("ERROR: function export name used twice: {s}.\n", .{export_names.items[i]});
379+
std.posix.exit(1);
380+
}
381+
}
382+
}
259383

260-
try writer.writeAll(" };\n");
261-
try writer.writeAll(" }\n");
262-
try writer.writeAll("};\n");
263384
try out_file.sync();
264385
}
265386

src/zjb.zig

+28-1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,32 @@ pub fn global(comptime b: []const u8) ConstHandle {
3333
}.get();
3434
}
3535

36+
pub fn fnHandle(comptime name: []const u8, comptime f: anytype) ConstHandle {
37+
comptime exportFn(name, f);
38+
39+
return struct {
40+
var handle: ?ConstHandle = null;
41+
fn get() ConstHandle {
42+
if (handle) |h| {
43+
return h;
44+
}
45+
handle = @enumFromInt(@intFromEnum(ConstHandle.exports.get(name, Handle)));
46+
return handle.?;
47+
}
48+
}.get();
49+
}
50+
51+
pub fn exportFn(comptime name: []const u8, comptime f: anytype) void {
52+
comptime var export_name: []const u8 = "zjb_fn_";
53+
const type_info = @typeInfo(@TypeOf(f)).Fn;
54+
inline for (type_info.params) |param| {
55+
export_name = export_name ++ comptime shortTypeName(param.type orelse @compileError("zjb exported functions need specified types."));
56+
}
57+
export_name = export_name ++ "_" ++ comptime shortTypeName(type_info.return_type orelse null) ++ "_" ++ name;
58+
59+
@export(f, .{ .name = export_name });
60+
}
61+
3662
pub fn i8ArrayView(data: []const i8) Handle {
3763
return zjb.i8ArrayView(data.ptr, data.len);
3864
}
@@ -88,6 +114,7 @@ pub const ConstHandle = enum(i32) {
88114
null = 0,
89115
global = 1,
90116
empty_string = 2,
117+
exports = 3,
91118
_,
92119

93120
pub fn isNull(handle: ConstHandle) bool {
@@ -216,7 +243,7 @@ fn shortTypeName(comptime T: type) []const u8 {
216243
// in javascript so there's no reason not to do it.
217244
i32, i64, f32, f64, comptime_int, comptime_float => "n",
218245
else => {
219-
@compileError("unexpected type" ++ @typeName(T));
246+
@compileError("unexpected type " ++ @typeName(T) ++ ". Supported types: zjb.Handle, bool, i32, i64, f32, f64, comptime_int, copmtime_float, void (as return type).");
220247
},
221248
};
222249
}

0 commit comments

Comments
 (0)