ink/src/c_api.zig

153 lines
4 KiB
Zig

const std = @import("std");
const ink = @import("ink");
const Story = ink.Story;
const Compilation = ink.Compilation;
const Ast = ink.Ast;
const AstGen = ink.AstGen;
const Ir = ink.Ir;
var global_allocator: std.heap.DebugAllocator(.{}) = .init;
var stdout_buffer: [4096]u8 align(std.heap.page_size_min) = undefined;
pub const InkLoadOpts = extern struct {
filename: [*]const u8,
filename_length: usize,
source_bytes: [*]const u8,
source_length: usize,
flags: i32,
};
const LoadStoryOptions = struct {
filename: []const u8,
source_bytes: [:0]const u8,
flags: i32,
stack_size: usize,
error_writer: *std.Io.Writer,
};
fn loadStory(gpa: std.mem.Allocator, options: LoadStoryOptions) !*Story {
var arena_allocator = std.heap.ArenaAllocator.init(gpa);
defer arena_allocator.deinit();
const arena = arena_allocator.allocator();
var tree = try Ast.parse(
gpa,
arena,
options.source_bytes,
options.filename,
@intCast(options.flags),
);
defer tree.deinit(gpa);
var ir = try AstGen.generate(gpa, &tree);
defer ir.deinit(gpa);
var errors: std.ArrayListUnmanaged(Compilation.Error) = .empty;
defer errors.deinit(gpa);
var cu = Compilation.build(gpa, tree, ir, &errors) catch |err| switch (err) {
else => |e| return e,
};
defer cu.deinit();
if (cu.hasCompileErrors()) {
for (cu.errors.items) |err| {
try cu.renderError(options.error_writer, err);
}
return error.Failed;
}
const story = try gpa.create(Story);
errdefer gpa.destroy(story);
story.* = .{
.gpa = gpa,
.arena = .init(gpa),
};
errdefer story.deinit();
try Story.Loader.fromCompilationCompat(gpa, &cu, story, .{
.stack_size = options.stack_size,
});
return story;
}
pub export fn ink_load_story_options(
options: *const InkLoadOpts,
) callconv(.c) ?*Story {
const gpa = global_allocator.allocator();
const source_bytes = options.source_bytes[0..options.source_length :0];
const filename = options.filename[0..options.filename_length :0];
const stack_size = 128;
const stderr = std.fs.File.stderr();
var stderr_writer = stderr.writer(&stdout_buffer);
return loadStory(gpa, .{
.filename = filename,
.source_bytes = source_bytes,
.flags = options.flags,
.error_writer = &stderr_writer.interface,
.stack_size = stack_size,
}) catch |err| {
std.debug.print("{any}\n", .{@errorName(err)});
return null;
};
}
pub export fn ink_close(optional_story: ?*Story) callconv(.c) void {
defer _ = global_allocator.deinit();
if (optional_story) |story| {
const gpa = story.gpa;
story.deinit();
gpa.destroy(story);
}
}
pub export fn ink_story_can_continue(story: *Story) callconv(.c) bool {
return !story.is_exited and story.can_advance;
}
pub export fn ink_story_continue(
story: *Story,
line: *?[*]const u8,
linelen: *usize,
) callconv(.c) c_int {
const result = story.advance() catch return -1;
if (result) |slice| {
line.* = slice.ptr;
linelen.* = slice.len;
return 1;
} else {
line.* = null;
linelen.* = 0;
return 0;
}
}
// FIXME: The internal counter may be a bad idea...
pub export fn ink_story_choice_next(
story: *Story,
line: *?[*]const u8,
linelen: *usize,
) callconv(.c) c_int {
if (story.internal_counter < story.current_choices.items.len) {
const choice = &story.current_choices.items[story.internal_counter];
line.* = choice.content.ptr;
linelen.* = choice.content.len;
story.internal_counter += 1;
return 1;
} else {
line.* = null;
linelen.* = 0;
return 0;
}
}
// FIXME: The internal counter may be a bad idea...
pub export fn ink_story_choose(story: *Story, index: usize) callconv(.c) c_int {
story.selectChoiceIndex(index) catch {
return -1;
};
story.internal_counter = 0;
return 0;
}