diff --git a/build.zig b/build.zig
index 626ffc0..ab971a0 100644
--- a/build.zig
+++ b/build.zig
@@ -41,6 +41,7 @@ pub fn build(b: *std.Build) void {
// Targets:
const run_step = b.step("run", "Run the app");
const test_step = b.step("test", "Run unit tests");
+ const wasm_target = b.resolveTargetQuery(.{ .cpu_arch = .wasm32, .os_tag = .freestanding });
// Build:
const hyperdoc = b.addModule("hyperdoc", .{
@@ -60,6 +61,20 @@ pub fn build(b: *std.Build) void {
});
b.installArtifact(exe);
+ const wasm_exe = b.addExecutable(.{
+ .name = "hyperdoc_wasm",
+ .root_module = b.createModule(.{
+ .root_source_file = b.path("src/wasm.zig"),
+ .target = wasm_target,
+ .optimize = optimize,
+ .single_threaded = true,
+ .imports = &.{
+ .{ .name = "hyperdoc", .module = hyperdoc },
+ },
+ }),
+ });
+ b.installArtifact(wasm_exe);
+
const run_cmd = b.addRunArtifact(exe);
run_cmd.step.dependOn(b.getInstallStep());
if (b.args) |arg| {
diff --git a/src/playground.html b/src/playground.html
new file mode 100644
index 0000000..6336eed
--- /dev/null
+++ b/src/playground.html
@@ -0,0 +1,284 @@
+
+
+
+
+ HyperDoc Playground
+
+
+
+
+
+
+
+
diff --git a/src/wasm.zig b/src/wasm.zig
new file mode 100644
index 0000000..852768a
--- /dev/null
+++ b/src/wasm.zig
@@ -0,0 +1,221 @@
+const std = @import("std");
+const hyperdoc = @import("hyperdoc");
+
+const LogLevel = enum(u8) { err, warn, info, debug };
+
+extern fn reset_log() void;
+extern fn append_log(ptr: [*]const u8, len: usize) void;
+extern fn flush_log(level: LogLevel) void;
+
+const LogWriter = struct {
+ fn appendWrite(self: LogWriter, chunk: []const u8) error{OutOfMemory}!usize {
+ _ = self;
+ append_log(chunk.ptr, chunk.len);
+ return chunk.len;
+ }
+
+ fn writer(self: LogWriter) std.io.GenericWriter(LogWriter, error{OutOfMemory}, appendWrite) {
+ return .{ .context = self };
+ }
+};
+
+fn log_to_host(
+ comptime level: std.log.Level,
+ comptime _scope: @TypeOf(.enum_literal),
+ comptime format: []const u8,
+ args: anytype,
+) void {
+ _ = _scope;
+
+ reset_log();
+
+ const log_writer = LogWriter{};
+ const writer = log_writer.writer();
+ _ = std.fmt.format(writer, format, args) catch {};
+
+ const mapped: LogLevel = switch (level) {
+ .err => .err,
+ .warn => .warn,
+ .info => .info,
+ .debug => .debug,
+ };
+
+ flush_log(mapped);
+}
+
+fn fixedPageSize() usize {
+ return 4096;
+}
+
+fn zeroRandom(buffer: []u8) void {
+ @memset(buffer, 0);
+}
+
+pub const std_options: std.Options = .{
+ .enable_segfault_handler = false,
+ .logFn = log_to_host,
+ .queryPageSize = fixedPageSize,
+ .cryptoRandomSeed = zeroRandom,
+};
+
+const allocator = std.heap.wasm_allocator;
+
+pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace, ret_addr: ?usize) noreturn {
+ _ = message;
+ _ = stack_trace;
+ _ = ret_addr;
+ @breakpoint();
+ unreachable;
+}
+
+pub fn main() !void {}
+
+const DiagnosticView = struct {
+ line: u32,
+ column: u32,
+ message: []u8,
+};
+
+var document_buffer: std.array_list.Managed(u8) = std.array_list.Managed(u8).init(allocator);
+var html_buffer: std.array_list.Managed(u8) = std.array_list.Managed(u8).init(allocator);
+var diagnostic_views: std.array_list.Managed(DiagnosticView) = std.array_list.Managed(DiagnosticView).init(allocator);
+var diagnostic_text: std.array_list.Managed(u8) = std.array_list.Managed(u8).init(allocator);
+
+const CountingWriter = struct {
+ count: usize = 0,
+
+ fn write(self: *CountingWriter, bytes: []const u8) error{}!usize {
+ self.count += bytes.len;
+ return bytes.len;
+ }
+
+ fn generic(self: *CountingWriter) std.Io.GenericWriter(*CountingWriter, error{}, write) {
+ return .{ .context = self };
+ }
+};
+
+fn capture_diagnostics(source: *hyperdoc.Diagnostics) !void {
+ diagnostic_views.clearRetainingCapacity();
+ diagnostic_text.clearRetainingCapacity();
+
+ if (source.items.items.len == 0) return;
+
+ var total: usize = 0;
+ for (source.items.items) |diag| {
+ var cw: CountingWriter = .{};
+ _ = diag.code.format(cw.generic()) catch {};
+ total += cw.count;
+ }
+
+ diagnostic_text.ensureTotalCapacityPrecise(total) catch return;
+
+ var diag_writer = diagnostic_text.writer();
+ var adapter_buffer: [256]u8 = undefined;
+ var adapter = diag_writer.any().adaptToNewApi(&adapter_buffer);
+
+ for (source.items.items) |diag| {
+ const start = diagnostic_text.items.len;
+ diag.code.format(&adapter.new_interface) catch {
+ adapter.err = error.WriteFailed;
+ };
+ if (adapter.err) |_| return;
+
+ const rendered = diagnostic_text.items[start..];
+ try diagnostic_views.append(.{
+ .line = diag.location.line,
+ .column = diag.location.column,
+ .message = rendered,
+ });
+ }
+}
+
+export fn hdoc_set_document_len(len: usize) bool {
+ document_buffer.clearRetainingCapacity();
+ document_buffer.items.len = 0;
+
+ if (len == 0) return true;
+
+ document_buffer.ensureTotalCapacityPrecise(len) catch return false;
+ document_buffer.items.len = len;
+ return true;
+}
+
+export fn hdoc_document_ptr() [*]u8 {
+ return document_buffer.items.ptr;
+}
+
+export fn hdoc_process() bool {
+ html_buffer.clearRetainingCapacity();
+ diagnostic_views.clearRetainingCapacity();
+ diagnostic_text.clearRetainingCapacity();
+
+ const source: []const u8 = document_buffer.items;
+
+ var diagnostics = hyperdoc.Diagnostics.init(allocator);
+ defer diagnostics.deinit();
+
+ var parsed = hyperdoc.parse(allocator, source, &diagnostics) catch {
+ capture_diagnostics(&diagnostics) catch {};
+ return false;
+ };
+ defer parsed.deinit();
+
+ if (diagnostics.has_error()) {
+ capture_diagnostics(&diagnostics) catch {};
+ return false;
+ }
+
+ var html_writer = html_buffer.writer();
+ var html_adapter_buffer: [256]u8 = undefined;
+ var html_adapter = html_writer.any().adaptToNewApi(&html_adapter_buffer);
+
+ hyperdoc.render.html5(parsed, &html_adapter.new_interface) catch {
+ html_adapter.err = error.WriteFailed;
+ };
+ if (html_adapter.err) |_| {
+ capture_diagnostics(&diagnostics) catch {};
+ return false;
+ }
+
+ capture_diagnostics(&diagnostics) catch {};
+ return true;
+}
+
+export fn hdoc_html_ptr() ?[*]const u8 {
+ if (html_buffer.items.len == 0) return null;
+ return html_buffer.items.ptr;
+}
+
+export fn hdoc_html_len() usize {
+ return html_buffer.items.len;
+}
+
+export fn hdoc_diagnostic_count() usize {
+ return diagnostic_views.items.len;
+}
+
+export fn hdoc_diagnostic_line(index: usize) u32 {
+ if (index >= diagnostic_views.items.len) return 0;
+
+ return diagnostic_views.items[index].line;
+}
+
+export fn hdoc_diagnostic_column(index: usize) u32 {
+ if (index >= diagnostic_views.items.len) return 0;
+
+ return diagnostic_views.items[index].column;
+}
+
+export fn hdoc_diagnostic_message_ptr(index: usize) ?[*]const u8 {
+ if (index >= diagnostic_views.items.len) return null;
+
+ if (diagnostic_views.items[index].message.len == 0) return null;
+
+ return diagnostic_views.items[index].message.ptr;
+}
+
+export fn hdoc_diagnostic_message_len(index: usize) usize {
+ if (index >= diagnostic_views.items.len) return 0;
+
+ return diagnostic_views.items[index].message.len;
+}