feat: part of the story api exposed via c bindings
This commit is contained in:
parent
a065e5bf46
commit
3afbbb6ec2
6 changed files with 748 additions and 16 deletions
158
src/c_api.zig
Normal file
158
src/c_api.zig
Normal file
|
|
@ -0,0 +1,158 @@
|
|||
const std = @import("std");
|
||||
const ink = @import("ink");
|
||||
const Story = ink.Story;
|
||||
const Module = ink.Module;
|
||||
|
||||
var global_allocator: std.heap.DebugAllocator(.{}) = .init;
|
||||
var stdout_buffer: [4096]u8 align(std.heap.page_size_min) = undefined;
|
||||
|
||||
pub export fn ink_open() callconv(.c) ?*Story {
|
||||
const gpa = global_allocator.allocator();
|
||||
const story = gpa.create(Story) catch |err| switch (err) {
|
||||
error.OutOfMemory => return null,
|
||||
};
|
||||
story.* = .{
|
||||
.gpa = gpa,
|
||||
.arena = .init(gpa),
|
||||
.is_exited = false,
|
||||
.can_advance = false,
|
||||
.stack_top = 0,
|
||||
.call_stack_top = 0,
|
||||
.output_marker = 0,
|
||||
.choice_selected = null,
|
||||
.output_buffer = .empty,
|
||||
.output_scratch = .empty,
|
||||
.current_choices = .empty,
|
||||
.variable_observers = .empty,
|
||||
.globals = .empty,
|
||||
.stack = &.{},
|
||||
.call_stack = &.{},
|
||||
.code_chunks = .empty,
|
||||
.gc_objects = .{},
|
||||
.constants_pool = &.{},
|
||||
.string_bytes = &.{},
|
||||
.dump_writer = null,
|
||||
};
|
||||
return story;
|
||||
}
|
||||
|
||||
pub export fn ink_close(story: *Story) callconv(.c) void {
|
||||
defer _ = global_allocator.deinit();
|
||||
const gpa = story.gpa;
|
||||
story.deinit();
|
||||
gpa.destroy(story);
|
||||
}
|
||||
|
||||
pub const InkLoadOpts = extern struct {
|
||||
filename: [*]const u8,
|
||||
filename_length: usize,
|
||||
source_bytes: [*]const u8,
|
||||
source_length: usize,
|
||||
flags: i32,
|
||||
};
|
||||
|
||||
fn loadStory(story: *Story, options: *const InkLoadOpts) !void {
|
||||
const gpa = story.gpa;
|
||||
var arena_allocator = std.heap.ArenaAllocator.init(gpa);
|
||||
defer arena_allocator.deinit();
|
||||
|
||||
const arena = arena_allocator.allocator();
|
||||
const stderr = std.fs.File.stderr();
|
||||
var stderr_writer = stderr.writer(&stdout_buffer);
|
||||
|
||||
var comp = try Module.compile(gpa, arena, .{
|
||||
.source_bytes = options.source_bytes[0..options.source_length :0],
|
||||
.filename = options.filename[0..options.filename_length],
|
||||
.dump_writer = null,
|
||||
.dump_use_color = false,
|
||||
.dump_ast = false,
|
||||
.dump_ir = false,
|
||||
});
|
||||
defer comp.deinit();
|
||||
|
||||
if (comp.errors.items.len > 0) {
|
||||
for (comp.errors.items) |err| {
|
||||
try comp.renderError(&stderr_writer.interface, err);
|
||||
}
|
||||
return error.LoadFailed;
|
||||
}
|
||||
|
||||
// TODO: Make this configureable.
|
||||
const stack_size = 128;
|
||||
const eval_stack_ptr = try gpa.alloc(Story.Value, stack_size);
|
||||
errdefer gpa.free(eval_stack_ptr);
|
||||
|
||||
const call_stack_ptr = try gpa.alloc(Story.CallFrame, stack_size);
|
||||
errdefer gpa.free(call_stack_ptr);
|
||||
|
||||
story.can_advance = false;
|
||||
story.stack = eval_stack_ptr;
|
||||
story.call_stack = call_stack_ptr;
|
||||
errdefer story.deinit();
|
||||
|
||||
try comp.setupStoryRuntime(gpa, story);
|
||||
if (story.getKnot(Story.default_knot_name)) |knot| {
|
||||
try story.pushStack(.{ .object = &knot.base });
|
||||
try story.divert(knot, 0);
|
||||
}
|
||||
}
|
||||
|
||||
pub export fn ink_load_story_options(
|
||||
story: *Story,
|
||||
options: *const InkLoadOpts,
|
||||
) callconv(.c) c_int {
|
||||
loadStory(story, options) catch |err| {
|
||||
std.debug.print("{any}\n", .{@errorName(err)});
|
||||
return -1;
|
||||
};
|
||||
return 0;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue