├── .github
├── FUNDING.yml
└── pull_request_template.md
├── .gitattributes
├── README.md
├── .gitignore
├── example
├── main.zig
├── example.js
├── sysjs.zig
├── sysjs_generated.js
└── sysjs_generated.zig
├── www
└── index.html
├── LICENSE
├── LICENSE-MIT
├── src
├── analysis.zig
├── main.zig
├── IrGen.zig
└── types.zig
└── LICENSE-APACHE
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: slimsag
2 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto eol=lf
2 | upstream/** linguist-vendored
3 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | This project has moved to https://github.com/hexops/mach/tree/main/src/sysjs
2 |
--------------------------------------------------------------------------------
/.github/pull_request_template.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | - [ ] By selecting this checkbox, I agree to license my contributions to this project under the license(s) described in the LICENSE file, and I have the right to do so or have received permission to do so by an employer or client I am producing work for whom has this right.
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # This file is for zig-specific build artifacts.
2 | # If you have OS-specific or editor-specific files to ignore,
3 | # such as *.swp or .DS_Store, put those in your global
4 | # ~/.gitignore and put this in your ~/.gitconfig:
5 | #
6 | # [core]
7 | # excludesfile = ~/.gitignore
8 | #
9 | # Cheers!
10 | # -andrewrk
11 |
12 | zig-cache/
13 | zig-out/
14 | /release/
15 | /debug/
16 | /build/
17 | /build-*/
18 | /docgen_tmp/
19 |
--------------------------------------------------------------------------------
/example/main.zig:
--------------------------------------------------------------------------------
1 | const std = @import("std");
2 | const js = @import("sysjs_generated.zig");
3 |
4 | pub export fn main() u8 {
5 | const td = js.TextDecoder.new();
6 | const string = td.decode("Hello, world!\n");
7 |
8 | js.console.log(string);
9 | js.console.log(string.charAt(3));
10 |
11 | const power = td.decode("low-power");
12 | const options = js.RequestAdapterOptions{ .powerPreference = power };
13 | js.navigator.gpu.requestAdapter(options.toExtern());
14 |
15 | return 0;
16 | }
17 |
--------------------------------------------------------------------------------
/www/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {[app_name]s}
6 |
7 |
8 |
9 |
10 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/example/example.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const zig = require('./sysjs_generated');
3 | const source = fs.readFileSync("zig-out/lib/example.wasm");
4 | const typedArray = new Uint8Array(source);
5 | let text_decoder = new TextDecoder();
6 |
7 | let memory = undefined;
8 |
9 | WebAssembly.instantiate(typedArray, {
10 | env: {
11 | sysjs_console_log: (v0_str_ptr, v0_str_len) => {
12 | const v0 = text_decoder.decode(new Uint8Array(memory.buffer, v0_str_ptr, v0_str_len));
13 | console.log(`JS:console.log("${v0}")`);
14 | }
15 | },
16 | }).then(result => {
17 | const doPrint = result.instance.exports.doPrint;
18 | memory = result.instance.exports.memory;
19 | doPrint();
20 | });
21 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2021, Hexops Contributors (given via the Git commit history).
2 |
3 | All documentation, image, sound, font, and 2D/3D model files are CC-BY-4.0 licensed unless
4 | otherwise noted. You may get a copy of this license at https://creativecommons.org/licenses/by/4.0
5 |
6 | Files in a directory with a separate LICENSE file may contain files under different license terms,
7 | described within that LICENSE file.
8 |
9 | All other files are licensed under the Apache License, Version 2.0 (see LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
10 | or the MIT license (see LICENSE-MIT or http://opensource.org/licenses/MIT), at your option.
11 |
12 | All files in the project without exclusions may not be copied, modified, or distributed except
13 | according to the terms above.
--------------------------------------------------------------------------------
/example/sysjs.zig:
--------------------------------------------------------------------------------
1 | //! A file-level comment
2 |
3 | const builtin = @import("builtin");
4 |
5 | pub const bindings = struct {
6 | pub const console = struct {
7 | /// console.log invokes the JS console.log API
8 | log: fn (string: String) void,
9 | log2: fn (string: []const u8, []const u8) void,
10 | };
11 |
12 | pub const TextDecoder = struct {
13 | new: fn () TextDecoder,
14 | decode: fn (td: TextDecoder, str: []const u8) String,
15 | };
16 |
17 | pub const String = struct {
18 | new: fn (buf: []const u8) String,
19 | charAt: fn (string: String, index: u32) String,
20 | };
21 |
22 | pub const navigator = struct {
23 | pub const gpu = struct {
24 | requestAdapter: fn (options: RequestAdapterOptions) void,
25 | };
26 | };
27 | };
28 |
29 | pub const RequestAdapterOptions = struct {
30 | powerPreference: String,
31 | };
32 |
33 | /// doPrint does stuff
34 | pub fn doPrint() void {
35 | // use console.log
36 | console.log("zig:js.console.log(\"hello from Zig\")");
37 | }
38 |
--------------------------------------------------------------------------------
/LICENSE-MIT:
--------------------------------------------------------------------------------
1 | Copyright (c) 2021 Hexops Contributors (given via the Git commit history).
2 |
3 | Permission is hereby granted, free of charge, to any
4 | person obtaining a copy of this software and associated
5 | documentation files (the "Software"), to deal in the
6 | Software without restriction, including without
7 | limitation the rights to use, copy, modify, merge,
8 | publish, distribute, sublicense, and/or sell copies of
9 | the Software, and to permit persons to whom the Software
10 | is furnished to do so, subject to the following
11 | conditions:
12 |
13 | The above copyright notice and this permission notice
14 | shall be included in all copies or substantial portions
15 | of the Software.
16 |
17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
25 | DEALINGS IN THE SOFTWARE.
26 |
--------------------------------------------------------------------------------
/example/sysjs_generated.js:
--------------------------------------------------------------------------------
1 | // Generated by `zig build`; DO NOT EDIT.
2 | let wasm = undefined;
3 | let wasm_memory_buf = undefined;
4 | function wasmGetMemory() {
5 | if (wasm_memory_buf === undefined || wasm_memory_buf !== wasm.memory.buffer) {
6 | wasm_memory_buf = new Uint8Array(wasm.memory.buffer);
7 | }
8 | return wasm_memory_buf;
9 | }
10 |
11 | function wasmGetDataView() {
12 | return new DataView(wasm.memory.buffer);
13 | }
14 |
15 | function wasmGetSlice(ptr, len) {
16 | return wasmGetMemory().slice(ptr, ptr + len);
17 | }
18 |
19 | let wasm_objects = [];
20 | function wasmWrapObject(obj) {
21 | return wasm_objects.push(obj) - 1;
22 | }
23 |
24 | function wasmGetObject(id) {
25 | return wasm_objects[id];
26 | }
27 |
28 | export function init(wasmObj) {
29 | wasm = wasmObj;
30 | }
31 |
32 | /* Gen */
33 | export function sysjs_console_log(string) {
34 | const l0 = wasmGetObject(string);
35 | console.log(l0);
36 | }
37 | export function sysjs_console_log2(string, string_len, v1, v1_len) {
38 | const l0 = wasmGetSlice(string, string_len);
39 | const l1 = wasmGetSlice(v1, v1_len);
40 | console.log2(l0, l1);
41 | }
42 | export function sysjs_TextDecoder_new() {
43 | return wasmWrapObject(new TextDecoder());
44 | }
45 | export function sysjs_TextDecoder_decode(td, str, str_len) {
46 | const l0 = wasmGetObject(td);
47 | const l1 = wasmGetSlice(str, str_len);
48 | return wasmWrapObject(l0.decode(l1));
49 | }
50 | export function sysjs_String_new(buf, buf_len) {
51 | const l0 = wasmGetSlice(buf, buf_len);
52 | return wasmWrapObject(new String(l0));
53 | }
54 | export function sysjs_String_charAt(string, index) {
55 | const l0 = wasmGetObject(string);
56 | const l1 = index;
57 | return wasmWrapObject(l0.charAt(l1));
58 | }
59 | export function sysjs_navigator_gpu_requestAdapter(options) {
60 | const l0 = {
61 | 'powerPreference': wasmGetObject(wasmGetDataView().getUint32(options + 0, true)),
62 | };
63 | navigator.gpu.requestAdapter(l0);
64 | }
65 |
--------------------------------------------------------------------------------
/src/analysis.zig:
--------------------------------------------------------------------------------
1 | const std = @import("std");
2 | const Ast = std.zig.Ast;
3 |
4 | // Borrowed from ZLS analysis engine:
5 | // https://github.com/zigtools/zls/blob/master/src/analysis.zig#L301
6 | pub fn getDeclNameToken(tree: Ast, node: Ast.Node.Index) ?Ast.TokenIndex {
7 | const tags = tree.nodes.items(.tag);
8 | const datas = tree.nodes.items(.data);
9 | const main_token = tree.nodes.items(.main_token)[node];
10 |
11 | return switch (tags[node]) {
12 | // regular declaration names. + 1 to mut token because name comes after 'const'/'var'
13 | .local_var_decl,
14 | .global_var_decl,
15 | .simple_var_decl,
16 | .aligned_var_decl,
17 | => {
18 | const tok = tree.fullVarDecl(node).?.ast.mut_token + 1;
19 | return if (tok >= tree.tokens.len)
20 | null
21 | else
22 | tok;
23 | },
24 | // function declaration names
25 | .fn_proto,
26 | .fn_proto_multi,
27 | .fn_proto_one,
28 | .fn_proto_simple,
29 | .fn_decl,
30 | => blk: {
31 | var params: [1]Ast.Node.Index = undefined;
32 | break :blk tree.fullFnProto(¶ms, node).?.name_token;
33 | },
34 |
35 | // containers
36 | .container_field,
37 | .container_field_init,
38 | .container_field_align,
39 | => {
40 | const field = tree.fullContainerField(node).?.ast;
41 | return field.main_token;
42 | },
43 |
44 | .identifier => main_token,
45 | .error_value => {
46 | const tok = main_token + 2;
47 | return if (tok >= tree.tokens.len)
48 | null
49 | else
50 | tok;
51 | }, // 'error'.
52 |
53 | .test_decl => datas[node].lhs,
54 |
55 | else => null,
56 | };
57 | }
58 |
59 | pub const DeclType = enum { field, decl, other };
60 |
61 | pub fn getDeclType(tree: Ast, decl_idx: Ast.Node.Index) DeclType {
62 | const tags = tree.nodes.items(.tag);
63 | return switch (tags[decl_idx]) {
64 | .container_field_init,
65 | .container_field_align,
66 | .container_field,
67 | => .field,
68 | .local_var_decl,
69 | .global_var_decl,
70 | .simple_var_decl,
71 | .aligned_var_decl,
72 | => .decl,
73 | else => .other,
74 | };
75 | }
76 |
--------------------------------------------------------------------------------
/example/sysjs_generated.zig:
--------------------------------------------------------------------------------
1 | // Generated by `zig build`; DO NOT EDIT.
2 |
3 |
4 | const builtin = @import("builtin");
5 |
6 |
7 |
8 | pub const console = struct {
9 |
10 | extern fn sysjs_console_log(string: u32) void;
11 | pub inline fn log(string: String) void {
12 | sysjs_console_log(string.id);
13 | }
14 | extern fn sysjs_console_log2(string: [*]const u8, string_len: u32, [*]const u8, u32) void;
15 | pub inline fn log2(string: []const u8, v1: []const u8) void {
16 | sysjs_console_log2(string.ptr, string.len, v1.ptr, v1.len);
17 | }
18 | };
19 |
20 | pub const TextDecoder = struct {
21 | id: u32,
22 |
23 | extern fn sysjs_TextDecoder_new() u32;
24 | pub inline fn new() TextDecoder {
25 | return TextDecoder{.id = sysjs_TextDecoder_new()};
26 | }
27 | extern fn sysjs_TextDecoder_decode(td: u32, str: [*]const u8, str_len: u32) u32;
28 | pub inline fn decode(td: TextDecoder, str: []const u8) String {
29 | return String{.id = sysjs_TextDecoder_decode(td.id, str.ptr, str.len)};
30 | }
31 | };
32 |
33 | pub const String = struct {
34 | id: u32,
35 |
36 | extern fn sysjs_String_new(buf: [*]const u8, buf_len: u32) u32;
37 | pub inline fn new(buf: []const u8) String {
38 | return String{.id = sysjs_String_new(buf.ptr, buf.len)};
39 | }
40 | extern fn sysjs_String_charAt(string: u32, index: u32) u32;
41 | pub inline fn charAt(string: String, index: u32) String {
42 | return String{.id = sysjs_String_charAt(string.id, index)};
43 | }
44 | };
45 |
46 | pub const navigator = struct {
47 |
48 |
49 | pub const gpu = struct {
50 |
51 | extern fn sysjs_navigator_gpu_requestAdapter(options: *const CRequestAdapterOptions) void;
52 | pub inline fn requestAdapter(options: CRequestAdapterOptions) void {
53 | sysjs_navigator_gpu_requestAdapter(&options);
54 | }
55 | };
56 | };
57 |
58 | pub const RequestAdapterOptions = struct {
59 | powerPreference: String,
60 |
61 | pub fn toExtern(s: RequestAdapterOptions) CRequestAdapterOptions {
62 | return CRequestAdapterOptions{
63 | .powerPreference = s.powerPreference.id,
64 | };
65 | }
66 | };
67 |
68 | pub const CRequestAdapterOptions = extern struct {
69 | powerPreference: u32,
70 |
71 | };
72 | pub fn doPrint() void {
73 | // use console.log
74 | console.log("zig:js.console.log(\"hello from Zig\")");
75 | }
76 |
--------------------------------------------------------------------------------
/src/main.zig:
--------------------------------------------------------------------------------
1 | const std = @import("std");
2 | const testing = std.testing;
3 | const Ast = std.zig.Ast;
4 | const IrGen = @import("IrGen.zig");
5 |
6 | pub fn generateFiles(
7 | allocator: std.mem.Allocator,
8 | bindings_path: []const u8,
9 | generated_sysjs_zig: []const u8,
10 | generated_sysjs_js: []const u8,
11 | ) !void {
12 | var generated_zig = try std.fs.cwd().createFile(generated_sysjs_zig, .{});
13 | var generated_js = try std.fs.cwd().createFile(generated_sysjs_js, .{});
14 | const bindings_code = try std.fs.cwd().readFileAllocOptions(allocator, bindings_path, 1024 * 1024 * 128, null, @alignOf(u8), 0);
15 | defer allocator.free(bindings_code);
16 |
17 | const zig_writer = generated_zig.writer();
18 | const js_writer = generated_js.writer();
19 | var gen = Generator(@TypeOf(zig_writer), @TypeOf(js_writer)){
20 | .zig = zig_writer,
21 | .js = js_writer,
22 | .allocator = allocator,
23 | .bindings_code = bindings_code,
24 | };
25 | try gen.root();
26 |
27 | generated_zig.close();
28 | generated_js.close();
29 | }
30 |
31 | fn Generator(comptime ZigWriter: type, comptime JSWriter: type) type {
32 | return struct {
33 | zig: ZigWriter,
34 | js: JSWriter,
35 | allocator: std.mem.Allocator,
36 | bindings_code: [:0]const u8,
37 |
38 | // internal fields
39 | tree: Ast = undefined,
40 | buf: [4096]u8 = undefined,
41 | namespace: std.ArrayListUnmanaged([]const u8) = .{},
42 |
43 | pub fn root(gen: *@This()) !void {
44 | gen.tree = try Ast.parse(gen.allocator, gen.bindings_code, .zig);
45 | defer gen.tree.deinit(gen.allocator);
46 |
47 | try std.fmt.format(gen.zig, "// Generated by `zig build`; DO NOT EDIT.\n", .{});
48 | try std.fmt.format(gen.js, "// Generated by `zig build`; DO NOT EDIT.\n", .{});
49 |
50 | var irgen = IrGen{
51 | .allocator = gen.allocator,
52 | .ast = gen.tree,
53 | };
54 |
55 | try irgen.addRoot();
56 |
57 | try gen.generateJsScaffold();
58 | try irgen.root.emitZig(gen.allocator, gen.zig, 0);
59 | try irgen.root.emitJs(gen.allocator, gen.js, 0);
60 | }
61 |
62 | fn generateJsScaffold(gen: *@This()) !void {
63 | try gen.js.writeAll(
64 | \\let wasm = undefined;
65 | \\let wasm_memory_buf = undefined;
66 | \\function wasmGetMemory() {
67 | \\ if (wasm_memory_buf === undefined || wasm_memory_buf !== wasm.memory.buffer) {
68 | \\ wasm_memory_buf = new Uint8Array(wasm.memory.buffer);
69 | \\ }
70 | \\ return wasm_memory_buf;
71 | \\}
72 | \\
73 | \\function wasmGetDataView() {
74 | \\ return new DataView(wasm.memory.buffer);
75 | \\}
76 | \\
77 | \\function wasmGetSlice(ptr, len) {
78 | \\ return wasmGetMemory().slice(ptr, ptr + len);
79 | \\}
80 | \\
81 | \\let wasm_objects = [];
82 | \\function wasmWrapObject(obj) {
83 | \\ return wasm_objects.push(obj) - 1;
84 | \\}
85 | \\
86 | \\function wasmGetObject(id) {
87 | \\ return wasm_objects[id];
88 | \\}
89 | \\
90 | \\export function init(wasmObj) {
91 | \\ wasm = wasmObj;
92 | \\}
93 | \\
94 | \\/* Gen */
95 | \\
96 | );
97 | }
98 | };
99 | }
100 |
--------------------------------------------------------------------------------
/LICENSE-APACHE:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright [yyyy] [name of copyright owner]
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
203 |
--------------------------------------------------------------------------------
/src/IrGen.zig:
--------------------------------------------------------------------------------
1 | const std = @import("std");
2 | const Ast = std.zig.Ast;
3 | const analysis = @import("analysis.zig");
4 | const types = @import("types.zig");
5 | const Container = types.Container;
6 | const Function = types.Function;
7 | const Type = types.Type;
8 |
9 | const IrGen = @This();
10 |
11 | ast: Ast,
12 | allocator: std.mem.Allocator,
13 | root: *Container = undefined,
14 |
15 | pub fn addRoot(gen: *IrGen) !void {
16 | gen.root = try gen.addContainer(null, gen.ast.containerDeclRoot(), null);
17 | gen.resolveContainer(gen.root);
18 | }
19 |
20 | fn addContainer(
21 | gen: IrGen,
22 | parent: ?*Container,
23 | container_decl: Ast.full.ContainerDecl,
24 | name: ?[]const u8,
25 | ) anyerror!*Container {
26 | var cont = try gen.allocator.create(Container);
27 |
28 | const is_struct_bindings = (name != null and std.mem.eql(u8, name.?, "bindings"));
29 | const is_parent_bindings = (parent != null and parent.?.val_type == .namespace);
30 |
31 | cont.* = Container{
32 | .name = if (is_struct_bindings) null else name,
33 | .parent = parent,
34 | .val_type = if (is_parent_bindings or is_struct_bindings)
35 | .namespace
36 | else
37 | .struct_val,
38 | };
39 |
40 | if (parent) |par|
41 | try par.children.append(gen.allocator, cont);
42 |
43 | for (container_decl.ast.members) |decl_idx| {
44 | const std_type = analysis.getDeclType(gen.ast, decl_idx);
45 | switch (std_type) {
46 | // If this decl is a struct field `foo: fn () void,` then consume it.
47 | .field => {
48 | try gen.addContainerField(cont, decl_idx);
49 | continue;
50 | },
51 | // If this decl is a `pub const foo = struct {};` then consume it
52 | .decl => {
53 | if (try gen.addContainerDecl(cont, decl_idx))
54 | continue;
55 | },
56 | .other => {},
57 | }
58 |
59 | try cont.contents.append(gen.allocator, .{ .std_code = .{
60 | .data = gen.ast.getNodeSource(decl_idx),
61 | .type = std_type,
62 | } });
63 | }
64 |
65 | if (cont.val_type == .namespace) {
66 | const emit_id_field: bool = blk: {
67 | for (cont.contents.items) |content| {
68 | switch (content) {
69 | .func => |func| if (func.val_ty != .none)
70 | break :blk true
71 | else
72 | continue,
73 | else => continue,
74 | }
75 | }
76 | break :blk false;
77 | };
78 |
79 | if (emit_id_field) {
80 | try cont.fields.append(gen.allocator, .{
81 | .name = "id",
82 | .type = .{
83 | .slice = "u32",
84 | .info = .{ .int = .{ .bits = 32, .signedness = .unsigned } },
85 | },
86 | });
87 | }
88 | }
89 |
90 | return cont;
91 | }
92 |
93 | // Resolves all types in a container recursively
94 | fn resolveContainer(gen: *IrGen, cont: *Container) void {
95 | for (cont.contents.items, 0..) |item, idx| {
96 | switch (item) {
97 | .func => {
98 | var fun = cont.contents.items[idx].func;
99 | fun.return_ty = gen.resolveType(cont, fun.return_ty);
100 |
101 | for (fun.params, 0..) |param, pid| {
102 | var par = fun.params[pid];
103 | par.type = gen.resolveType(cont, param.type);
104 | fun.params[pid] = par;
105 | }
106 | },
107 | .container => |child| gen.resolveContainer(child),
108 | else => {},
109 | }
110 | }
111 |
112 | for (cont.fields.items, 0..) |field, idx| {
113 | cont.fields.items[idx].type = gen.resolveType(cont, field.type);
114 | }
115 | }
116 |
117 | fn addContainerField(
118 | gen: IrGen,
119 | container: *Container,
120 | node: Ast.Node.Index,
121 | ) !void {
122 | const field = gen.ast.fullContainerField(node).?;
123 |
124 | if (field.ast.value_expr == 0) {
125 | const type_expr = gen.ast.nodes.get(field.ast.type_expr);
126 | switch (type_expr.tag) {
127 | .fn_proto_simple, .fn_proto_multi => {
128 | if (container.val_type != .struct_val) {
129 | try gen.addFunction(
130 | container,
131 | field.ast.type_expr,
132 | field.ast.main_token,
133 | );
134 | return;
135 | }
136 | },
137 | else => {},
138 | }
139 | }
140 |
141 | try container.fields.append(gen.allocator, .{
142 | .name = gen.ast.tokenSlice(field.ast.main_token),
143 | .type = try gen.makeType(field.ast.type_expr),
144 | });
145 | }
146 |
147 | fn addContainerDecl(
148 | gen: IrGen,
149 | container: *Container,
150 | node: Ast.Node.Index,
151 | ) !bool {
152 | const var_decl = gen.ast.fullVarDecl(node).?;
153 | // var/const
154 | if (var_decl.visib_token) |visib_token| {
155 | const visib = gen.ast.tokenSlice(visib_token);
156 | if (std.mem.eql(u8, visib, "pub")) {
157 | // pub var/const
158 | const init_node = gen.ast.nodes.get(var_decl.ast.init_node);
159 | switch (init_node.tag) {
160 | .container_decl,
161 | .container_decl_two,
162 | .container_decl_trailing,
163 | .container_decl_two_trailing,
164 | => {
165 | var buf: [2]Ast.Node.Index = undefined;
166 | const container_decl = gen.ast.fullContainerDecl(&buf, var_decl.ast.init_node).?;
167 |
168 | const name = if (analysis.getDeclNameToken(gen.ast, node)) |token|
169 | gen.ast.tokenSlice(token)
170 | else
171 | null;
172 |
173 | const cont = try gen.addContainer(container, container_decl, name);
174 | try container.contents.append(gen.allocator, .{ .container = cont });
175 |
176 | return true;
177 | },
178 | else => {},
179 | }
180 | }
181 | }
182 |
183 | return false;
184 | }
185 |
186 | fn addFunction(gen: IrGen, container: *Container, node_index: Ast.TokenIndex, name_token: Ast.TokenIndex) !void {
187 | var param_buf: [1]Ast.Node.Index = undefined;
188 | const fn_proto = gen.ast.fullFnProto(¶m_buf, node_index).?;
189 |
190 | const name = gen.ast.tokenSlice(name_token);
191 | const return_type = try gen.makeType(fn_proto.ast.return_type);
192 |
193 | var params: std.ArrayListUnmanaged(Function.Param) = .{};
194 |
195 | var params_iter = fn_proto.iterate(&gen.ast);
196 | var i: usize = 0;
197 | while (params_iter.next()) |param| : (i += 1) {
198 | try params.append(gen.allocator, .{
199 | .name = if (param.name_token) |nt| gen.ast.tokenSlice(nt) else null,
200 | .type = try gen.makeType(param.type_expr),
201 | });
202 | }
203 |
204 | const func_obj = try gen.allocator.create(Function);
205 | func_obj.* = Function{
206 | .name = name,
207 | .return_ty = return_type,
208 | .params = params.items,
209 | .parent = container,
210 | .val_ty = if (container.name) |coname|
211 | // If the return type is container and the function name is 'new', it is a container
212 | // If the function name is not new, but return type is container, we can assume that
213 | // the function returns an appropriate type, so `new Class()` is not needed.
214 | if (std.mem.eql(u8, coname, return_type.slice) and std.mem.eql(u8, name, "new"))
215 | .constructor
216 | else if (params.items.len > 0 and
217 | std.mem.eql(u8, coname, params.items[0].type.slice))
218 | .method
219 | else
220 | .none
221 | else
222 | .none,
223 | };
224 |
225 | try container.contents.append(gen.allocator, .{ .func = func_obj });
226 | }
227 |
228 | fn makeType(gen: IrGen, index: Ast.Node.Index) !Type {
229 | const token_slice = gen.ast.getNodeSource(index);
230 | if (gen.ast.fullPtrType(index)) |ptr| {
231 | const child_ty = try gen.makeType(ptr.ast.child_type);
232 | const base_ty = try gen.allocator.create(Type);
233 | base_ty.* = child_ty;
234 |
235 | return Type{
236 | .slice = token_slice,
237 | .info = .{ .ptr = Type.Ptr{
238 | .size = ptr.size,
239 | .is_const = ptr.const_token != null,
240 | .base_ty = base_ty,
241 | } },
242 | };
243 | }
244 |
245 | if (std.zig.primitives.isPrimitive(token_slice)) {
246 | const signedness: ?Type.Int.Signedness = switch (token_slice[0]) {
247 | 'u' => .unsigned,
248 | 'i' => .signed,
249 | else => null,
250 | };
251 |
252 | const float = token_slice[0] == 'f';
253 | const c_types = token_slice[0] == 'c' and token_slice[1] == '_';
254 |
255 | const size: ?u16 = std.fmt.parseInt(u16, token_slice[1..], 10) catch |err| blk: {
256 | switch (err) {
257 | error.InvalidCharacter => break :blk null,
258 | else => |e| return e,
259 | }
260 | };
261 |
262 | if (signedness) |sig| {
263 | // iXX or uXX
264 | if (size) |sz| {
265 | return Type{
266 | .slice = token_slice,
267 | .info = .{
268 | .int = Type.Int{
269 | .signedness = sig,
270 | .bits = sz,
271 | },
272 | },
273 | };
274 | } else {
275 | // TODO: usize or isize
276 | }
277 | }
278 |
279 | if (float and size != null) {
280 | // fXX
281 | return Type{
282 | .slice = token_slice,
283 | .info = .{
284 | .float = Type.Float{
285 | .bits = size.?,
286 | },
287 | },
288 | };
289 | }
290 |
291 | if (c_types) {
292 | // TODO c types
293 | }
294 |
295 | inline for (std.meta.fields(Type.TypeInfo)) |field| {
296 | if (field.type == void) {
297 | if (std.mem.eql(u8, token_slice, field.name)) {
298 | return Type{
299 | .slice = token_slice,
300 | .info = @unionInit(Type.TypeInfo, field.name, {}),
301 | };
302 | }
303 | }
304 | }
305 |
306 | @panic("TODO: error on impossible types");
307 | }
308 |
309 | return Type{
310 | .slice = token_slice,
311 | .info = .{ .name_ref = {} },
312 | };
313 | }
314 |
315 | fn resolveType(
316 | gen: *IrGen,
317 | container: *Container,
318 | ty: Type,
319 | ) Type {
320 | switch (ty.info) {
321 | .name_ref => {
322 | // Check if type is current container
323 | if (container.name) |cname| {
324 | if (std.mem.eql(u8, cname, ty.slice))
325 | return Type{ .slice = cname, .info = .{ .composite_ref = {} } };
326 | }
327 |
328 | // Check if the type is parent, or parent's parent and so on...
329 | var container_parent = container.parent;
330 | while (container_parent) |parent| {
331 | if (gen.resolveTypeRecurse(parent.contents.items, ty)) |resolved_type|
332 | return resolved_type;
333 |
334 | container_parent = parent.parent;
335 | }
336 | },
337 | else => {},
338 | }
339 |
340 | return ty;
341 | }
342 |
343 | fn resolveTypeRecurse(gen: *IrGen, contents: []Container.Content, ty: Type) ?Type {
344 | for (contents) |item| {
345 | switch (item) {
346 | .container => |cont| {
347 | if (cont.name) |cname| {
348 | if (std.mem.eql(u8, cname, ty.slice)) {
349 | if (cont.val_type == .namespace) {
350 | return Type{ .slice = cname, .info = .{ .composite_ref = {} } };
351 | } else {
352 | return Type{ .slice = cname, .info = .{ .container_value = cont } };
353 | }
354 | }
355 | }
356 |
357 | if (gen.resolveTypeRecurse(cont.contents.items, ty)) |res|
358 | return res;
359 | },
360 | else => {},
361 | }
362 | }
363 | return null;
364 | }
365 |
366 | fn makeCompositeType(
367 | gen: IrGen,
368 | container: Ast.full.ContainerDecl,
369 | init_node: Ast.Node.Index,
370 | ) !Type {
371 | var fields: std.ArrayListUnmanaged(Type.Composite.Field) = .{};
372 |
373 | for (container.ast.members) |decl_idx| {
374 | const std_type = analysis.getDeclType(gen.ast, decl_idx);
375 | switch (std_type) {
376 | .field => {
377 | const field = gen.ast.fullContainerField(decl_idx);
378 | const name = gen.ast.tokenSlice(field.?.ast.main_token);
379 | const ty = try gen.makeType(field.?.ast.type_expr);
380 | const ty_alloc = try gen.allocator.create(Type);
381 | ty_alloc.* = ty;
382 |
383 | try fields.append(gen.allocator, .{
384 | .name = name,
385 | .type = ty_alloc,
386 | });
387 | },
388 | else => {}, // TODO
389 | }
390 | }
391 |
392 | return Type{ .slice = gen.ast.getNodeSource(init_node), .info = .{
393 | .composite = .{ .fields = try fields.toOwnedSlice(gen.allocator) },
394 | } };
395 | }
396 |
--------------------------------------------------------------------------------
/src/types.zig:
--------------------------------------------------------------------------------
1 | const std = @import("std");
2 | const Ast = std.zig.Ast;
3 | const analysis = @import("analysis.zig");
4 |
5 | pub const Container = struct {
6 | name: ?[]const u8 = null,
7 | parent: ?*Container = null,
8 | children: std.ArrayListUnmanaged(*Container) = .{},
9 | contents: std.ArrayListUnmanaged(Content) = .{},
10 | types: std.ArrayListUnmanaged(Type) = .{},
11 | fields: std.ArrayListUnmanaged(Type.Composite.Field) = .{},
12 | val_type: enum { namespace, struct_val } = .struct_val,
13 |
14 | const Namespace = std.ArrayListUnmanaged(u8);
15 |
16 | pub const Content = union(enum) {
17 | func: *Function,
18 | container: *Container,
19 | std_code: struct {
20 | type: analysis.DeclType = .other,
21 | data: []const u8,
22 | },
23 | };
24 |
25 | fn composeNamespace(container: *const Container, allocator: std.mem.Allocator) !Namespace {
26 | var namespace: Namespace = .{};
27 |
28 | var parent: ?*const Container = container;
29 | while (parent) |par| {
30 | if (par.name) |name| {
31 | try namespace.insert(allocator, 0, '_');
32 | try namespace.insertSlice(allocator, 0, name);
33 | }
34 |
35 | if (par.parent) |pd| {
36 | parent = pd;
37 | } else {
38 | break;
39 | }
40 | }
41 |
42 | return namespace;
43 | }
44 |
45 | pub fn emitZig(container: Container, allocator: std.mem.Allocator, writer: anytype, indent: u8) !void {
46 | _ = try writer.writeByte('\n');
47 |
48 | var local_indent = indent;
49 | if (container.name) |n| {
50 | _ = try writer.writeByteNTimes(' ', indent);
51 | // TODO: namespacing
52 | try std.fmt.format(writer, "pub const {s} = struct {{\n", .{n});
53 | local_indent += 4;
54 | }
55 |
56 | for (container.fields.items) |field| {
57 | try writer.writeByteNTimes(' ', local_indent);
58 | try writer.print("{s}: {s},\n", .{ field.name, field.type.slice });
59 | }
60 |
61 | try writer.writeByte('\n');
62 |
63 | for (container.contents.items) |content|
64 | switch (content) {
65 | .func => {
66 | var namespace = try container.composeNamespace(allocator);
67 | defer namespace.deinit(allocator);
68 |
69 | const fun = content.func;
70 | try fun.emitExtern(writer, local_indent, namespace);
71 | try fun.emitWrapper(writer, local_indent, namespace);
72 | },
73 | .container => {
74 | try content.container.emitZig(allocator, writer, local_indent);
75 | try content.container.emitExtern(allocator, writer, local_indent);
76 | },
77 | .std_code => |stdc| {
78 | _ = try writer.writeByteNTimes(' ', local_indent);
79 | try writer.writeAll(stdc.data);
80 |
81 | try writer.writeAll(switch (stdc.type) {
82 | .field => unreachable,
83 | .decl => ";\n",
84 | else => "\n",
85 | });
86 | },
87 | };
88 |
89 | if (container.name) |n| {
90 | if (container.val_type == .struct_val) {
91 | try writer.writeByteNTimes(' ', local_indent);
92 | try writer.print("pub fn toExtern(s: {s}) C{s} {{\n", .{ n, n });
93 | try writer.writeByteNTimes(' ', local_indent + 4);
94 | try writer.print("return C{s}{{\n", .{n});
95 |
96 | for (container.fields.items) |field| {
97 | try writer.writeByteNTimes(' ', local_indent + 8);
98 | try field.type.emitExternField(writer, field.name, "s");
99 | }
100 |
101 | try writer.writeByteNTimes(' ', local_indent + 4);
102 | try writer.writeAll("};\n");
103 |
104 | try writer.writeByteNTimes(' ', local_indent);
105 | try writer.writeAll("}\n");
106 | }
107 |
108 | _ = try writer.writeByteNTimes(' ', indent);
109 | _ = try writer.write("};\n");
110 | }
111 | }
112 |
113 | pub fn emitExtern(container: Container, allocator: std.mem.Allocator, writer: anytype, indent: u8) anyerror!void {
114 | if (container.val_type == .namespace)
115 | return;
116 |
117 | _ = try writer.writeByte('\n');
118 |
119 | var local_indent = indent;
120 | if (container.name) |n| {
121 | _ = try writer.writeByteNTimes(' ', indent);
122 | try std.fmt.format(writer, "pub const C{s} = extern struct {{\n", .{n});
123 | local_indent += 4;
124 | }
125 |
126 | for (container.fields.items) |field| {
127 | try writer.writeByteNTimes(' ', local_indent);
128 | try field.type.emitExternParam(writer, field.name);
129 | try writer.writeAll(",\n");
130 | }
131 |
132 | try writer.writeByte('\n');
133 |
134 | for (container.contents.items) |content|
135 | switch (content) {
136 | .func => {},
137 | .container => {
138 | try content.container.emitZig(allocator, writer, local_indent);
139 | try content.container.emitExtern(allocator, writer, local_indent);
140 | },
141 | .std_code => |stdc| {
142 | _ = try writer.writeByteNTimes(' ', local_indent);
143 | try writer.writeAll(stdc.data);
144 |
145 | try writer.writeAll(switch (stdc.type) {
146 | .field => unreachable,
147 | .decl => ";\n",
148 | else => "\n",
149 | });
150 | },
151 | };
152 |
153 | if (container.name != null) {
154 | _ = try writer.writeByteNTimes(' ', indent);
155 | _ = try writer.write("};\n");
156 | }
157 | }
158 |
159 | pub fn emitJs(container: Container, allocator: std.mem.Allocator, writer: anytype, indent: u8) !void {
160 | for (container.contents.items) |content| {
161 | switch (content) {
162 | .func => {
163 | var namespace = try container.composeNamespace(allocator);
164 | defer namespace.deinit(allocator);
165 |
166 | try content.func.emitBinding(writer, indent, namespace);
167 | },
168 | .container => try content.container.emitJs(allocator, writer, indent),
169 | else => {},
170 | }
171 | }
172 | }
173 | };
174 |
175 | pub const Function = struct {
176 | name: []const u8,
177 | parent: ?*Container = null,
178 | return_ty: Type,
179 | params: []Param,
180 | val_ty: enum { constructor, method, none } = .none,
181 |
182 | // No. of parameters in zig's self hosted wasm backend is limited to maxInt(u32).
183 | // The actual number of limit is however runtime implementation defined.
184 | const max_param_char_count = std.math.log10_int(@as(u32, std.math.maxInt(u32))) + 1;
185 |
186 | pub const Param = struct {
187 | name: ?[]const u8,
188 | type: Type,
189 | };
190 |
191 | pub fn emitExtern(fun: Function, writer: anytype, indent: u8, namespace: Container.Namespace) !void {
192 | _ = try writer.writeByteNTimes(' ', indent);
193 | try writer.writeAll("extern fn sysjs_");
194 | try writer.writeAll(namespace.items);
195 | try writer.writeAll(fun.name);
196 | try writer.writeByte('(');
197 |
198 | for (fun.params, 0..) |param, i| {
199 | if (i != 0) try writer.writeAll(", ");
200 | try param.type.emitExternParam(writer, param.name);
201 | }
202 |
203 | switch (fun.return_ty.info) {
204 | .container_value => {
205 | try writer.writeAll(", ");
206 | try fun.return_ty.emitExternParam(writer, "return_val");
207 | try writer.writeAll(") void;\n");
208 | },
209 | else => {
210 | try writer.writeAll(") ");
211 | try fun.return_ty.emitExternParam(writer, null);
212 | try writer.writeAll(";\n");
213 | },
214 | }
215 | }
216 |
217 | pub fn emitWrapper(fun: Function, writer: anytype, indent: u8, namespace: Container.Namespace) !void {
218 | _ = try writer.writeByteNTimes(' ', indent);
219 | try writer.print("pub inline fn {s}(", .{fun.name});
220 |
221 | for (fun.params, 0..) |param, i| {
222 | if (i != 0) try writer.writeAll(", ");
223 | if (param.name) |name| {
224 | try param.type.emitParam(writer, name);
225 | } else {
226 | var buf: [Function.max_param_char_count]u8 = undefined;
227 | const name = try std.fmt.bufPrint(&buf, "v{d}", .{i});
228 | try param.type.emitParam(writer, name);
229 | }
230 | }
231 |
232 | try writer.writeAll(") ");
233 | try fun.return_ty.emitParam(writer, null);
234 | try writer.writeAll(" {\n");
235 |
236 | _ = try writer.writeByteNTimes(' ', indent + 4);
237 |
238 | switch (fun.return_ty.info) {
239 | .void => {},
240 | .container_value => {
241 | try writer.writeAll("var return_val: ");
242 | try fun.return_ty.emitExternParam(writer, null);
243 | try writer.writeAll(" = undefined;\n");
244 |
245 | _ = try writer.writeByteNTimes(' ', indent + 4);
246 | },
247 | else => try writer.writeAll("return "),
248 | }
249 |
250 | const is_ret_obj = fun.return_ty.info == .composite_ref;
251 | if (is_ret_obj) {
252 | try writer.print("{s}{{.id = ", .{fun.return_ty.slice});
253 | }
254 |
255 | try writer.writeAll("sysjs_");
256 | try writer.writeAll(namespace.items);
257 | try writer.writeAll(fun.name);
258 | try writer.writeByte('(');
259 |
260 | for (fun.params, 0..) |param, i| {
261 | if (i != 0) try writer.writeAll(", ");
262 | if (param.name) |name| {
263 | try param.type.emitExternArg(writer, name);
264 | } else {
265 | var buf: [Function.max_param_char_count]u8 = undefined;
266 | const name = try std.fmt.bufPrint(&buf, "v{d}", .{i});
267 | try param.type.emitExternArg(writer, name);
268 | }
269 | }
270 |
271 | if (fun.return_ty.info == .container_value) {
272 | try writer.writeAll(", &return_val);\n");
273 | _ = try writer.writeByteNTimes(' ', indent + 4);
274 | try writer.writeAll("return return_val");
275 | } else try writer.writeByte(')');
276 |
277 | if (is_ret_obj) try writer.writeByte('}');
278 | try writer.writeAll(";\n");
279 |
280 | _ = try writer.writeByteNTimes(' ', indent);
281 | try writer.writeAll("}\n");
282 | }
283 |
284 | pub fn emitBinding(fun: Function, writer: anytype, indent: u8, namespace: Container.Namespace) !void {
285 | _ = try writer.writeByteNTimes(' ', indent);
286 | try writer.print("export function sysjs_{s}{s}(", .{ namespace.items, fun.name });
287 |
288 | for (fun.params, 0..) |param, i| {
289 | if (i != 0) try writer.writeAll(", ");
290 | if (param.name) |name| {
291 | try param.type.emitBindingParam(writer, name);
292 | } else {
293 | var buf: [Function.max_param_char_count]u8 = undefined;
294 | const name = try std.fmt.bufPrint(&buf, "v{d}", .{i});
295 | try param.type.emitBindingParam(writer, name);
296 | }
297 | }
298 |
299 | try writer.writeAll(") {\n");
300 |
301 | for (fun.params, 0..) |param, i| {
302 | _ = try writer.writeByteNTimes(' ', indent + 4);
303 | if (param.name) |name| {
304 | try param.type.emitBindingGet(writer, name, i);
305 | } else {
306 | var buf: [Function.max_param_char_count]u8 = undefined;
307 | const name = try std.fmt.bufPrint(&buf, "v{d}", .{i});
308 | try param.type.emitBindingGet(writer, name, i);
309 | }
310 | }
311 |
312 | _ = try writer.writeByteNTimes(' ', indent + 4);
313 |
314 | const is_ret_obj = fun.return_ty.info == .composite_ref;
315 | const is_const = fun.val_ty == .constructor;
316 | const is_method = fun.val_ty == .method;
317 |
318 | switch (fun.return_ty.info) {
319 | .void => {},
320 | .container_value => try writer.writeAll("const ret_val = "),
321 | .composite_ref => try writer.writeAll("return wasmWrapObject("),
322 | else => try writer.writeAll("return "),
323 | }
324 |
325 | if (is_const)
326 | try writer.writeAll("new ");
327 |
328 | var arg_index: u32 = 0;
329 | if (is_method) {
330 | try writer.writeAll("l0.");
331 | arg_index = 1;
332 | } else {
333 | // If its a constructor, remove the last '_'
334 | var end = namespace.items.len;
335 | end -= if (is_const) 1 else 0;
336 |
337 | // Replace all '_' with '.' for namespace
338 | std.mem.replaceScalar(u8, namespace.items, '_', '.');
339 | try writer.writeAll(namespace.items[0..end]);
340 | }
341 |
342 | try writer.print("{s}(", .{if (!is_const) fun.name else ""});
343 |
344 | for (arg_index..fun.params.len) |i| {
345 | if (i != arg_index) try writer.writeAll(", ");
346 | try writer.print("l{d}", .{i});
347 | }
348 |
349 | if (is_ret_obj) try writer.writeByte(')');
350 | try writer.writeAll(");\n}\n");
351 | }
352 | };
353 |
354 | pub const Type = struct {
355 | slice: []const u8,
356 | info: TypeInfo,
357 |
358 | pub const TypeInfo = union(enum) {
359 | int: Int,
360 | float: Float,
361 | ptr: Ptr,
362 | void: void,
363 | bool: void,
364 | anyopaque: void,
365 | name_ref: void,
366 | composite_ref: void,
367 | container_value: *Container,
368 | };
369 |
370 | pub const Int = struct {
371 | bits: u16,
372 | signedness: Signedness,
373 |
374 | pub const Signedness = enum { signed, unsigned };
375 | };
376 |
377 | pub const Float = struct {
378 | bits: u16,
379 | };
380 |
381 | pub const Ptr = struct {
382 | size: std.builtin.Type.Pointer.Size,
383 | is_const: bool,
384 | base_ty: *Type,
385 | };
386 |
387 | pub const Composite = struct {
388 | fields: []Field,
389 |
390 | pub const Field = struct {
391 | name: []const u8,
392 | type: Type,
393 | };
394 | };
395 |
396 | pub fn getJsSize(ty: Type) usize {
397 | return ty.getJsBitSize() / 8;
398 | }
399 |
400 | pub fn getJsBitSize(ty: Type) usize {
401 | return switch (ty.info) {
402 | .int => |int| std.math.powi(usize, 2, std.math.log2_int_ceil(usize, int.bits)) catch unreachable,
403 | .float => |float| std.math.clamp(float.bits, 32, 64),
404 | .ptr => 64,
405 | .bool => 8,
406 | .composite_ref => 32,
407 | else => 0,
408 | };
409 | }
410 |
411 | fn printParamName(writer: anytype, param_name: ?[]const u8, extension: ?[]const u8) !void {
412 | if (param_name) |param| {
413 | try writer.writeAll(param);
414 | if (extension) |ext| try writer.writeAll(ext);
415 | try writer.writeAll(": ");
416 | }
417 | }
418 | // Emits parameters for functions in zig's format.
419 | pub fn emitParam(ty: Type, writer: anytype, param_name: ?[]const u8) !void {
420 | try printParamName(writer, param_name, null);
421 |
422 | switch (ty.info) {
423 | .container_value => try writer.print("C{s}", .{ty.slice}),
424 | else => try writer.writeAll(ty.slice),
425 | }
426 | }
427 |
428 | // Emits parameters for functions, but in their 'extern' form.
429 | // e.g. expanding `[]const u8` to a pointer and length or such.
430 | pub fn emitExternParam(ty: Type, writer: anytype, param_name: ?[]const u8) !void {
431 | try printParamName(writer, param_name, null);
432 |
433 | switch (ty.info) {
434 | .int => |int| try writer.print("{c}{d}", .{ @tagName(int.signedness)[0], ty.getJsBitSize() }),
435 | .float => try writer.print("f{d}", .{ty.getJsBitSize()}),
436 | .ptr => |ptr| {
437 | switch (ptr.size) {
438 | .Slice => {
439 | try std.fmt.format(writer, "[*]{s}", .{if (ptr.is_const) "const " else ""});
440 | try ptr.base_ty.*.emitExternParam(writer, null);
441 | try writer.writeAll(", ");
442 | try printParamName(writer, param_name, "_len");
443 | try writer.writeAll("u32");
444 | },
445 | else => {}, // TODO: one, many
446 | }
447 | },
448 | .composite_ref => try writer.writeAll("u32"),
449 | .container_value => try writer.print("*const C{s}", .{ty.slice}),
450 |
451 | else => try writer.writeAll(ty.slice),
452 | }
453 | }
454 |
455 | // Emits arguments for function calls, but in their 'extern' form.
456 | // e.g. expanding `[]const u8` to a pointer and length or such.
457 | pub fn emitExternArg(ty: Type, writer: anytype, arg_name: []const u8) !void {
458 | switch (ty.info) {
459 | .ptr => |ptr| switch (ptr.size) {
460 | .Slice => try std.fmt.format(writer, "{s}.ptr, {s}.len", .{ arg_name, arg_name }),
461 | else => {}, // TODO: one, many
462 | },
463 | .composite_ref => try writer.print("{s}.id", .{arg_name}),
464 | .container_value => try writer.print("&{s}", .{arg_name}),
465 | else => try writer.writeAll(arg_name),
466 | }
467 | }
468 |
469 | pub fn emitExternField(ty: Type, writer: anytype, field_name: []const u8, struct_name: []const u8) !void {
470 | switch (ty.info) {
471 | .ptr => |ptr| switch (ptr.size) {
472 | .Slice => try writer.print(
473 | ".{s} = {s}.{s}.ptr, .{s}_len = {s}.{s}.len,\n",
474 | .{ field_name, struct_name, field_name, field_name, struct_name, field_name },
475 | ),
476 | else => {}, // TODO: one, many
477 | },
478 | .container_value => try writer.print(
479 | ".{s} = {s}.{s}.toExtern(),\n",
480 | .{ field_name, struct_name, field_name },
481 | ),
482 | .composite_ref => try writer.print(
483 | ".{s} = {s}.{s}.id,\n",
484 | .{ field_name, struct_name, field_name },
485 | ),
486 | else => try writer.print(".{s} = {s}.{s},\n", .{ field_name, struct_name, field_name }),
487 | }
488 | }
489 |
490 | pub fn emitBindingParam(ty: Type, writer: anytype, param_name: []const u8) !void {
491 | switch (ty.info) {
492 | .ptr => |ptr| switch (ptr.size) {
493 | .Slice => try std.fmt.format(writer, "{s}, {s}_len", .{ param_name, param_name }),
494 | else => {}, // TODO: one, many
495 | },
496 | else => try writer.writeAll(param_name),
497 | }
498 | }
499 |
500 | pub fn emitBindingGet(ty: Type, writer: anytype, param_name: []const u8, index: usize) !void {
501 | try writer.print("const l{d} = ", .{index});
502 | switch (ty.info) {
503 | .ptr => |ptr| switch (ptr.size) {
504 | .Slice => try writer.print("wasmGetSlice({s}, {s}_len)", .{ param_name, param_name }),
505 | else => {}, // TODO: one, many
506 | },
507 | .composite_ref => {
508 | try writer.print("wasmGetObject({s})", .{param_name});
509 | },
510 | .container_value => |container| _ = try ty.containerBinding(writer, container, param_name),
511 | else => try writer.print("{s}", .{param_name}),
512 | }
513 |
514 | try writer.writeAll(";\n");
515 | }
516 |
517 | fn containerBinding(ty: Type, writer: anytype, container: *Container, param_name: []const u8) !usize {
518 | try writer.writeAll("{\n");
519 |
520 | const initial_size = container.fields.items[0].type.getJsSize();
521 | var offset: usize = 0;
522 | for (container.fields.items) |field| {
523 | try writer.print("'{s}': ", .{field.name});
524 | switch (field.type.info) {
525 | .int => |int| try writer.print(
526 | "wasmGetDataView().get{s}{d}({s} + {d}, true)",
527 | .{
528 | // TODO: u64/i64 support
529 | if (int.signedness == .unsigned) "Uint" else "Int",
530 | field.type.getJsBitSize(),
531 | param_name,
532 | offset,
533 | },
534 | ),
535 | .float => try writer.print(
536 | "wasmGetDataView().getFloat{d}({s} + {d}, true)",
537 | .{ field.type.getJsBitSize(), param_name, offset },
538 | ),
539 | .ptr => |ptr| {
540 | switch (ptr.size) {
541 | .Slice => try writer.print(
542 | "wasmGetSlice({s} + {d}, wasmGetDataView().getUint32({s} + {d} + 32, true))",
543 | .{ param_name, offset, param_name, offset },
544 | ),
545 | else => {},
546 | }
547 | },
548 | .bool => try writer.print(
549 | "Boolean(wasmGetDataView().getUint8({s} + {d}, true))",
550 | .{ param_name, offset },
551 | ),
552 | .container_value => |child_container| {
553 | var buf: [256]u8 = undefined;
554 | const buf_offset = try std.fmt.bufPrint(&buf, "{s} + {d}", .{ param_name, offset });
555 |
556 | offset += try ty.containerBinding(writer, child_container, buf_offset);
557 | },
558 | .composite_ref => {
559 | try writer.print("wasmGetObject(wasmGetDataView().getUint32({s} + {d}, true))", .{ param_name, offset });
560 | },
561 | else => {},
562 | }
563 | try writer.writeAll(",\n");
564 |
565 | offset += @max(initial_size, field.type.getJsSize());
566 | }
567 | try writer.writeAll("}");
568 |
569 | return offset;
570 | }
571 | };
572 |
--------------------------------------------------------------------------------