From 3e00c224007db19cdc7869435967c5decea570a5 Mon Sep 17 00:00:00 2001 From: Anhgelus Morhtuuzh Date: Fri, 1 May 2026 18:33:16 +0200 Subject: feat(lib): introduce document notion in C ABI --- build.zig | 58 +++++++++++++++++++++++++++++++++---------------------- examples/main.c | 22 +++++++++++++++------ include/typdown.h | 5 ++++- src/eval/math.zig | 2 +- src/root.zig | 30 +++++++++++++++++++++++----- 5 files changed, 81 insertions(+), 36 deletions(-) diff --git a/build.zig b/build.zig index 0af4927..cf4493c 100644 --- a/build.zig +++ b/build.zig @@ -8,6 +8,10 @@ pub fn build(b: *std.Build) void { const target = b.standardTargetOptions(.{}); const optimize = b.standardOptimizeOption(.{}); + const short = b.option(bool, "short", "skip long tests") orelse false; + const options = b.addOptions(); + options.addOption(bool, "short", short); + const install = b.getInstallStep(); // build typst module @@ -17,44 +21,58 @@ pub fn build(b: *std.Build) void { build_typst.setCwd(b.path(TYPST)); if (optimize != .Debug) build_typst.addArg("--release"); + const typst = b.addTranslateC(.{ + .root_source_file = b.path(TYPST ++ "/typdown_typst.h"), + .link_libc = true, + .target = target, + .optimize = optimize, + }); + const mod = b.addModule("typdown", .{ .root_source_file = b.path("src/root.zig"), .target = target, .optimize = optimize, + .imports = &.{ + .{ .name = "typst", .module = typst.createModule() }, + }, }); if (!target.result.isWasiLibC()) mod.link_libc = true; if (optimize != .Debug) mod.strip = true; + mod.addOptions("config", options); // find typst module - mod.addIncludePath(b.path(TYPST)); + mod.linkSystemLibrary("typdown_typst", .{ .preferred_link_mode = .static }); mod.addLibraryPath(if (optimize == .Debug) b.path(TYPST_DEBUG) else b.path(TYPST_RELEASE)); const lib = b.addLibrary(.{ .name = "typdown", - .linkage = .static, + .linkage = .dynamic, .root_module = mod, .use_llvm = true, // zig internal backend crashes during linking (for 0.15.2) }); - // link typst module - lib.linkSystemLibrary("typdown_typst"); const installed_lib = b.addInstallArtifact(lib, .{}); installed_lib.step.dependOn(&build_typst.step); // when emitting headers will be fixed //installed_lib.emitted_h = lib.getEmittedH(); - const example = b.addExecutable(.{ - .name = "example", - .root_module = b.createModule(.{ - .target = target, - .optimize = optimize, - .link_libc = true, - }), + const example_mod = b.createModule(.{ + .target = target, + .optimize = optimize, + .link_libc = true, }); - example.root_module.addCSourceFile(.{ + example_mod.addCSourceFile(.{ .file = b.path("examples/main.c"), }); - example.root_module.linkLibrary(lib); - example.root_module.addIncludePath(b.path("include")); + example_mod.linkLibrary(lib); + example_mod.addIncludePath(b.path("include")); + example_mod.linkSystemLibrary("typdown_typst", .{ .preferred_link_mode = .static }); + example_mod.addLibraryPath(if (optimize == .Debug) b.path(TYPST_DEBUG) else b.path(TYPST_RELEASE)); + + const example = b.addExecutable(.{ + .name = "example", + .root_module = example_mod, + }); + example.step.dependOn(install); install.dependOn(&installed_lib.step); @@ -68,24 +86,18 @@ pub fn build(b: *std.Build) void { install.dependOn(&fmt.step); const test_step = b.step("test", "Run tests"); - const mod_tests = b.addTest(.{ + const exe_tests = b.addTest(.{ .root_module = mod, .use_llvm = true, // zig internal backend crashes during linking (for 0.15.2) }); + exe_tests.step.dependOn(install); - const options = b.addOptions(); - const short = b.option(bool, "short", "skip long tests") orelse false; - options.addOption(bool, "short", short); - mod_tests.root_module.addOptions("config", options); - - const run_mod_tests = b.addRunArtifact(mod_tests); + const run_mod_tests = b.addRunArtifact(exe_tests); generateSVG(b, &run_mod_tests.step) catch |err| run_mod_tests.step.addError("{}\n", .{err}) catch unreachable; - run_mod_tests.step.dependOn(install); test_step.dependOn(&run_mod_tests.step); const examples_step = b.step("examples", "Run examples"); const example_run = b.addRunArtifact(example); - example_run.step.dependOn(install); examples_step.dependOn(&example_run.step); const check = b.step("check", "Check if foo compiles"); diff --git a/examples/main.c b/examples/main.c index 5dd3a4b..18393c6 100644 --- a/examples/main.c +++ b/examples/main.c @@ -1,15 +1,25 @@ #include #include #include -#include "typdown.h" +#include void foo(char *v) { uint8_t code; - char *res = typdown_parse(v, &code); - if (code == 0) { - printf("%s\n", res); - free(res); - } else printf("cannot parse '%s', error: %s (%d)\n", v, typdown_getErrorString(code), code); + void *doc = typdown_parse(v, &code); + if (code != 0) { + printf("cannot parse '%s', error: %s (%d)\n", v, typdown_getErrorString(code), code); + typdown_free(doc); + return; + } + char *res = typdown_renderHTML(doc, &code); + if (code != 0) { + printf("cannot render '%s', error: %s (%d)\n", v, typdown_getErrorString(code), code); + typdown_free(doc); + return; + } + printf("%s\n", res); + free(res); + typdown_free(doc); } int main() { diff --git a/include/typdown.h b/include/typdown.h index 51821a0..8de37d8 100644 --- a/include/typdown.h +++ b/include/typdown.h @@ -2,4 +2,7 @@ #include char * typdown_getErrorString(uint8_t); -char * typdown_parse(char *, uint8_t *); + +void * typdown_parse(char *, uint8_t *); +void typdown_free(void *); +char * typdown_renderHTML(void *, uint8_t *); diff --git a/src/eval/math.zig b/src/eval/math.zig index fa95918..071287e 100644 --- a/src/eval/math.zig +++ b/src/eval/math.zig @@ -1,5 +1,5 @@ const std = @import("std"); -const typst = @cImport(@cInclude("typdown_typst.h")); +const typst = @import("typst"); const Allocator = std.mem.Allocator; const HTML = Element.HTML; const Element = @import("Element.zig"); diff --git a/src/root.zig b/src/root.zig index f7899a7..3a7c134 100644 --- a/src/root.zig +++ b/src/root.zig @@ -54,16 +54,28 @@ var default_alloc: std.mem.Allocator = /// Parse the content. /// Code is a pointer to an u8 populated with an error code > 0. /// -/// Returns a not null strings and set the code to 0 if everything is fine. +/// Returns a non-null void pointer containing the document and set the code to 0 if everything is fine. /// Else, it returns null and set an error code above 0. /// Use typdown_getErrorString to retrieve the string linked with the error code. /// Use parse if you are in Zig. -export fn typdown_parse(content: [*:0]const u8, code: *u8) ?[*:0]const u8 { - const doc = parse(default_alloc, std.mem.span(content)) catch |err| { +/// +/// You must free the document with typdown_free. +export fn typdown_parse(content: [*:0]const u8, code: *u8) ?*anyopaque { + return parse(default_alloc, std.mem.span(content)) catch |err| { code.* = getErrorCode(err); return null; }; - defer doc.deinit(); +} + +/// Free the document. +export fn typdown_free(document: *anyopaque) void { + const doc: *Document = @ptrCast(@alignCast(document)); + doc.deinit(); +} + +/// Render an HTML from the document. +export fn typdown_renderHTML(document: *anyopaque, code: *u8) ?[*:0]const u8 { + const doc: *Document = @ptrCast(@alignCast(document)); const res = doc.renderHTML(default_alloc) catch |err| { code.* = getErrorCode(err); return null; @@ -88,7 +100,15 @@ fn doTest(content: [*:0]const u8, exp: []const u8, comptime exp_code: u8) !void const expect = std.testing.expect; var code: u8 = undefined; - const raw = typdown_parse(content, &code) orelse { + const doc = typdown_parse(content, &code) orelse { + expect(code == exp_code) catch |err| { + std.debug.print("{}\n", .{code}); + return err; + }; + return; + }; + defer typdown_free(doc); + const raw = typdown_renderHTML(doc, &code) orelse { expect(code == exp_code) catch |err| { std.debug.print("{}\n", .{code}); return err; -- cgit v1.2.3