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; }