refactor: make room for loading and outputting pre-compiled files
This commit is contained in:
parent
3afbbb6ec2
commit
96866ba9ae
11 changed files with 537 additions and 433 deletions
|
|
@ -230,7 +230,6 @@ int main(int argc, char *argv[])
|
||||||
int flags = 0;
|
int flags = 0;
|
||||||
const char *filename = NULL;
|
const char *filename = NULL;
|
||||||
struct ink_source source;
|
struct ink_source source;
|
||||||
struct ink_story *story = NULL;
|
|
||||||
|
|
||||||
option_setopts(cli_options, argv);
|
option_setopts(cli_options, argv);
|
||||||
|
|
||||||
|
|
@ -285,11 +284,6 @@ int main(int argc, char *argv[])
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
story = ink_open();
|
|
||||||
if (!story) {
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
|
|
||||||
const struct ink_story_options opts = {
|
const struct ink_story_options opts = {
|
||||||
.source_bytes = source.bytes,
|
.source_bytes = source.bytes,
|
||||||
.source_length = source.length,
|
.source_length = source.length,
|
||||||
|
|
@ -298,8 +292,8 @@ int main(int argc, char *argv[])
|
||||||
.flags = flags,
|
.flags = flags,
|
||||||
};
|
};
|
||||||
|
|
||||||
rc = ink_load_story_options(story, &opts);
|
struct ink_story *const story = ink_load_story_options(&opts);
|
||||||
if (rc < 0) {
|
if (story == NULL) {
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
if (!compile_only) {
|
if (!compile_only) {
|
||||||
|
|
|
||||||
|
|
@ -71,24 +71,17 @@ enum ink_flags {
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Open a story context.
|
* Load an Ink story with extended options.
|
||||||
*
|
*
|
||||||
* @returns a new story context
|
* @returns an opaque pointer on success or null on failure.
|
||||||
*/
|
*/
|
||||||
INK_API struct ink_story *ink_open(void);
|
INK_API struct ink_story *ink_load_story_options(const struct ink_load_options *options);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Close a story context.
|
* Close a story context.
|
||||||
*/
|
*/
|
||||||
INK_API void ink_close(struct ink_story *story);
|
INK_API void ink_close(struct ink_story *story);
|
||||||
|
|
||||||
/**
|
|
||||||
* Load an Ink story with extended options.
|
|
||||||
*
|
|
||||||
* @returns a non-zero value on error.
|
|
||||||
*/
|
|
||||||
INK_API int ink_load_story_options(struct ink_story *story,
|
|
||||||
const struct ink_load_options *options);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine if the story can continue.
|
* Determine if the story can continue.
|
||||||
|
|
|
||||||
57
src/Sema.zig
57
src/Sema.zig
|
|
@ -4,16 +4,16 @@ const Ir = @import("Ir.zig");
|
||||||
const Story = @import("Story.zig");
|
const Story = @import("Story.zig");
|
||||||
const InternPool = @import("InternPool.zig");
|
const InternPool = @import("InternPool.zig");
|
||||||
const compile = @import("compile.zig");
|
const compile = @import("compile.zig");
|
||||||
const Module = compile.Module;
|
const Compilation = compile.Compilation;
|
||||||
const assert = std.debug.assert;
|
const assert = std.debug.assert;
|
||||||
const Sema = @This();
|
const Sema = @This();
|
||||||
|
|
||||||
gpa: std.mem.Allocator,
|
gpa: std.mem.Allocator,
|
||||||
arena: std.mem.Allocator,
|
arena: std.mem.Allocator,
|
||||||
module: *compile.Module,
|
module: *Compilation,
|
||||||
ir: Ir,
|
ir: Ir,
|
||||||
inst_map: std.AutoHashMapUnmanaged(Ir.Inst.Index, ValueInfo) = .empty,
|
inst_map: std.AutoHashMapUnmanaged(Ir.Inst.Index, ValueInfo) = .empty,
|
||||||
errors: *std.ArrayListUnmanaged(Module.Error),
|
errors: *std.ArrayListUnmanaged(Compilation.Error),
|
||||||
comptime_break_inst: Ir.Inst.Index = undefined,
|
comptime_break_inst: Ir.Inst.Index = undefined,
|
||||||
|
|
||||||
const InnerError = error{
|
const InnerError = error{
|
||||||
|
|
@ -136,17 +136,17 @@ pub fn lookupIdentifier(
|
||||||
builder: *Builder,
|
builder: *Builder,
|
||||||
ident: InternPool.Index,
|
ident: InternPool.Index,
|
||||||
src: SrcLoc,
|
src: SrcLoc,
|
||||||
) !Module.Namespace.Decl {
|
) !Compilation.Namespace.Decl {
|
||||||
return sema.lookupInNamespace(builder.namespace, ident, src);
|
return sema.lookupInNamespace(builder.namespace, ident, src);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn lookupInNamespace(
|
pub fn lookupInNamespace(
|
||||||
sema: *Sema,
|
sema: *Sema,
|
||||||
namespace: *Module.Namespace,
|
namespace: *Compilation.Namespace,
|
||||||
ident: InternPool.Index,
|
ident: InternPool.Index,
|
||||||
src: SrcLoc,
|
src: SrcLoc,
|
||||||
) !Module.Namespace.Decl {
|
) !Compilation.Namespace.Decl {
|
||||||
var scope: ?*Module.Namespace = namespace;
|
var scope: ?*Compilation.Namespace = namespace;
|
||||||
while (scope) |s| : (scope = s.parent) {
|
while (scope) |s| : (scope = s.parent) {
|
||||||
if (s.decls.get(ident)) |decl| return decl;
|
if (s.decls.get(ident)) |decl| return decl;
|
||||||
}
|
}
|
||||||
|
|
@ -166,7 +166,7 @@ pub const Block = struct {
|
||||||
|
|
||||||
pub const Builder = struct {
|
pub const Builder = struct {
|
||||||
sema: *Sema,
|
sema: *Sema,
|
||||||
namespace: *Module.Namespace,
|
namespace: *Compilation.Namespace,
|
||||||
code: *InternPool.CodeChunk,
|
code: *InternPool.CodeChunk,
|
||||||
constants_map: std.AutoHashMapUnmanaged(InternPool.Index, u8) = .empty,
|
constants_map: std.AutoHashMapUnmanaged(InternPool.Index, u8) = .empty,
|
||||||
labels: std.ArrayListUnmanaged(Label) = .empty,
|
labels: std.ArrayListUnmanaged(Label) = .empty,
|
||||||
|
|
@ -1148,7 +1148,7 @@ fn analyzeCallTarget(
|
||||||
builder: *Builder,
|
builder: *Builder,
|
||||||
src: SrcLoc,
|
src: SrcLoc,
|
||||||
callee: ValueInfo,
|
callee: ValueInfo,
|
||||||
) !Module.Namespace.Decl {
|
) !Compilation.Namespace.Decl {
|
||||||
switch (callee) {
|
switch (callee) {
|
||||||
.function => |ip_index| {
|
.function => |ip_index| {
|
||||||
try builder.materialize(callee);
|
try builder.materialize(callee);
|
||||||
|
|
@ -1163,7 +1163,7 @@ fn analyzeDivertTarget(
|
||||||
builder: *Builder,
|
builder: *Builder,
|
||||||
src: SrcLoc,
|
src: SrcLoc,
|
||||||
callee: ValueInfo,
|
callee: ValueInfo,
|
||||||
) !Module.Namespace.Decl {
|
) !Compilation.Namespace.Decl {
|
||||||
switch (callee) {
|
switch (callee) {
|
||||||
.knot => |ip_index| {
|
.knot => |ip_index| {
|
||||||
try builder.materialize(callee);
|
try builder.materialize(callee);
|
||||||
|
|
@ -1333,29 +1333,31 @@ pub fn analyzeKnot(
|
||||||
|
|
||||||
fn analyzeNestedDecl(
|
fn analyzeNestedDecl(
|
||||||
sema: *Sema,
|
sema: *Sema,
|
||||||
namespace: *Module.Namespace,
|
namespace: *Compilation.Namespace,
|
||||||
inst: Ir.Inst.Index,
|
inst: Ir.Inst.Index,
|
||||||
) !void {
|
) !void {
|
||||||
|
const gpa = sema.gpa;
|
||||||
|
const arena = sema.arena;
|
||||||
const data = sema.ir.instructions[@intFromEnum(inst)].data.payload;
|
const data = sema.ir.instructions[@intFromEnum(inst)].data.payload;
|
||||||
const extra = sema.ir.extraData(Ir.Inst.Declaration, data.extra_index).data;
|
const extra = sema.ir.extraData(Ir.Inst.Declaration, data.extra_index).data;
|
||||||
const decl = sema.ir.instructions[@intFromEnum(extra.value)];
|
const decl = sema.ir.instructions[@intFromEnum(extra.value)];
|
||||||
|
|
||||||
const decl_name = try sema.module.intern_pool.getOrPutString(
|
const decl_name = try sema.module.intern_pool.getOrPutString(
|
||||||
sema.gpa,
|
gpa,
|
||||||
sema.ir.nullTerminatedString(extra.name),
|
sema.ir.nullTerminatedString(extra.name),
|
||||||
);
|
);
|
||||||
const ip_index = try sema.module.intern_pool.getOrPutValue(sema.gpa, .{ .str = decl_name });
|
const ip_index = try sema.module.intern_pool.getOrPutValue(gpa, .{ .str = decl_name });
|
||||||
|
|
||||||
switch (decl.tag) {
|
switch (decl.tag) {
|
||||||
.decl_stitch => {
|
.decl_stitch => {
|
||||||
const child_namespace = try sema.module.createNamespace(namespace);
|
const child_namespace = try sema.module.createNamespace(arena, namespace);
|
||||||
try namespace.decls.put(sema.arena, ip_index, .{
|
try namespace.decls.put(arena, ip_index, .{
|
||||||
.tag = .knot,
|
.tag = .knot,
|
||||||
.decl_inst = extra.value,
|
.decl_inst = extra.value,
|
||||||
.args_count = 0,
|
.args_count = 0,
|
||||||
.namespace = child_namespace,
|
.namespace = child_namespace,
|
||||||
});
|
});
|
||||||
try sema.module.queueWorkItem(.{
|
try sema.module.queueWorkItem(arena, .{
|
||||||
.tag = .stitch,
|
.tag = .stitch,
|
||||||
.decl_name = ip_index,
|
.decl_name = ip_index,
|
||||||
.inst_index = extra.value,
|
.inst_index = extra.value,
|
||||||
|
|
@ -1366,7 +1368,8 @@ fn analyzeNestedDecl(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn scanTopLevelDecl(sema: *Sema, namespace: *Module.Namespace, inst: Ir.Inst.Index) !void {
|
fn scanTopLevelDecl(sema: *Sema, namespace: *Compilation.Namespace, inst: Ir.Inst.Index) !void {
|
||||||
|
const arena = sema.arena;
|
||||||
const data = sema.ir.instructions[@intFromEnum(inst)].data.payload;
|
const data = sema.ir.instructions[@intFromEnum(inst)].data.payload;
|
||||||
const extra = sema.ir.extraData(Ir.Inst.Declaration, data.extra_index).data;
|
const extra = sema.ir.extraData(Ir.Inst.Declaration, data.extra_index).data;
|
||||||
const decl_inst = sema.ir.instructions[@intFromEnum(extra.value)];
|
const decl_inst = sema.ir.instructions[@intFromEnum(extra.value)];
|
||||||
|
|
@ -1398,8 +1401,8 @@ fn scanTopLevelDecl(sema: *Sema, namespace: *Module.Namespace, inst: Ir.Inst.Ind
|
||||||
const _body = sema.ir.bodySlice(_extra.end, _extra.data.body_len);
|
const _body = sema.ir.bodySlice(_extra.end, _extra.data.body_len);
|
||||||
const _stitches = sema.ir.bodySlice(_extra.end + _body.len, _extra.data.stitches_len);
|
const _stitches = sema.ir.bodySlice(_extra.end + _body.len, _extra.data.stitches_len);
|
||||||
|
|
||||||
const child_namespace = try sema.module.createNamespace(namespace);
|
const child_namespace = try sema.module.createNamespace(arena, namespace);
|
||||||
const gop = try namespace.decls.getOrPut(sema.arena, decl_name);
|
const gop = try namespace.decls.getOrPut(arena, decl_name);
|
||||||
if (gop.found_existing) {
|
if (gop.found_existing) {
|
||||||
return sema.fail(src_loc, "duplicate identifier", .{});
|
return sema.fail(src_loc, "duplicate identifier", .{});
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -1412,7 +1415,7 @@ fn scanTopLevelDecl(sema: *Sema, namespace: *Module.Namespace, inst: Ir.Inst.Ind
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
try sema.module.queueWorkItem(.{
|
try sema.module.queueWorkItem(arena, .{
|
||||||
.tag = .knot,
|
.tag = .knot,
|
||||||
.decl_name = decl_name,
|
.decl_name = decl_name,
|
||||||
.inst_index = extra.value,
|
.inst_index = extra.value,
|
||||||
|
|
@ -1424,8 +1427,8 @@ fn scanTopLevelDecl(sema: *Sema, namespace: *Module.Namespace, inst: Ir.Inst.Ind
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
.decl_stitch => {
|
.decl_stitch => {
|
||||||
const child_namespace = try sema.module.createNamespace(namespace);
|
const child_namespace = try sema.module.createNamespace(arena, namespace);
|
||||||
const gop = try namespace.decls.getOrPut(sema.arena, decl_name);
|
const gop = try namespace.decls.getOrPut(arena, decl_name);
|
||||||
if (gop.found_existing) {
|
if (gop.found_existing) {
|
||||||
return sema.fail(src_loc, "duplicate identifier", .{});
|
return sema.fail(src_loc, "duplicate identifier", .{});
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -1437,7 +1440,7 @@ fn scanTopLevelDecl(sema: *Sema, namespace: *Module.Namespace, inst: Ir.Inst.Ind
|
||||||
.namespace = child_namespace,
|
.namespace = child_namespace,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
try sema.module.queueWorkItem(.{
|
try sema.module.queueWorkItem(arena, .{
|
||||||
.tag = .stitch,
|
.tag = .stitch,
|
||||||
.decl_name = decl_name,
|
.decl_name = decl_name,
|
||||||
.inst_index = extra.value,
|
.inst_index = extra.value,
|
||||||
|
|
@ -1445,8 +1448,8 @@ fn scanTopLevelDecl(sema: *Sema, namespace: *Module.Namespace, inst: Ir.Inst.Ind
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
.decl_function => {
|
.decl_function => {
|
||||||
const child_namespace = try sema.module.createNamespace(namespace);
|
const child_namespace = try sema.module.createNamespace(arena, namespace);
|
||||||
const gop = try namespace.decls.getOrPut(sema.arena, decl_name);
|
const gop = try namespace.decls.getOrPut(arena, decl_name);
|
||||||
if (gop.found_existing) {
|
if (gop.found_existing) {
|
||||||
return sema.fail(src_loc, "duplicate identifier", .{});
|
return sema.fail(src_loc, "duplicate identifier", .{});
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -1458,7 +1461,7 @@ fn scanTopLevelDecl(sema: *Sema, namespace: *Module.Namespace, inst: Ir.Inst.Ind
|
||||||
.namespace = child_namespace,
|
.namespace = child_namespace,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
try sema.module.queueWorkItem(.{
|
try sema.module.queueWorkItem(arena, .{
|
||||||
.tag = .function,
|
.tag = .function,
|
||||||
.decl_name = decl_name,
|
.decl_name = decl_name,
|
||||||
.inst_index = extra.value,
|
.inst_index = extra.value,
|
||||||
|
|
@ -1500,7 +1503,7 @@ fn resolveGlobalDecl(
|
||||||
|
|
||||||
pub fn scanTopLevelDecls(
|
pub fn scanTopLevelDecls(
|
||||||
sema: *Sema,
|
sema: *Sema,
|
||||||
namespace: *Module.Namespace,
|
namespace: *Compilation.Namespace,
|
||||||
decls: []const Ir.Inst.Index,
|
decls: []const Ir.Inst.Index,
|
||||||
) !void {
|
) !void {
|
||||||
const gpa = sema.gpa;
|
const gpa = sema.gpa;
|
||||||
|
|
|
||||||
146
src/Story.zig
146
src/Story.zig
|
|
@ -1,13 +1,14 @@
|
||||||
//! Virtual machine state for story execution.
|
//! Virtual machine state for story execution.
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const tokenizer = @import("tokenizer.zig");
|
const assert = std.debug.assert;
|
||||||
|
|
||||||
const Ast = @import("Ast.zig");
|
const Ast = @import("Ast.zig");
|
||||||
const AstGen = @import("AstGen.zig");
|
const AstGen = @import("AstGen.zig");
|
||||||
const Module = @import("compile.zig").Module;
|
const Compilation = @import("compile.zig").Compilation;
|
||||||
|
pub const Loader = @import("Story/Loader.zig");
|
||||||
pub const Object = @import("Story/Object.zig");
|
pub const Object = @import("Story/Object.zig");
|
||||||
const Dumper = @import("Story/Dumper.zig");
|
pub const Dumper = @import("Story/Dumper.zig");
|
||||||
const ink = @import("root.zig");
|
const ink = @import("root.zig");
|
||||||
const assert = std.debug.assert;
|
|
||||||
const Story = @This();
|
const Story = @This();
|
||||||
|
|
||||||
gpa: std.mem.Allocator,
|
gpa: std.mem.Allocator,
|
||||||
|
|
@ -42,17 +43,6 @@ internal_counter: usize = 0,
|
||||||
|
|
||||||
pub const default_knot_name: [:0]const u8 = "$__main__$";
|
pub const default_knot_name: [:0]const u8 = "$__main__$";
|
||||||
|
|
||||||
pub const VariableObserver = struct {
|
|
||||||
callback: Callback,
|
|
||||||
context: Context,
|
|
||||||
|
|
||||||
pub const Callback = *const fn (Value, Context) anyerror!void;
|
|
||||||
|
|
||||||
pub const Context = struct {
|
|
||||||
ptr: *anyopaque,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const Opcode = enum(u8) {
|
pub const Opcode = enum(u8) {
|
||||||
/// Exit the VM normally.
|
/// Exit the VM normally.
|
||||||
exit,
|
exit,
|
||||||
|
|
@ -118,6 +108,17 @@ pub const Opcode = enum(u8) {
|
||||||
_,
|
_,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const VariableObserver = struct {
|
||||||
|
callback: Callback,
|
||||||
|
context: Context,
|
||||||
|
|
||||||
|
pub const Callback = *const fn (Value, Context) anyerror!void;
|
||||||
|
|
||||||
|
pub const Context = struct {
|
||||||
|
ptr: *anyopaque,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
pub const CallFrame = struct {
|
pub const CallFrame = struct {
|
||||||
/// Pointer to the knot that initiated the call.
|
/// Pointer to the knot that initiated the call.
|
||||||
callee: *Object.Knot,
|
callee: *Object.Knot,
|
||||||
|
|
@ -936,70 +937,6 @@ pub fn dump(story: *Story, writer: *std.Io.Writer) !void {
|
||||||
return Dumper.dump(story, writer);
|
return Dumper.dump(story, writer);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const LoadOptions = struct {
|
|
||||||
filename: []const u8,
|
|
||||||
error_writer: *std.Io.Writer,
|
|
||||||
dump_writer: ?*std.Io.Writer = null,
|
|
||||||
dump_use_color: bool = true,
|
|
||||||
dump_ast: bool = false,
|
|
||||||
dump_ir: bool = false,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn fromSourceBytes(
|
|
||||||
gpa: std.mem.Allocator,
|
|
||||||
source_bytes: [:0]const u8,
|
|
||||||
options: LoadOptions,
|
|
||||||
) !Story {
|
|
||||||
var arena_allocator = std.heap.ArenaAllocator.init(gpa);
|
|
||||||
defer arena_allocator.deinit();
|
|
||||||
|
|
||||||
const arena = arena_allocator.allocator();
|
|
||||||
|
|
||||||
var comp = try Module.compile(gpa, arena, .{
|
|
||||||
.source_bytes = source_bytes,
|
|
||||||
.filename = options.filename,
|
|
||||||
.dump_writer = options.dump_writer,
|
|
||||||
.dump_use_color = options.dump_use_color,
|
|
||||||
.dump_ast = options.dump_ast,
|
|
||||||
.dump_ir = options.dump_ir,
|
|
||||||
});
|
|
||||||
defer comp.deinit();
|
|
||||||
|
|
||||||
if (comp.errors.items.len > 0) {
|
|
||||||
for (comp.errors.items) |err| {
|
|
||||||
try comp.renderError(options.error_writer, err);
|
|
||||||
}
|
|
||||||
return error.LoadFailed;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Make this configureable.
|
|
||||||
const stack_size = 128;
|
|
||||||
const eval_stack_ptr = try gpa.alloc(Value, stack_size);
|
|
||||||
errdefer gpa.free(eval_stack_ptr);
|
|
||||||
|
|
||||||
const call_stack_ptr = try gpa.alloc(CallFrame, stack_size);
|
|
||||||
errdefer gpa.free(call_stack_ptr);
|
|
||||||
|
|
||||||
var story: Story = .{
|
|
||||||
.gpa = gpa,
|
|
||||||
.arena = .init(gpa),
|
|
||||||
.can_advance = false,
|
|
||||||
.dump_writer = null,
|
|
||||||
.stack = eval_stack_ptr,
|
|
||||||
.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);
|
|
||||||
}
|
|
||||||
return story;
|
|
||||||
}
|
|
||||||
|
|
||||||
var read_buffer: [4096]u8 align(std.heap.page_size_min) = undefined;
|
|
||||||
|
|
||||||
pub const LoadFileOptions = struct {
|
pub const LoadFileOptions = struct {
|
||||||
error_writer: *std.Io.Writer,
|
error_writer: *std.Io.Writer,
|
||||||
};
|
};
|
||||||
|
|
@ -1009,6 +946,7 @@ pub fn readSourceFile(
|
||||||
filename: []const u8,
|
filename: []const u8,
|
||||||
options: LoadFileOptions,
|
options: LoadFileOptions,
|
||||||
) !Story {
|
) !Story {
|
||||||
|
var read_buffer: [4096]u8 align(std.heap.page_size_min) = undefined;
|
||||||
// FIXME: Temporary until 0.16.x
|
// FIXME: Temporary until 0.16.x
|
||||||
var arena_allocator = std.heap.ArenaAllocator.init(gpa);
|
var arena_allocator = std.heap.ArenaAllocator.init(gpa);
|
||||||
defer arena_allocator.deinit();
|
defer arena_allocator.deinit();
|
||||||
|
|
@ -1030,3 +968,53 @@ pub fn readSourceFile(
|
||||||
.dump_ir = false,
|
.dump_ir = false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub const LoadOptions = struct {
|
||||||
|
filename: [:0]const u8,
|
||||||
|
errors: *std.ArrayListUnmanaged(Compilation.Error),
|
||||||
|
stack_size: usize = 128,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn fromSourceBytes(
|
||||||
|
gpa: std.mem.Allocator,
|
||||||
|
source_bytes: [:0]const u8,
|
||||||
|
options: LoadOptions,
|
||||||
|
) !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, source_bytes, options.filename, 0);
|
||||||
|
defer tree.deinit(gpa);
|
||||||
|
|
||||||
|
var ir = try AstGen.generate(gpa, &tree);
|
||||||
|
defer ir.deinit(gpa);
|
||||||
|
|
||||||
|
var cu = Compilation.build(gpa, tree, ir, options.errors) catch |err| switch (err) {
|
||||||
|
else => |e| return e,
|
||||||
|
};
|
||||||
|
defer cu.deinit();
|
||||||
|
|
||||||
|
if (cu.hasCompileErrors()) {
|
||||||
|
return error.CompilationError;
|
||||||
|
}
|
||||||
|
return .fromCompilation(gpa, &cu, .{
|
||||||
|
.stack_size = options.stack_size,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fromCompilation(
|
||||||
|
gpa: std.mem.Allocator,
|
||||||
|
cu: *Compilation,
|
||||||
|
options: Loader.Options,
|
||||||
|
) !Story {
|
||||||
|
return Loader.fromCompilation(gpa, cu, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fromCachedCompilation(
|
||||||
|
gpa: std.mem.Allocator,
|
||||||
|
bytes: []const u8,
|
||||||
|
options: Loader.Options,
|
||||||
|
) !Story {
|
||||||
|
return Loader.fromCachedCompilation(gpa, bytes, options);
|
||||||
|
}
|
||||||
|
|
|
||||||
136
src/Story/Loader.zig
Normal file
136
src/Story/Loader.zig
Normal file
|
|
@ -0,0 +1,136 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const InternPool = @import("../InternPool.zig");
|
||||||
|
const Compilation = @import("../compile.zig").Compilation;
|
||||||
|
const Story = @import("../Story.zig");
|
||||||
|
const Value = Story.Value;
|
||||||
|
const Object = Story.Object;
|
||||||
|
|
||||||
|
fn makeValueFromInterned(story: *Story, value: InternPool.Key) !Story.Value {
|
||||||
|
return switch (value) {
|
||||||
|
.bool => |boolean| .{ .bool = boolean },
|
||||||
|
.int => |int| .{ .int = @intCast(int) },
|
||||||
|
.float => |float| .{ .float = @bitCast(float) },
|
||||||
|
.str => |index| blk: {
|
||||||
|
const str_object = try Object.String.create(story, .{
|
||||||
|
.bytes = stringBytes(story, index),
|
||||||
|
});
|
||||||
|
break :blk .{ .object = &str_object.base };
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn makeKnotObject(
|
||||||
|
story: *Story,
|
||||||
|
name: []const u8,
|
||||||
|
code: *InternPool.CodeChunk,
|
||||||
|
) !*Object.Knot {
|
||||||
|
return Object.Knot.create(story, .{
|
||||||
|
.name = name,
|
||||||
|
.code = try Object.Code.create(story, .{
|
||||||
|
.args_count = @intCast(code.args_count),
|
||||||
|
.locals_count = @intCast(code.locals_count),
|
||||||
|
.stack_size = @intCast(code.stack_size),
|
||||||
|
.constants = try code.constants.toOwnedSlice(story.gpa),
|
||||||
|
.code_bytes = try code.bytecode.toOwnedSlice(story.gpa),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn makeConstantsPool(mod: *Compilation, story: *Story) ![]const Story.Value {
|
||||||
|
const ip = &mod.intern_pool;
|
||||||
|
const gpa = mod.gpa;
|
||||||
|
var constants_pool: std.ArrayListUnmanaged(Value) = .empty;
|
||||||
|
defer constants_pool.deinit(mod.gpa);
|
||||||
|
try constants_pool.ensureUnusedCapacity(mod.gpa, ip.values.items.len);
|
||||||
|
|
||||||
|
for (ip.values.items) |value| {
|
||||||
|
const obj = try makeValueFromInterned(story, value);
|
||||||
|
constants_pool.appendAssumeCapacity(obj);
|
||||||
|
}
|
||||||
|
return constants_pool.toOwnedSlice(gpa);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn stringBytes(story: *Story, index: InternPool.NullTerminatedString) [:0]const u8 {
|
||||||
|
const slice = story.string_bytes[@intFromEnum(index)..];
|
||||||
|
return slice[0..std.mem.indexOfScalar(u8, slice, 0).? :0];
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const Options = struct {
|
||||||
|
stack_size: usize,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn fromCompilationCompat(
|
||||||
|
gpa: std.mem.Allocator,
|
||||||
|
mod: *Compilation,
|
||||||
|
story: *Story,
|
||||||
|
options: Options,
|
||||||
|
) !void {
|
||||||
|
const ip = &mod.intern_pool;
|
||||||
|
|
||||||
|
story.stack = try gpa.alloc(Story.Value, options.stack_size);
|
||||||
|
story.call_stack = try gpa.alloc(Story.CallFrame, options.stack_size);
|
||||||
|
story.string_bytes = try ip.string_bytes.toOwnedSlice(gpa);
|
||||||
|
story.constants_pool = try makeConstantsPool(mod, story);
|
||||||
|
|
||||||
|
for (mod.globals.items) |global| {
|
||||||
|
const ip_key = ip.internedValue(global.key);
|
||||||
|
const ip_value = ip.internedValue(global.value);
|
||||||
|
|
||||||
|
const key_bytes = stringBytes(story, ip_key.str);
|
||||||
|
const obj = try makeValueFromInterned(story, ip_value);
|
||||||
|
try story.globals.put(gpa, key_bytes, obj);
|
||||||
|
}
|
||||||
|
for (mod.knots.items) |knot| {
|
||||||
|
const ip_key = ip.internedValue(knot.name_index);
|
||||||
|
const key_bytes = stringBytes(story, ip_key.str);
|
||||||
|
|
||||||
|
const knot_object = try makeKnotObject(story, key_bytes, knot.code_chunk);
|
||||||
|
const value: Story.Value = .{ .object = &knot_object.base };
|
||||||
|
try story.globals.put(gpa, key_bytes, value);
|
||||||
|
}
|
||||||
|
for (mod.stitches.items) |stitch| {
|
||||||
|
const ip_key = ip.internedValue(stitch.name_index);
|
||||||
|
const key_bytes = stringBytes(story, ip_key.str);
|
||||||
|
const stitch_object = try makeKnotObject(story, key_bytes, stitch.code_chunk);
|
||||||
|
|
||||||
|
if (stitch.knot_index) |index| {
|
||||||
|
const parent_knot = mod.knots.items[@intFromEnum(index)];
|
||||||
|
const s_key_value = ip.internedValue(parent_knot.name_index);
|
||||||
|
const parent_knot_name = stringBytes(story, s_key_value.str);
|
||||||
|
const parent_knot_value = story.globals.get(parent_knot_name).?;
|
||||||
|
const parent_knot_obj: *Object.Knot = @ptrCast(parent_knot_value.object);
|
||||||
|
try parent_knot_obj.members.put(gpa, key_bytes, &stitch_object.base);
|
||||||
|
} else {
|
||||||
|
const value: Story.Value = .{ .object = &stitch_object.base };
|
||||||
|
try story.globals.put(gpa, key_bytes, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (story.getKnot(Story.default_knot_name)) |knot| {
|
||||||
|
try story.pushStack(.{ .object = &knot.base });
|
||||||
|
try story.divert(knot, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fromCompilation(
|
||||||
|
gpa: std.mem.Allocator,
|
||||||
|
mod: *Compilation,
|
||||||
|
options: Options,
|
||||||
|
) !Story {
|
||||||
|
var story: Story = .{
|
||||||
|
.gpa = gpa,
|
||||||
|
.arena = .init(gpa),
|
||||||
|
};
|
||||||
|
try fromCompilationCompat(gpa, mod, &story, options);
|
||||||
|
return story;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fromCachedCompilation(
|
||||||
|
gpa: std.mem.Allocator,
|
||||||
|
bytes: []const u8,
|
||||||
|
options: Options,
|
||||||
|
) !Story {
|
||||||
|
_ = gpa;
|
||||||
|
_ = bytes;
|
||||||
|
_ = options;
|
||||||
|
return error.NotImplemented;
|
||||||
|
}
|
||||||
|
|
@ -2,6 +2,7 @@ const std = @import("std");
|
||||||
const fatal = std.process.fatal;
|
const fatal = std.process.fatal;
|
||||||
const ink = @import("../root.zig");
|
const ink = @import("../root.zig");
|
||||||
const Story = ink.Story;
|
const Story = ink.Story;
|
||||||
|
const Compilation = ink.Compilation;
|
||||||
|
|
||||||
test "fixture - variable arithmetic" {
|
test "fixture - variable arithmetic" {
|
||||||
try testRuntimeFixture("variable-arithmetic");
|
try testRuntimeFixture("variable-arithmetic");
|
||||||
|
|
@ -262,12 +263,15 @@ test "variable observer" {
|
||||||
var io_w = std.Io.Writer.Allocating.init(gpa);
|
var io_w = std.Io.Writer.Allocating.init(gpa);
|
||||||
defer io_w.deinit();
|
defer io_w.deinit();
|
||||||
|
|
||||||
|
var errors: std.ArrayListUnmanaged(Compilation.Error) = .empty;
|
||||||
|
defer errors.deinit(gpa);
|
||||||
|
|
||||||
var story = try ink.Story.fromSourceBytes(gpa,
|
var story = try ink.Story.fromSourceBytes(gpa,
|
||||||
\\VAR foo = 1
|
\\VAR foo = 1
|
||||||
\\~foo = 10
|
\\~foo = 10
|
||||||
, .{
|
, .{
|
||||||
.filename = "<STDIN>",
|
.filename = "<STDIN>",
|
||||||
.error_writer = &io_w.writer,
|
.errors = &errors,
|
||||||
});
|
});
|
||||||
defer story.deinit();
|
defer story.deinit();
|
||||||
|
|
||||||
|
|
@ -297,9 +301,13 @@ const Options = struct {
|
||||||
fn testRunner(gpa: std.mem.Allocator, source_bytes: [:0]const u8, options: Options) !void {
|
fn testRunner(gpa: std.mem.Allocator, source_bytes: [:0]const u8, options: Options) !void {
|
||||||
const io_r = options.input_reader;
|
const io_r = options.input_reader;
|
||||||
const io_w = options.transcript_writer;
|
const io_w = options.transcript_writer;
|
||||||
|
|
||||||
|
var errors: std.ArrayListUnmanaged(Compilation.Error) = .empty;
|
||||||
|
defer errors.deinit(gpa);
|
||||||
|
|
||||||
var story = try Story.fromSourceBytes(gpa, source_bytes, .{
|
var story = try Story.fromSourceBytes(gpa, source_bytes, .{
|
||||||
.filename = "<STDIN>",
|
.filename = "<STDIN>",
|
||||||
.error_writer = options.error_writer,
|
.errors = &errors,
|
||||||
});
|
});
|
||||||
defer story.deinit();
|
defer story.deinit();
|
||||||
|
|
||||||
|
|
|
||||||
147
src/c_api.zig
147
src/c_api.zig
|
|
@ -1,48 +1,14 @@
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const ink = @import("ink");
|
const ink = @import("ink");
|
||||||
const Story = ink.Story;
|
const Story = ink.Story;
|
||||||
const Module = ink.Module;
|
const Compilation = ink.Compilation;
|
||||||
|
const Ast = ink.Ast;
|
||||||
|
const AstGen = ink.AstGen;
|
||||||
|
const Ir = ink.Ir;
|
||||||
|
|
||||||
var global_allocator: std.heap.DebugAllocator(.{}) = .init;
|
var global_allocator: std.heap.DebugAllocator(.{}) = .init;
|
||||||
var stdout_buffer: [4096]u8 align(std.heap.page_size_min) = undefined;
|
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 {
|
pub const InkLoadOpts = extern struct {
|
||||||
filename: [*]const u8,
|
filename: [*]const u8,
|
||||||
filename_length: usize,
|
filename_length: usize,
|
||||||
|
|
@ -51,61 +17,90 @@ pub const InkLoadOpts = extern struct {
|
||||||
flags: i32,
|
flags: i32,
|
||||||
};
|
};
|
||||||
|
|
||||||
fn loadStory(story: *Story, options: *const InkLoadOpts) !void {
|
const LoadStoryOptions = struct {
|
||||||
const gpa = story.gpa;
|
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);
|
var arena_allocator = std.heap.ArenaAllocator.init(gpa);
|
||||||
defer arena_allocator.deinit();
|
defer arena_allocator.deinit();
|
||||||
|
|
||||||
const arena = arena_allocator.allocator();
|
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, .{
|
var tree = try Ast.parse(
|
||||||
.source_bytes = options.source_bytes[0..options.source_length :0],
|
gpa,
|
||||||
.filename = options.filename[0..options.filename_length],
|
arena,
|
||||||
.dump_writer = null,
|
options.source_bytes,
|
||||||
.dump_use_color = false,
|
options.filename,
|
||||||
.dump_ast = false,
|
@intCast(options.flags),
|
||||||
.dump_ir = false,
|
);
|
||||||
});
|
defer tree.deinit(gpa);
|
||||||
defer comp.deinit();
|
|
||||||
|
|
||||||
if (comp.errors.items.len > 0) {
|
var ir = try AstGen.generate(gpa, &tree);
|
||||||
for (comp.errors.items) |err| {
|
defer ir.deinit(gpa);
|
||||||
try comp.renderError(&stderr_writer.interface, err);
|
|
||||||
|
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.LoadFailed;
|
return error.Failed;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Make this configureable.
|
const story = try gpa.create(Story);
|
||||||
const stack_size = 128;
|
errdefer gpa.destroy(story);
|
||||||
const eval_stack_ptr = try gpa.alloc(Story.Value, stack_size);
|
story.* = .{
|
||||||
errdefer gpa.free(eval_stack_ptr);
|
.gpa = gpa,
|
||||||
|
.arena = .init(gpa),
|
||||||
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();
|
errdefer story.deinit();
|
||||||
|
|
||||||
try comp.setupStoryRuntime(gpa, story);
|
try Story.Loader.fromCompilationCompat(gpa, &cu, story, .{
|
||||||
if (story.getKnot(Story.default_knot_name)) |knot| {
|
.stack_size = options.stack_size,
|
||||||
try story.pushStack(.{ .object = &knot.base });
|
});
|
||||||
try story.divert(knot, 0);
|
return story;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub export fn ink_load_story_options(
|
pub export fn ink_load_story_options(
|
||||||
story: *Story,
|
|
||||||
options: *const InkLoadOpts,
|
options: *const InkLoadOpts,
|
||||||
) callconv(.c) c_int {
|
) callconv(.c) ?*Story {
|
||||||
loadStory(story, options) catch |err| {
|
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)});
|
std.debug.print("{any}\n", .{@errorName(err)});
|
||||||
return -1;
|
return null;
|
||||||
};
|
};
|
||||||
return 0;
|
}
|
||||||
|
|
||||||
|
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 {
|
pub export fn ink_story_can_continue(story: *Story) callconv(.c) bool {
|
||||||
|
|
|
||||||
290
src/compile.zig
290
src/compile.zig
|
|
@ -1,12 +1,8 @@
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const Ast = @import("Ast.zig");
|
const Ast = @import("Ast.zig");
|
||||||
const AstGen = @import("AstGen.zig");
|
|
||||||
const Sema = @import("Sema.zig");
|
|
||||||
const Ir = @import("Ir.zig");
|
const Ir = @import("Ir.zig");
|
||||||
const Story = @import("Story.zig");
|
const Sema = @import("Sema.zig");
|
||||||
const InternPool = @import("InternPool.zig");
|
const InternPool = @import("InternPool.zig");
|
||||||
const Value = Story.Value;
|
|
||||||
const Object = Story.Object;
|
|
||||||
const assert = std.debug.assert;
|
const assert = std.debug.assert;
|
||||||
|
|
||||||
pub fn IntrusiveQueue(comptime T: type) type {
|
pub fn IntrusiveQueue(comptime T: type) type {
|
||||||
|
|
@ -80,35 +76,34 @@ test IntrusiveQueue {
|
||||||
try testing.expect(q.pop() == null);
|
try testing.expect(q.pop() == null);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Revisit this. We might not need this at all.
|
pub const Compilation = struct {
|
||||||
pub const WorkItem = struct {
|
|
||||||
tag: Tag,
|
|
||||||
next: ?*WorkItem = null,
|
|
||||||
decl_name: InternPool.Index,
|
|
||||||
inst_index: Ir.Inst.Index,
|
|
||||||
namespace: *Module.Namespace,
|
|
||||||
|
|
||||||
pub const Tag = enum {
|
|
||||||
knot,
|
|
||||||
stitch,
|
|
||||||
function,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const WorkQueue = IntrusiveQueue(WorkItem);
|
|
||||||
|
|
||||||
pub const Module = struct {
|
|
||||||
gpa: std.mem.Allocator,
|
gpa: std.mem.Allocator,
|
||||||
arena: std.mem.Allocator,
|
arena: std.heap.ArenaAllocator,
|
||||||
tree: Ast,
|
tree: Ast,
|
||||||
ir: Ir,
|
ir: Ir,
|
||||||
globals: std.ArrayListUnmanaged(InternPool.Global) = .empty,
|
globals: std.ArrayListUnmanaged(InternPool.Global) = .empty,
|
||||||
knots: std.ArrayListUnmanaged(InternPool.Knot) = .empty,
|
knots: std.ArrayListUnmanaged(InternPool.Knot) = .empty,
|
||||||
stitches: std.ArrayListUnmanaged(InternPool.Stitch) = .empty,
|
stitches: std.ArrayListUnmanaged(InternPool.Stitch) = .empty,
|
||||||
errors: std.ArrayListUnmanaged(Error) = .empty,
|
errors: *std.ArrayListUnmanaged(Error),
|
||||||
intern_pool: InternPool = .{},
|
intern_pool: InternPool = .{},
|
||||||
work_queue: WorkQueue = .{},
|
work_queue: WorkQueue = .{},
|
||||||
|
|
||||||
|
pub const WorkItem = struct {
|
||||||
|
tag: Tag,
|
||||||
|
next: ?*WorkItem = null,
|
||||||
|
decl_name: InternPool.Index,
|
||||||
|
inst_index: Ir.Inst.Index,
|
||||||
|
namespace: *Compilation.Namespace,
|
||||||
|
|
||||||
|
pub const Tag = enum {
|
||||||
|
knot,
|
||||||
|
stitch,
|
||||||
|
function,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const WorkQueue = IntrusiveQueue(WorkItem);
|
||||||
|
|
||||||
pub const Namespace = struct {
|
pub const Namespace = struct {
|
||||||
parent: ?*Namespace,
|
parent: ?*Namespace,
|
||||||
decls: std.AutoArrayHashMapUnmanaged(InternPool.Index, Decl),
|
decls: std.AutoArrayHashMapUnmanaged(InternPool.Index, Decl),
|
||||||
|
|
@ -143,28 +138,29 @@ pub const Module = struct {
|
||||||
message: []const u8,
|
message: []const u8,
|
||||||
};
|
};
|
||||||
|
|
||||||
fn generateFile(mod: *Module) !void {
|
fn analyzeAndGenerate(cu: *Compilation) !void {
|
||||||
|
const gpa = cu.gpa;
|
||||||
|
const arena = cu.arena.allocator();
|
||||||
const root_node: Ir.Inst.Index = .file_inst;
|
const root_node: Ir.Inst.Index = .file_inst;
|
||||||
const gpa = mod.gpa;
|
const data = cu.ir.instructions[@intFromEnum(root_node)].data.payload;
|
||||||
const data = mod.ir.instructions[@intFromEnum(root_node)].data.payload;
|
const extra = cu.ir.extraData(Ir.Inst.Block, data.extra_index);
|
||||||
const extra = mod.ir.extraData(Ir.Inst.Block, data.extra_index);
|
const top_level_decls = cu.ir.bodySlice(extra.end, extra.data.body_len);
|
||||||
const top_level_decls = mod.ir.bodySlice(extra.end, extra.data.body_len);
|
|
||||||
|
|
||||||
var knot_index: ?InternPool.Knot.Index = null;
|
var knot_index: ?InternPool.Knot.Index = null;
|
||||||
var sema: Sema = .{
|
var sema: Sema = .{
|
||||||
.module = mod,
|
|
||||||
.gpa = gpa,
|
.gpa = gpa,
|
||||||
.arena = mod.arena,
|
.arena = arena,
|
||||||
.ir = mod.ir,
|
.module = cu,
|
||||||
.errors = &mod.errors,
|
.ir = cu.ir,
|
||||||
|
.errors = cu.errors,
|
||||||
};
|
};
|
||||||
defer sema.deinit();
|
defer sema.deinit();
|
||||||
|
|
||||||
const file_scope = try mod.createNamespace(null);
|
const file_scope = try cu.createNamespace(arena, null);
|
||||||
try sema.scanTopLevelDecls(file_scope, top_level_decls);
|
try sema.scanTopLevelDecls(file_scope, top_level_decls);
|
||||||
|
|
||||||
while (mod.work_queue.pop()) |work_unit| {
|
while (cu.work_queue.pop()) |work_unit| {
|
||||||
const code_chunk = try mod.createCodeChunk();
|
const code_chunk = try cu.createCodeChunk(arena);
|
||||||
|
|
||||||
var builder: Sema.Builder = .{
|
var builder: Sema.Builder = .{
|
||||||
.sema = &sema,
|
.sema = &sema,
|
||||||
|
|
@ -180,8 +176,8 @@ pub const Module = struct {
|
||||||
try sema.analyzeKnot(&builder, work_unit.inst_index);
|
try sema.analyzeKnot(&builder, work_unit.inst_index);
|
||||||
try builder.finalize();
|
try builder.finalize();
|
||||||
|
|
||||||
knot_index = @enumFromInt(mod.knots.items.len);
|
knot_index = @enumFromInt(cu.knots.items.len);
|
||||||
try mod.knots.append(gpa, .{
|
try cu.knots.append(gpa, .{
|
||||||
.name_index = work_unit.decl_name,
|
.name_index = work_unit.decl_name,
|
||||||
.code_chunk = code_chunk,
|
.code_chunk = code_chunk,
|
||||||
});
|
});
|
||||||
|
|
@ -191,7 +187,7 @@ pub const Module = struct {
|
||||||
try sema.analyzeStitch(&builder, work_unit.inst_index);
|
try sema.analyzeStitch(&builder, work_unit.inst_index);
|
||||||
try builder.finalize();
|
try builder.finalize();
|
||||||
|
|
||||||
try mod.stitches.append(gpa, .{
|
try cu.stitches.append(gpa, .{
|
||||||
.knot_index = knot_index,
|
.knot_index = knot_index,
|
||||||
.name_index = work_unit.decl_name,
|
.name_index = work_unit.decl_name,
|
||||||
.code_chunk = code_chunk,
|
.code_chunk = code_chunk,
|
||||||
|
|
@ -202,7 +198,7 @@ pub const Module = struct {
|
||||||
try sema.analyzeFunction(&builder, work_unit.inst_index);
|
try sema.analyzeFunction(&builder, work_unit.inst_index);
|
||||||
try builder.finalize();
|
try builder.finalize();
|
||||||
|
|
||||||
try mod.stitches.append(gpa, .{
|
try cu.stitches.append(gpa, .{
|
||||||
.knot_index = null,
|
.knot_index = null,
|
||||||
.name_index = work_unit.decl_name,
|
.name_index = work_unit.decl_name,
|
||||||
.code_chunk = code_chunk,
|
.code_chunk = code_chunk,
|
||||||
|
|
@ -212,170 +208,78 @@ pub const Module = struct {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const Options = struct {
|
fn generate(
|
||||||
source_bytes: [:0]const u8,
|
|
||||||
filename: []const u8,
|
|
||||||
dump_writer: ?*std.Io.Writer = null,
|
|
||||||
dump_ast: bool = false,
|
|
||||||
dump_ir: bool = false,
|
|
||||||
dump_use_color: bool = false,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn compile(
|
|
||||||
gpa: std.mem.Allocator,
|
gpa: std.mem.Allocator,
|
||||||
arena: std.mem.Allocator,
|
tree: Ast,
|
||||||
options: Options,
|
ir: Ir,
|
||||||
) !Module {
|
errors: *std.ArrayListUnmanaged(Error),
|
||||||
const tree = try Ast.parse(gpa, arena, options.source_bytes, options.filename, 0);
|
) !Compilation {
|
||||||
if (options.dump_writer) |w| {
|
var cu: Compilation = .{
|
||||||
if (options.dump_ast) {
|
|
||||||
try w.writeAll("=== AST ===\n");
|
|
||||||
try tree.render(gpa, w, .{
|
|
||||||
.use_color = options.dump_use_color,
|
|
||||||
});
|
|
||||||
try w.flush();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var module: Module = .{
|
|
||||||
.gpa = gpa,
|
.gpa = gpa,
|
||||||
.arena = arena,
|
.arena = .init(gpa),
|
||||||
.tree = tree,
|
.tree = tree,
|
||||||
.ir = try AstGen.generate(gpa, &tree),
|
.ir = ir,
|
||||||
|
.errors = errors,
|
||||||
};
|
};
|
||||||
errdefer module.deinit();
|
errdefer cu.deinit();
|
||||||
|
|
||||||
if (options.dump_writer) |w| {
|
if (errors.items.len != 0) return cu;
|
||||||
if (options.dump_ir) {
|
|
||||||
try w.writeAll("=== Semantic IR ===\n");
|
try cu.intern_pool.string_bytes.append(gpa, 0);
|
||||||
try module.ir.dumpInfo(w);
|
try cu.intern_pool.values.append(gpa, .{ .bool = true });
|
||||||
try module.ir.render(w);
|
try cu.intern_pool.values.append(gpa, .{ .bool = false });
|
||||||
}
|
try cu.intern_pool.values.append(gpa, .{ .str = .empty });
|
||||||
}
|
|
||||||
if (module.ir.hasCompileErrors()) {
|
cu.analyzeAndGenerate() catch |err| switch (err) {
|
||||||
const payload_index = module.ir.extra[@intFromEnum(Ir.ExtraIndex.compile_errors)];
|
error.OutOfMemory => return error.OutOfMemory,
|
||||||
|
error.AnalysisFail => return cu,
|
||||||
|
else => |e| @panic(@errorName(e)),
|
||||||
|
};
|
||||||
|
return cu;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build(
|
||||||
|
gpa: std.mem.Allocator,
|
||||||
|
tree: Ast,
|
||||||
|
ir: Ir,
|
||||||
|
errors: *std.ArrayListUnmanaged(Error),
|
||||||
|
) !Compilation {
|
||||||
|
if (ir.hasCompileErrors()) {
|
||||||
|
const payload_index = ir.extra[@intFromEnum(Ir.ExtraIndex.compile_errors)];
|
||||||
assert(payload_index != 0);
|
assert(payload_index != 0);
|
||||||
|
|
||||||
const header = module.ir.extraData(Ir.Inst.CompileErrors, payload_index);
|
const header = ir.extraData(Ir.Inst.CompileErrors, payload_index);
|
||||||
const items_len = header.data.items_len;
|
const items_len = header.data.items_len;
|
||||||
var extra_index = header.end;
|
var extra_index = header.end;
|
||||||
|
|
||||||
// TODO: Make an iterator for this?
|
// TODO: Make an iterator for this?
|
||||||
for (0..items_len) |_| {
|
for (0..items_len) |_| {
|
||||||
const item = module.ir.extraData(Ir.Inst.CompileErrors.Item, extra_index);
|
const item = ir.extraData(Ir.Inst.CompileErrors.Item, extra_index);
|
||||||
extra_index = item.end;
|
extra_index = item.end;
|
||||||
|
|
||||||
const loc = findLineColumn(tree.source, item.data.byte_offset);
|
const loc = findLineColumn(tree.source, item.data.byte_offset);
|
||||||
try module.errors.append(gpa, .{
|
try errors.append(gpa, .{
|
||||||
.line = loc.line,
|
.line = loc.line,
|
||||||
.column = loc.column,
|
.column = loc.column,
|
||||||
.snippet = loc.source_line,
|
.snippet = loc.source_line,
|
||||||
.message = module.ir.nullTerminatedString(item.data.msg),
|
.message = ir.nullTerminatedString(item.data.msg),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
try module.intern_pool.string_bytes.append(gpa, 0);
|
|
||||||
try module.intern_pool.values.append(gpa, .{ .bool = true });
|
|
||||||
try module.intern_pool.values.append(gpa, .{ .bool = false });
|
|
||||||
try module.intern_pool.values.append(gpa, .{ .str = .empty });
|
|
||||||
// TODO: Revisit this.
|
|
||||||
module.generateFile() catch |err| switch (err) {
|
|
||||||
error.OutOfMemory => return error.OutOfMemory,
|
|
||||||
error.AnalysisFail => return module,
|
|
||||||
else => |e| @panic(@errorName(e)),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
return module;
|
return .generate(gpa, tree, ir, errors);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn storyStr(story: *Story, index: InternPool.NullTerminatedString) [:0]const u8 {
|
pub fn hasCompileErrors(cu: *const Compilation) bool {
|
||||||
const slice = story.string_bytes[@intFromEnum(index)..];
|
return cu.errors.items.len > 0;
|
||||||
return slice[0..std.mem.indexOfScalar(u8, slice, 0).? :0];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn makeValueFromInterned(story: *Story, value: InternPool.Key) !Story.Value {
|
pub fn createNamespace(
|
||||||
return switch (value) {
|
cu: *Compilation,
|
||||||
.bool => |boolean| .{ .bool = boolean },
|
arena: std.mem.Allocator,
|
||||||
.int => |int| .{ .int = @intCast(int) },
|
parent: ?*Namespace,
|
||||||
.float => |float| .{ .float = @bitCast(float) },
|
) error{OutOfMemory}!*Namespace {
|
||||||
.str => |index| blk: {
|
_ = cu;
|
||||||
const str_object = try Object.String.create(story, .{
|
const ns = try arena.create(Namespace);
|
||||||
.bytes = storyStr(story, index),
|
|
||||||
});
|
|
||||||
break :blk .{ .object = &str_object.base };
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
fn makeKnotObject(story: *Story, name_bytes: []const u8, code: *InternPool.CodeChunk) !*Object.Knot {
|
|
||||||
return Object.Knot.create(story, .{
|
|
||||||
.name = name_bytes,
|
|
||||||
.code = try Object.Code.create(story, .{
|
|
||||||
.args_count = @intCast(code.args_count),
|
|
||||||
.locals_count = @intCast(code.locals_count),
|
|
||||||
.stack_size = @intCast(code.stack_size),
|
|
||||||
.constants = try code.constants.toOwnedSlice(story.gpa),
|
|
||||||
.code_bytes = try code.bytecode.toOwnedSlice(story.gpa),
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn makeConstantsPool(mod: *Module, story: *Story) ![]const Story.Value {
|
|
||||||
const ip = &mod.intern_pool;
|
|
||||||
const gpa = mod.gpa;
|
|
||||||
var constants_pool: std.ArrayListUnmanaged(Value) = .empty;
|
|
||||||
defer constants_pool.deinit(mod.gpa);
|
|
||||||
try constants_pool.ensureUnusedCapacity(mod.gpa, ip.values.items.len);
|
|
||||||
|
|
||||||
for (ip.values.items) |value| {
|
|
||||||
const obj = try makeValueFromInterned(story, value);
|
|
||||||
constants_pool.appendAssumeCapacity(obj);
|
|
||||||
}
|
|
||||||
return constants_pool.toOwnedSlice(gpa);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn setupStoryRuntime(mod: *Module, gpa: std.mem.Allocator, story: *Story) !void {
|
|
||||||
const ip = &mod.intern_pool;
|
|
||||||
story.string_bytes = try ip.string_bytes.toOwnedSlice(mod.gpa);
|
|
||||||
story.constants_pool = try makeConstantsPool(mod, story);
|
|
||||||
|
|
||||||
for (mod.globals.items) |global| {
|
|
||||||
const ip_key = ip.internedValue(global.key);
|
|
||||||
const ip_value = ip.internedValue(global.value);
|
|
||||||
|
|
||||||
const key_bytes = storyStr(story, ip_key.str);
|
|
||||||
const obj = try makeValueFromInterned(story, ip_value);
|
|
||||||
try story.globals.put(gpa, key_bytes, obj);
|
|
||||||
}
|
|
||||||
for (mod.knots.items) |knot| {
|
|
||||||
const ip_key = ip.internedValue(knot.name_index);
|
|
||||||
const key_bytes = storyStr(story, ip_key.str);
|
|
||||||
|
|
||||||
const knot_object = try makeKnotObject(story, key_bytes, knot.code_chunk);
|
|
||||||
const value: Story.Value = .{ .object = &knot_object.base };
|
|
||||||
try story.globals.put(gpa, key_bytes, value);
|
|
||||||
}
|
|
||||||
for (mod.stitches.items) |stitch| {
|
|
||||||
const ip_key = ip.internedValue(stitch.name_index);
|
|
||||||
const key_bytes = storyStr(story, ip_key.str);
|
|
||||||
const stitch_object = try makeKnotObject(story, key_bytes, stitch.code_chunk);
|
|
||||||
|
|
||||||
if (stitch.knot_index) |index| {
|
|
||||||
const parent_knot = mod.knots.items[@intFromEnum(index)];
|
|
||||||
const s_key_value = ip.internedValue(parent_knot.name_index);
|
|
||||||
const parent_knot_name = storyStr(story, s_key_value.str);
|
|
||||||
const parent_knot_value = story.globals.get(parent_knot_name).?;
|
|
||||||
const parent_knot_obj: *Object.Knot = @ptrCast(parent_knot_value.object);
|
|
||||||
try parent_knot_obj.members.put(gpa, key_bytes, &stitch_object.base);
|
|
||||||
} else {
|
|
||||||
const value: Story.Value = .{ .object = &stitch_object.base };
|
|
||||||
try story.globals.put(gpa, key_bytes, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn createNamespace(mod: *Module, parent: ?*Namespace) error{OutOfMemory}!*Namespace {
|
|
||||||
const ns = try mod.arena.create(Namespace);
|
|
||||||
ns.* = .{
|
ns.* = .{
|
||||||
.parent = parent,
|
.parent = parent,
|
||||||
.decls = .empty,
|
.decls = .empty,
|
||||||
|
|
@ -383,14 +287,19 @@ pub const Module = struct {
|
||||||
return ns;
|
return ns;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn createCodeChunk(mod: *Module) error{OutOfMemory}!*InternPool.CodeChunk {
|
pub fn createCodeChunk(
|
||||||
const chunk = try mod.arena.create(InternPool.CodeChunk);
|
cu: *Compilation,
|
||||||
|
arena: std.mem.Allocator,
|
||||||
|
) error{OutOfMemory}!*InternPool.CodeChunk {
|
||||||
|
_ = cu;
|
||||||
|
const chunk = try arena.create(InternPool.CodeChunk);
|
||||||
chunk.* = .{};
|
chunk.* = .{};
|
||||||
return chunk;
|
return chunk;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn queueWorkItem(
|
pub fn queueWorkItem(
|
||||||
mod: *Module,
|
mod: *Compilation,
|
||||||
|
arena: std.mem.Allocator,
|
||||||
options: struct {
|
options: struct {
|
||||||
tag: WorkItem.Tag,
|
tag: WorkItem.Tag,
|
||||||
decl_name: InternPool.Index,
|
decl_name: InternPool.Index,
|
||||||
|
|
@ -398,7 +307,7 @@ pub const Module = struct {
|
||||||
namespace: *Namespace,
|
namespace: *Namespace,
|
||||||
},
|
},
|
||||||
) !void {
|
) !void {
|
||||||
const work_item = try mod.arena.create(WorkItem);
|
const work_item = try arena.create(WorkItem);
|
||||||
work_item.* = .{
|
work_item.* = .{
|
||||||
.tag = options.tag,
|
.tag = options.tag,
|
||||||
.decl_name = options.decl_name,
|
.decl_name = options.decl_name,
|
||||||
|
|
@ -408,7 +317,7 @@ pub const Module = struct {
|
||||||
mod.work_queue.push(work_item);
|
mod.work_queue.push(work_item);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn renderError(mod: *const Module, w: *std.Io.Writer, compile_error: Error) !void {
|
pub fn renderError(mod: *const Compilation, w: *std.Io.Writer, compile_error: Error) !void {
|
||||||
const filename = mod.tree.filename;
|
const filename = mod.tree.filename;
|
||||||
const line = compile_error.line + 1;
|
const line = compile_error.line + 1;
|
||||||
const column = compile_error.column + 1;
|
const column = compile_error.column + 1;
|
||||||
|
|
@ -427,15 +336,13 @@ pub const Module = struct {
|
||||||
return w.flush();
|
return w.flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(mod: *Module) void {
|
pub fn deinit(mod: *Compilation) void {
|
||||||
const gpa = mod.gpa;
|
const gpa = mod.gpa;
|
||||||
mod.tree.deinit(gpa);
|
mod.arena.deinit();
|
||||||
mod.ir.deinit(gpa);
|
|
||||||
mod.intern_pool.deinit(gpa);
|
mod.intern_pool.deinit(gpa);
|
||||||
mod.globals.deinit(gpa);
|
mod.globals.deinit(gpa);
|
||||||
mod.knots.deinit(gpa);
|
mod.knots.deinit(gpa);
|
||||||
mod.stitches.deinit(gpa);
|
mod.stitches.deinit(gpa);
|
||||||
mod.errors.deinit(gpa);
|
|
||||||
mod.* = undefined;
|
mod.* = undefined;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -472,8 +379,3 @@ pub fn findLineColumn(source: []const u8, byte_offset: usize) Loc {
|
||||||
.source_line = source[line_start..i],
|
.source_line = source[line_start..i],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hack(bytes: []const u8, index: InternPool.NullTerminatedString) [:0]const u8 {
|
|
||||||
const slice = bytes[@intFromEnum(index)..];
|
|
||||||
return slice[0..std.mem.indexOfScalar(u8, slice, 0).? :0];
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const compile = @import("compile.zig");
|
const Ast = @import("Ast.zig");
|
||||||
const Module = compile.Module;
|
const AstGen = @import("AstGen.zig");
|
||||||
|
const Compilation = @import("compile.zig").Compilation;
|
||||||
|
|
||||||
test "compiler: VAR expected expression" {
|
test "compiler: VAR expected expression" {
|
||||||
try testEqual(
|
try testEqual(
|
||||||
|
|
@ -173,17 +174,21 @@ fn testEqual(source_bytes: [:0]const u8, expected_error: []const u8) !void {
|
||||||
const io_w = &allocating.writer;
|
const io_w = &allocating.writer;
|
||||||
const arena = arena_allocator.allocator();
|
const arena = arena_allocator.allocator();
|
||||||
|
|
||||||
var c = try Module.compile(gpa, arena, .{
|
var errors: std.ArrayListUnmanaged(Compilation.Error) = .empty;
|
||||||
.source_bytes = source_bytes,
|
defer errors.deinit(gpa);
|
||||||
.filename = "<STDIN>",
|
|
||||||
.dump_writer = null,
|
|
||||||
.dump_use_color = false,
|
|
||||||
.dump_ast = false,
|
|
||||||
.dump_ir = false,
|
|
||||||
});
|
|
||||||
defer c.deinit();
|
|
||||||
|
|
||||||
try std.testing.expect(c.errors.items.len > 0);
|
var tree = try Ast.parse(gpa, arena, source_bytes, "<STDIN>", 0);
|
||||||
for (c.errors.items) |err| try c.renderError(io_w, err);
|
defer tree.deinit(gpa);
|
||||||
|
|
||||||
|
var ir = try AstGen.generate(gpa, &tree);
|
||||||
|
defer ir.deinit(gpa);
|
||||||
|
|
||||||
|
var cu = Compilation.build(gpa, tree, ir, &errors) catch |err| switch (err) {
|
||||||
|
else => |e| return e,
|
||||||
|
};
|
||||||
|
defer cu.deinit();
|
||||||
|
|
||||||
|
try std.testing.expect(errors.items.len > 0);
|
||||||
|
for (errors.items) |err| try cu.renderError(io_w, err);
|
||||||
return std.testing.expectEqualSlices(u8, expected_error, allocating.written());
|
return std.testing.expectEqualSlices(u8, expected_error, allocating.written());
|
||||||
}
|
}
|
||||||
|
|
|
||||||
124
src/main.zig
124
src/main.zig
|
|
@ -1,6 +1,10 @@
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const ink = @import("ink");
|
const ink = @import("ink");
|
||||||
|
const Ast = ink.Ast;
|
||||||
|
const AstGen = ink.AstGen;
|
||||||
|
const Ir = ink.Ir;
|
||||||
const Story = ink.Story;
|
const Story = ink.Story;
|
||||||
|
const Compilation = ink.Compilation;
|
||||||
const fatal = std.process.fatal;
|
const fatal = std.process.fatal;
|
||||||
|
|
||||||
var stdin_buffer: [4096]u8 align(std.heap.page_size_min) = undefined;
|
var stdin_buffer: [4096]u8 align(std.heap.page_size_min) = undefined;
|
||||||
|
|
@ -8,6 +12,32 @@ var stdout_buffer: [4096]u8 align(std.heap.page_size_min) = undefined;
|
||||||
var stderr_buffer: [4096]u8 align(std.heap.page_size_min) = undefined;
|
var stderr_buffer: [4096]u8 align(std.heap.page_size_min) = undefined;
|
||||||
var debug_allocator: std.heap.DebugAllocator(.{}) = .init;
|
var debug_allocator: std.heap.DebugAllocator(.{}) = .init;
|
||||||
|
|
||||||
|
const ArgsIterator = struct {
|
||||||
|
args: []const []const u8,
|
||||||
|
index: usize = 0,
|
||||||
|
|
||||||
|
pub fn next(iter: *ArgsIterator) ?[]const u8 {
|
||||||
|
if (iter.index >= iter.args.len) return null;
|
||||||
|
defer iter.index += 1;
|
||||||
|
return iter.args[iter.index];
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn nextOrFatal(iter: *ArgsIterator) []const u8 {
|
||||||
|
if (iter.index >= iter.args.len) {
|
||||||
|
fatal("expected parameter after {s}", .{iter.args[iter.index - 1]});
|
||||||
|
}
|
||||||
|
defer iter.index += 1;
|
||||||
|
return iter.args[iter.index];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const FileExtension = enum {
|
||||||
|
/// Ink source file.
|
||||||
|
ink,
|
||||||
|
/// Pre-compiled source file.
|
||||||
|
inkc,
|
||||||
|
};
|
||||||
|
|
||||||
pub fn main() !void {
|
pub fn main() !void {
|
||||||
const gpa = debug_allocator.allocator();
|
const gpa = debug_allocator.allocator();
|
||||||
defer _ = debug_allocator.deinit();
|
defer _ = debug_allocator.deinit();
|
||||||
|
|
@ -31,7 +61,7 @@ fn mainArgs(
|
||||||
args_list: []const [:0]const u8,
|
args_list: []const [:0]const u8,
|
||||||
) !void {
|
) !void {
|
||||||
var source_path: ?[]const u8 = null;
|
var source_path: ?[]const u8 = null;
|
||||||
var arg_index: usize = 1;
|
var output_path: ?[]const u8 = null;
|
||||||
var compile_only: bool = false;
|
var compile_only: bool = false;
|
||||||
var dump_ast: bool = false;
|
var dump_ast: bool = false;
|
||||||
var dump_ir: bool = false;
|
var dump_ir: bool = false;
|
||||||
|
|
@ -40,8 +70,11 @@ fn mainArgs(
|
||||||
var use_stdin: bool = false;
|
var use_stdin: bool = false;
|
||||||
var use_color: bool = false;
|
var use_color: bool = false;
|
||||||
|
|
||||||
while (arg_index < args_list.len) : (arg_index += 1) {
|
var args_iter: ArgsIterator = .{
|
||||||
const arg = args_list[arg_index];
|
.args = args_list[1..],
|
||||||
|
};
|
||||||
|
|
||||||
|
while (args_iter.next()) |arg| {
|
||||||
if (std.mem.startsWith(u8, arg, "-")) {
|
if (std.mem.startsWith(u8, arg, "-")) {
|
||||||
if (std.mem.eql(u8, arg, "--stdin")) {
|
if (std.mem.eql(u8, arg, "--stdin")) {
|
||||||
use_stdin = true;
|
use_stdin = true;
|
||||||
|
|
@ -57,6 +90,9 @@ fn mainArgs(
|
||||||
dump_trace = true;
|
dump_trace = true;
|
||||||
} else if (std.mem.eql(u8, arg, "--use-color")) {
|
} else if (std.mem.eql(u8, arg, "--use-color")) {
|
||||||
use_color = true;
|
use_color = true;
|
||||||
|
} else if (std.mem.eql(u8, arg, "--output")) {
|
||||||
|
const next_arg = args_iter.nextOrFatal();
|
||||||
|
output_path = next_arg;
|
||||||
} else {
|
} else {
|
||||||
fatal("invalid parameter: '{s}'", .{arg});
|
fatal("invalid parameter: '{s}'", .{arg});
|
||||||
}
|
}
|
||||||
|
|
@ -82,32 +118,74 @@ fn mainArgs(
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const stdout = std.fs.File.stdout();
|
|
||||||
var stdout_writer = stdout.writer(&stdout_buffer);
|
|
||||||
|
|
||||||
const stderr = std.fs.File.stderr();
|
const stderr = std.fs.File.stderr();
|
||||||
var stderr_writer = stderr.writer(&stderr_buffer);
|
var stderr_writer = stderr.writer(&stderr_buffer);
|
||||||
|
const io_w = &stderr_writer.interface;
|
||||||
|
const stack_size = 128;
|
||||||
|
|
||||||
var story = Story.fromSourceBytes(gpa, source_bytes, .{
|
const file_ext: FileExtension = f: {
|
||||||
.filename = filename,
|
const bytes = std.fs.path.extension(filename);
|
||||||
.error_writer = &stderr_writer.interface,
|
if (std.mem.eql(u8, bytes, ".ink")) break :f .ink;
|
||||||
.dump_writer = &stdout_writer.interface,
|
if (std.mem.eql(u8, bytes, ".inkc")) break :f .inkc;
|
||||||
.dump_use_color = use_color,
|
return error.InvalidFileExtension;
|
||||||
.dump_ast = dump_ast,
|
|
||||||
.dump_ir = dump_ir,
|
|
||||||
}) catch |err| switch (err) {
|
|
||||||
//error.LoadFailed => std.process.exit(1),
|
|
||||||
else => |e| return e,
|
|
||||||
};
|
};
|
||||||
defer story.deinit();
|
switch (file_ext) {
|
||||||
|
.ink => {
|
||||||
|
var tree = try Ast.parse(gpa, arena, source_bytes, filename, 0);
|
||||||
|
defer tree.deinit(gpa);
|
||||||
|
|
||||||
if (dump_trace) {
|
var ir = try AstGen.generate(gpa, &tree);
|
||||||
story.dump_writer = &stderr_writer.interface;
|
defer ir.deinit(gpa);
|
||||||
|
|
||||||
|
var errors: std.ArrayListUnmanaged(Compilation.Error) = .empty;
|
||||||
|
defer errors.deinit(gpa);
|
||||||
|
|
||||||
|
if (dump_ast) {
|
||||||
|
try io_w.writeAll("=== AST ===\n");
|
||||||
|
try tree.render(gpa, io_w, .{
|
||||||
|
.use_color = use_color,
|
||||||
|
});
|
||||||
|
try io_w.flush();
|
||||||
|
}
|
||||||
|
if (dump_ir) {
|
||||||
|
try io_w.writeAll("=== IR ===\n");
|
||||||
|
try ir.render(io_w);
|
||||||
|
try io_w.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
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(io_w, err);
|
||||||
|
}
|
||||||
|
} else if (output_path) |_| {
|
||||||
|
return error.NotImplemented;
|
||||||
|
} else {
|
||||||
|
var story: Story = try .fromCompilation(gpa, &cu, .{
|
||||||
|
.stack_size = stack_size,
|
||||||
|
});
|
||||||
|
defer story.deinit();
|
||||||
|
if (dump_story) {
|
||||||
|
try story.dump(io_w);
|
||||||
|
}
|
||||||
|
if (dump_trace) {
|
||||||
|
story.dump_writer = io_w;
|
||||||
|
}
|
||||||
|
return if (!compile_only) run(gpa, &story);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.inkc => {
|
||||||
|
var story: Story = try .fromCachedCompilation(gpa, source_bytes, .{
|
||||||
|
.stack_size = stack_size,
|
||||||
|
});
|
||||||
|
defer story.deinit();
|
||||||
|
return if (!compile_only) run(gpa, &story);
|
||||||
|
},
|
||||||
}
|
}
|
||||||
if (dump_story) {
|
|
||||||
try story.dump(&stdout_writer.interface);
|
|
||||||
}
|
|
||||||
return if (!compile_only) run(gpa, &story);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(_: std.mem.Allocator, story: *Story) !void {
|
fn run(_: std.mem.Allocator, story: *Story) !void {
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,9 @@ const std = @import("std");
|
||||||
const tokenizer = @import("tokenizer.zig");
|
const tokenizer = @import("tokenizer.zig");
|
||||||
pub const Story = @import("Story.zig");
|
pub const Story = @import("Story.zig");
|
||||||
pub const Ast = @import("Ast.zig");
|
pub const Ast = @import("Ast.zig");
|
||||||
pub const Module = @import("compile.zig").Module;
|
pub const AstGen = @import("AstGen.zig");
|
||||||
|
pub const Ir = @import("Ir.zig");
|
||||||
|
pub const Compilation = @import("compile.zig").Compilation;
|
||||||
|
|
||||||
pub const max_src_size = std.math.maxInt(u32);
|
pub const max_src_size = std.math.maxInt(u32);
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue