├── .gitattributes ├── .github ├── FUNDING.yml └── pull_request_template.md ├── .gitignore ├── LICENSE ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── build.zig ├── example ├── example.js ├── main.zig ├── sysjs.zig ├── sysjs_generated.js └── sysjs_generated.zig ├── src ├── IrGen.zig ├── analysis.zig ├── main.zig └── types.zig └── www └── index.html /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | upstream/** linguist-vendored 3 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: slimsag 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 | -------------------------------------------------------------------------------- /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. -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This project has moved to https://github.com/hexops/mach/tree/main/src/sysjs 2 | -------------------------------------------------------------------------------- /build.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const sysjs = @import("src/main.zig"); 3 | 4 | pub fn build(b: *std.Build) !void { 5 | const optimize = b.standardOptimizeOption(.{}); 6 | 7 | _ = b.addModule("mach-sysjs", .{ 8 | .root_source_file = .{ .path = "src/main.zig" }, 9 | }); 10 | 11 | // Use sysjs to generate bindings 12 | try sysjs.generateFiles( 13 | b.allocator, 14 | "example/sysjs.zig", 15 | "example/sysjs_generated.zig", 16 | "example/sysjs_generated.js", 17 | ); 18 | 19 | const app = try App.init(b, .{ 20 | .name = "example", 21 | .src = "example/main.zig", 22 | .optimize = optimize, 23 | .deps = &.{ 24 | .{ 25 | .name = "sysjs", 26 | .generated_js_file = "example/sysjs_generated.js", 27 | .generated_zig_file = "example/sysjs_generated.zig", 28 | }, 29 | }, 30 | }); 31 | 32 | var example_compile_step = b.step("example", "Compile example"); 33 | example_compile_step.dependOn(&app.install.step); 34 | } 35 | 36 | pub const index_html = @embedFile("www/index.html"); 37 | 38 | pub const Dependency = struct { 39 | name: []const u8, 40 | generated_js_file: []const u8, 41 | generated_zig_file: []const u8, 42 | }; 43 | 44 | pub const App = struct { 45 | b: *std.Build, 46 | name: []const u8, 47 | compile: *std.Build.Step.Compile, 48 | install: *std.Build.Step.InstallArtifact, 49 | 50 | pub fn init(b: *std.Build, options: struct { 51 | name: []const u8, 52 | src: []const u8, 53 | optimize: std.builtin.OptimizeMode, 54 | deps: []const Dependency = &.{}, 55 | }) !App { 56 | b.lib_dir = b.fmt("{s}/www", .{b.install_path}); 57 | 58 | const wasm32_freestanding = std.zig.CrossTarget{ .cpu_arch = .wasm32, .os_tag = .freestanding }; 59 | const lib = b.addExecutable(.{ 60 | .name = options.name, 61 | .root_source_file = .{ .path = options.src }, 62 | .optimize = options.optimize, 63 | .target = b.resolveTargetQuery(wasm32_freestanding), 64 | }); 65 | lib.entry = .disabled; 66 | lib.rdynamic = true; 67 | 68 | b.installArtifact(lib); 69 | const install = b.addInstallArtifact(lib, .{}); 70 | 71 | var inits = std.ArrayList(u8).init(b.allocator); 72 | var imports = std.ArrayList(u8).init(b.allocator); 73 | const import_writer = imports.writer(); 74 | for (options.deps) |dep| { 75 | try import_writer.print( 76 | "import * as {s} from './{s}';\n", 77 | .{ dep.name, std.fs.path.basename(dep.generated_js_file) }, 78 | ); 79 | 80 | try inits.writer().print("{s}.init(wasm);\n", .{dep.name}); 81 | 82 | const module = b.createModule(.{ 83 | .root_source_file = .{ .path = b.pathFromRoot(dep.generated_zig_file) }, 84 | }); 85 | lib.root_module.addImport(dep.name, module); 86 | 87 | const install_js = b.addInstallFile( 88 | .{ .path = dep.generated_js_file }, 89 | try std.fs.path.join(b.allocator, &.{ "www/", std.fs.path.basename(dep.generated_js_file) }), 90 | ); 91 | install.step.dependOn(&install_js.step); 92 | } 93 | 94 | try import_writer.writeAll("let imports = {\nenv: {\n"); 95 | for (options.deps) |dep| { 96 | try import_writer.print("...{s},\n", .{dep.name}); 97 | } 98 | try import_writer.writeAll("}\n};\n"); 99 | 100 | // TODO: do the format at runtime 101 | const index_formatted = try std.fmt.allocPrint(b.allocator, index_html, .{ 102 | .app_name = options.name, 103 | .wasm_path = options.name, 104 | .imports = imports.items, 105 | .inits = inits.items, 106 | }); 107 | 108 | const write_index_html = b.addWriteFile(b.getInstallPath(.lib, "index.html"), index_formatted); 109 | install.step.dependOn(&write_index_html.step); 110 | 111 | return .{ 112 | .b = b, 113 | .name = options.name, 114 | .compile = lib, 115 | .install = install, 116 | }; 117 | } 118 | }; 119 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /www/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {[app_name]s} 6 | 7 | 8 | 9 | 10 | 23 | 24 | 25 | --------------------------------------------------------------------------------