refactor: new direction for error reporting
This commit is contained in:
parent
72b686d750
commit
c940374f27
11 changed files with 758 additions and 582 deletions
62
src/Ast.zig
62
src/Ast.zig
|
|
@ -1,14 +1,17 @@
|
|||
//! Abstract Syntax Tree for Ink source code.
|
||||
const std = @import("std");
|
||||
const ink = @import("root.zig");
|
||||
const Tokenizer = @import("tokenizer.zig").Tokenizer;
|
||||
const Render = @import("Ast/Render.zig");
|
||||
const Parse = @import("Parse.zig");
|
||||
const Ast = @This();
|
||||
const assert = std.debug.assert;
|
||||
|
||||
filename: []const u8,
|
||||
source: []const u8,
|
||||
// TODO: Make this non-nullable. Empty files are valid.
|
||||
root: ?*Node = null,
|
||||
errors: []Error,
|
||||
errors: []const Error,
|
||||
|
||||
pub const Node = struct {
|
||||
tag: Tag,
|
||||
|
|
@ -215,24 +218,22 @@ pub const Error = struct {
|
|||
};
|
||||
|
||||
pub const Tag = enum {
|
||||
panic,
|
||||
unexpected_token,
|
||||
unknown_identifier,
|
||||
redefined_identifier,
|
||||
assignment_to_const,
|
||||
expected_token,
|
||||
expected_newline,
|
||||
expected_expression,
|
||||
expected_double_quote,
|
||||
expected_identifier,
|
||||
expected_expression,
|
||||
invalid_expression,
|
||||
invalid_lvalue,
|
||||
too_many_arguments,
|
||||
too_many_parameters,
|
||||
|
||||
invalid_else_stmt,
|
||||
unexpected_else_stmt,
|
||||
invalid_switch_case,
|
||||
unexpected_token,
|
||||
};
|
||||
|
||||
pub fn tokenSlice(err: Error, tree: Ast) []const u8 {
|
||||
assert(err.loc.start <= err.loc.end);
|
||||
return tree.source[err.loc.start..err.loc.end];
|
||||
}
|
||||
};
|
||||
|
||||
pub fn parse(
|
||||
|
|
@ -280,16 +281,35 @@ pub fn render(
|
|||
return Render.renderTree(gpa, writer, &ast, options);
|
||||
}
|
||||
|
||||
pub fn renderErrors(
|
||||
ast: Ast,
|
||||
gpa: std.mem.Allocator,
|
||||
writer: *std.Io.Writer,
|
||||
options: Render.Options,
|
||||
) !void {
|
||||
return Render.renderErrors(gpa, writer, &ast, options);
|
||||
pub fn renderError(tree: Ast, w: *std.Io.Writer, parse_error: Error) !void {
|
||||
switch (parse_error.tag) {
|
||||
.expected_token => try w.writeAll("expected token"),
|
||||
.expected_newline => try w.writeAll("expected newline"),
|
||||
.expected_double_quote => try w.writeAll("unterminated string, expected closing quote"),
|
||||
.expected_identifier => try w.writeAll("expected identifier"),
|
||||
.expected_expression => try w.writeAll("expected expression"),
|
||||
.unexpected_token => try w.writeAll("unexpected token"),
|
||||
.invalid_expression => try w.writeAll("invalid expression"),
|
||||
.invalid_lvalue => try w.writeAll("invalid lvalue for assignment"),
|
||||
.too_many_arguments => try w.print("too many arguments to '{s}'", .{
|
||||
errorSlice(tree, parse_error),
|
||||
}),
|
||||
.too_many_parameters => try w.print("too many parameters defined for '{s}'", .{
|
||||
errorSlice(tree, parse_error),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn deinit(ast: *Ast, gpa: std.mem.Allocator) void {
|
||||
gpa.free(ast.errors);
|
||||
ast.* = undefined;
|
||||
pub fn errorSlice(tree: Ast, parse_error: Ast.Error) []const u8 {
|
||||
return tree.source[parse_error.loc.start..parse_error.loc.end];
|
||||
}
|
||||
|
||||
pub fn nodeSlice(tree: Ast, node: *const Ast.Node) []const u8 {
|
||||
assert(node.loc.start <= node.loc.end);
|
||||
return tree.source[node.loc.start..node.loc.end];
|
||||
}
|
||||
|
||||
pub fn deinit(tree: *Ast, gpa: std.mem.Allocator) void {
|
||||
gpa.free(tree.errors);
|
||||
tree.* = undefined;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -123,62 +123,6 @@ fn nodeTagToString(tag: Ast.Node.Tag) []const u8 {
|
|||
};
|
||||
}
|
||||
|
||||
fn makeErrorInfo(r: *Render, err: Ast.Error, message: []const u8) ErrorInfo {
|
||||
const line_index = r.lines.calculateLine(err.loc.start);
|
||||
const snippet_range = r.lines.ranges.items[line_index];
|
||||
const column = err.loc.start - snippet_range.start;
|
||||
const snippet = r.tree.source[snippet_range.start..snippet_range.end];
|
||||
|
||||
return ErrorInfo{
|
||||
.filename = r.tree.filename,
|
||||
.message = message,
|
||||
.line = line_index,
|
||||
.column = column,
|
||||
.snippet = snippet,
|
||||
};
|
||||
}
|
||||
|
||||
fn renderErrorf(r: *Render, writer: *std.Io.Writer, err: Ast.Error, message: []const u8) !void {
|
||||
const error_info = makeErrorInfo(r, err, message);
|
||||
const filename = error_info.filename;
|
||||
const line = error_info.line + 1;
|
||||
const col = error_info.column + 1;
|
||||
const snippet = error_info.message;
|
||||
|
||||
try writer.print("{s}:{d}:{d}: error: {s}\n", .{ filename, line, col, snippet });
|
||||
try writer.print("{d:<4} | {s}\n", .{ line, error_info.snippet });
|
||||
try writer.writeAll(" | ");
|
||||
|
||||
var i: usize = 0;
|
||||
while (i < col) : (i += 1) {
|
||||
try writer.writeByte(' ');
|
||||
}
|
||||
|
||||
try writer.writeByte('^');
|
||||
try writer.writeAll("\n\n");
|
||||
}
|
||||
|
||||
fn renderError(r: *Render, writer: *std.Io.Writer, err: Ast.Error) !void {
|
||||
switch (err.tag) {
|
||||
.panic => try renderErrorf(r, writer, err, "parser panicked"),
|
||||
.unknown_identifier => try renderErrorf(r, writer, err, "unknown identifier"),
|
||||
.redefined_identifier => try renderErrorf(r, writer, err, "redefined identifier"),
|
||||
.assignment_to_const => try renderErrorf(r, writer, err, "assignment to constant value"),
|
||||
.unexpected_token => try renderErrorf(r, writer, err, "unexpected token"),
|
||||
.expected_newline => try renderErrorf(r, writer, err, "expected newline"),
|
||||
.expected_double_quote => try renderErrorf(r, writer, err, "unterminated string, expected closing quote"),
|
||||
.expected_identifier => try renderErrorf(r, writer, err, "expected identifier"),
|
||||
.expected_expression => try renderErrorf(r, writer, err, "expected expression"),
|
||||
.invalid_expression => try renderErrorf(r, writer, err, "invalid expression"),
|
||||
.invalid_lvalue => try renderErrorf(r, writer, err, "invalid lvalue for assignment"),
|
||||
.too_many_arguments => try renderErrorf(r, writer, err, "too many arguments to '{s}'"),
|
||||
.too_many_parameters => try renderErrorf(r, writer, err, "too many parameters defined for '{s}'"),
|
||||
.unexpected_else_stmt => try renderErrorf(r, writer, err, "unexpected else stmt"),
|
||||
.invalid_else_stmt => try renderErrorf(r, writer, err, "invalid else stmt"),
|
||||
.invalid_switch_case => try renderErrorf(r, writer, err, "invalid switch case expression"),
|
||||
}
|
||||
}
|
||||
|
||||
const Prefix = struct {
|
||||
buf: std.ArrayListUnmanaged(u8) = .empty,
|
||||
|
||||
|
|
@ -211,10 +155,8 @@ fn writeType(r: *Render, writer: *std.Io.Writer, node: *const Ast.Node) !void {
|
|||
}
|
||||
|
||||
fn writeLexeme(r: *Render, writer: *std.Io.Writer, node: *const Ast.Node) !void {
|
||||
const bytes = r.tree.source;
|
||||
const lexeme = bytes[node.loc.start..node.loc.end];
|
||||
try r.tty_config.setColor(writer, .yellow);
|
||||
try writer.print("`{s}`", .{lexeme});
|
||||
try writer.print("`{s}`", .{r.tree.nodeSlice(node)});
|
||||
try r.tty_config.setColor(writer, .reset);
|
||||
}
|
||||
|
||||
|
|
@ -549,23 +491,3 @@ pub fn renderTree(
|
|||
}
|
||||
try writer.flush();
|
||||
}
|
||||
|
||||
pub fn renderErrors(
|
||||
gpa: std.mem.Allocator,
|
||||
writer: *std.Io.Writer,
|
||||
ast: *const Ast,
|
||||
options: Options,
|
||||
) !void {
|
||||
var r: Render = .{
|
||||
.gpa = gpa,
|
||||
.tree = ast,
|
||||
.tty_config = if (options.use_color) .escape_codes else .no_color,
|
||||
.prefix = .{},
|
||||
.lines = try LineCache.build(gpa, ast.source),
|
||||
};
|
||||
defer r.prefix.deinit(gpa);
|
||||
defer r.lines.deinit(gpa);
|
||||
|
||||
for (ast.errors) |err| try r.renderError(writer, err);
|
||||
try writer.flush();
|
||||
}
|
||||
|
|
|
|||
528
src/AstGen.zig
528
src/AstGen.zig
|
|
@ -16,7 +16,7 @@ globals: std.ArrayListUnmanaged(Ir.Global) = .empty,
|
|||
global_ref_table: std.AutoHashMapUnmanaged(Ir.NullTerminatedString, usize) = .empty,
|
||||
extra: std.ArrayListUnmanaged(u32) = .empty,
|
||||
scratch: std.ArrayListUnmanaged(u32) = .empty,
|
||||
errors: std.ArrayListUnmanaged(Ast.Error) = .empty,
|
||||
compile_errors: std.ArrayListUnmanaged(Ir.Inst.CompileErrors.Item) = .empty,
|
||||
|
||||
pub const InnerError = error{
|
||||
OutOfMemory,
|
||||
|
|
@ -25,7 +25,239 @@ pub const InnerError = error{
|
|||
Overflow,
|
||||
};
|
||||
|
||||
pub fn deinit(astgen: *AstGen) void {
|
||||
/// Splat an IR data struct into the `extra` array.
|
||||
fn addExtra(astgen: *AstGen, extra: anytype) !u32 {
|
||||
const fields = std.meta.fields(@TypeOf(extra));
|
||||
try astgen.extra.ensureUnusedCapacity(astgen.gpa, fields.len);
|
||||
return addExtraAssumeCapacity(astgen, extra);
|
||||
}
|
||||
|
||||
/// Splat an IR data struct into the `extra` array.
|
||||
fn addExtraAssumeCapacity(astgen: *AstGen, extra: anytype) u32 {
|
||||
const fields = std.meta.fields(@TypeOf(extra));
|
||||
const extra_index: u32 = @intCast(astgen.extra.items.len);
|
||||
astgen.extra.items.len += fields.len;
|
||||
setExtra(astgen, extra_index, extra);
|
||||
return extra_index;
|
||||
}
|
||||
|
||||
fn setExtra(astgen: *AstGen, index: usize, extra: anytype) void {
|
||||
const fields = std.meta.fields(@TypeOf(extra));
|
||||
var i = index;
|
||||
inline for (fields) |field| {
|
||||
astgen.extra.items[i] = switch (field.type) {
|
||||
u32 => @field(extra, field.name),
|
||||
Ir.Inst.Index => @intFromEnum(@field(extra, field.name)),
|
||||
Ir.Inst.Ref => @intFromEnum(@field(extra, field.name)),
|
||||
Ir.NullTerminatedString => @intFromEnum(@field(extra, field.name)),
|
||||
else => @compileError("bad field type"),
|
||||
};
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
|
||||
fn appendBlockBody(astgen: *AstGen, body: []const Ir.Inst.Index) void {
|
||||
return appendBlockBodyArrayList(astgen, &astgen.extra, body);
|
||||
}
|
||||
|
||||
fn appendBlockBodyArrayList(
|
||||
_: *AstGen,
|
||||
list: *std.ArrayListUnmanaged(u32),
|
||||
body: []const Ir.Inst.Index,
|
||||
) void {
|
||||
for (body) |inst_index| {
|
||||
list.appendAssumeCapacity(@intFromEnum(inst_index));
|
||||
}
|
||||
}
|
||||
fn appendErrorNode(
|
||||
astgen: *AstGen,
|
||||
node: *const Ast.Node,
|
||||
comptime format: []const u8,
|
||||
args: anytype,
|
||||
) error{OutOfMemory}!void {
|
||||
return appendErrorToken(astgen, @intCast(node.loc.start), format, args);
|
||||
}
|
||||
|
||||
fn appendErrorToken(
|
||||
astgen: *AstGen,
|
||||
byte_offset: u32,
|
||||
comptime format: []const u8,
|
||||
args: anytype,
|
||||
) error{OutOfMemory}!void {
|
||||
const gpa = astgen.gpa;
|
||||
const string_bytes = &astgen.string_bytes;
|
||||
const msg: Ir.NullTerminatedString = @enumFromInt(string_bytes.items.len);
|
||||
try string_bytes.print(gpa, format ++ "\x00", args);
|
||||
|
||||
try astgen.compile_errors.append(gpa, .{
|
||||
.msg = msg,
|
||||
.byte_offset = byte_offset,
|
||||
});
|
||||
}
|
||||
|
||||
fn fail(
|
||||
astgen: *AstGen,
|
||||
node: *const Ast.Node,
|
||||
comptime format: []const u8,
|
||||
args: anytype,
|
||||
) error{ SemanticError, OutOfMemory } {
|
||||
try appendErrorNode(astgen, node, format, args);
|
||||
return error.SemanticError;
|
||||
}
|
||||
|
||||
fn lowerAstErrors(astgen: *AstGen) error{OutOfMemory}!void {
|
||||
const gpa = astgen.gpa;
|
||||
var msg: std.Io.Writer.Allocating = .init(gpa);
|
||||
defer msg.deinit();
|
||||
const w = &msg.writer;
|
||||
|
||||
for (astgen.tree.errors) |err| {
|
||||
astgen.tree.renderError(w, err) catch return error.OutOfMemory;
|
||||
try appendErrorToken(
|
||||
astgen,
|
||||
@intCast(err.loc.start),
|
||||
"{s}",
|
||||
.{msg.written()},
|
||||
);
|
||||
msg.clearRetainingCapacity();
|
||||
}
|
||||
}
|
||||
|
||||
fn nullTerminatedString(astgen: *AstGen, str: Ir.NullTerminatedString) [:0]const u8 {
|
||||
const slice = astgen.string_bytes.items[@intFromEnum(str)..];
|
||||
return slice[0..std.mem.indexOfScalar(u8, slice, 0).? :0];
|
||||
}
|
||||
|
||||
fn stringFromBytes(astgen: *AstGen, bytes: []const u8) error{OutOfMemory}!Ir.NullTerminatedString {
|
||||
const gpa = astgen.gpa;
|
||||
const string_bytes = &astgen.string_bytes;
|
||||
const str_index: u32 = @intCast(string_bytes.items.len);
|
||||
try string_bytes.appendSlice(gpa, bytes);
|
||||
|
||||
const key: []const u8 = string_bytes.items[str_index..];
|
||||
const gop = try astgen.string_table.getOrPutContextAdapted(gpa, key, StringIndexAdapter{
|
||||
.bytes = string_bytes,
|
||||
}, StringIndexContext{
|
||||
.bytes = string_bytes,
|
||||
});
|
||||
if (gop.found_existing) {
|
||||
string_bytes.shrinkRetainingCapacity(str_index);
|
||||
return @enumFromInt(gop.key_ptr.*);
|
||||
} else {
|
||||
gop.key_ptr.* = str_index;
|
||||
try string_bytes.append(gpa, 0);
|
||||
return @enumFromInt(str_index);
|
||||
}
|
||||
}
|
||||
|
||||
fn stringFromNode(astgen: *AstGen, node: *const Ast.Node) !Ir.NullTerminatedString {
|
||||
const name_bytes = astgen.tree.nodeSlice(node);
|
||||
return astgen.stringFromBytes(name_bytes);
|
||||
}
|
||||
|
||||
fn qualifiedString(
|
||||
astgen: *AstGen,
|
||||
prefix: Ir.NullTerminatedString,
|
||||
relative: Ir.NullTerminatedString,
|
||||
) !Ir.NullTerminatedString {
|
||||
const gpa = astgen.gpa;
|
||||
const string_bytes = &astgen.string_bytes;
|
||||
const string_table = &astgen.string_table;
|
||||
const str_index: u32 = @intCast(string_bytes.items.len);
|
||||
|
||||
switch (prefix) {
|
||||
.empty => return relative,
|
||||
else => |prev| {
|
||||
try string_bytes.appendSlice(gpa, nullTerminatedString(astgen, prev));
|
||||
try string_bytes.append(gpa, '.');
|
||||
try string_bytes.appendSlice(gpa, nullTerminatedString(astgen, relative));
|
||||
|
||||
const key: []const u8 = string_bytes.items[str_index..];
|
||||
const gop = try string_table.getOrPutContextAdapted(gpa, key, StringIndexAdapter{
|
||||
.bytes = string_bytes,
|
||||
}, StringIndexContext{
|
||||
.bytes = string_bytes,
|
||||
});
|
||||
if (gop.found_existing) {
|
||||
string_bytes.shrinkRetainingCapacity(str_index);
|
||||
return @enumFromInt(gop.key_ptr.*);
|
||||
} else {
|
||||
gop.key_ptr.* = str_index;
|
||||
try string_bytes.append(gpa, 0);
|
||||
return @enumFromInt(str_index);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Perform IR code generation via tree-walk.
|
||||
pub fn generate(gpa: std.mem.Allocator, tree: *const Ast) !Ir {
|
||||
var astgen: AstGen = .{
|
||||
.gpa = gpa,
|
||||
.tree = tree,
|
||||
};
|
||||
defer astgen.deinit();
|
||||
|
||||
// First entry is reserved for Ir.NullTerminatedString.empty.
|
||||
try astgen.string_bytes.append(gpa, 0);
|
||||
|
||||
var instructions: std.ArrayListUnmanaged(Ir.Inst.Index) = .empty;
|
||||
defer instructions.deinit(gpa);
|
||||
var file_scope: Scope = .{
|
||||
.parent = null,
|
||||
.decls = .empty,
|
||||
.namespace_prefix = .empty,
|
||||
.astgen = &astgen,
|
||||
};
|
||||
var block: GenIr = .{
|
||||
.astgen = &astgen,
|
||||
.instructions = &instructions,
|
||||
.instructions_top = 0,
|
||||
};
|
||||
defer block.unstack();
|
||||
|
||||
const reserved_extra_count = @typeInfo(Ir.ExtraIndex).@"enum".fields.len;
|
||||
try astgen.extra.ensureTotalCapacity(gpa, reserved_extra_count);
|
||||
astgen.extra.items.len += reserved_extra_count;
|
||||
|
||||
const fatal = if (tree.errors.len == 0) fatal: {
|
||||
// TODO: Make sure this is never null.
|
||||
const root_node = tree.root.?;
|
||||
file(&block, &file_scope, root_node) catch |err| switch (err) {
|
||||
error.OutOfMemory => return error.OutOfMemory,
|
||||
error.SemanticError => break :fatal true,
|
||||
else => |e| return e,
|
||||
};
|
||||
break :fatal false;
|
||||
} else fatal: {
|
||||
try lowerAstErrors(&astgen);
|
||||
break :fatal true;
|
||||
};
|
||||
|
||||
const err_index = @intFromEnum(Ir.ExtraIndex.compile_errors);
|
||||
if (astgen.compile_errors.items.len == 0) {
|
||||
astgen.extra.items[err_index] = 0;
|
||||
} else {
|
||||
const extra_len = 1 + astgen.compile_errors.items.len *
|
||||
@typeInfo(Ir.Inst.CompileErrors.Item).@"struct".fields.len;
|
||||
try astgen.extra.ensureUnusedCapacity(gpa, extra_len);
|
||||
|
||||
astgen.extra.items[err_index] = astgen.addExtraAssumeCapacity(Ir.Inst.CompileErrors{
|
||||
.items_len = @intCast(astgen.compile_errors.items.len),
|
||||
});
|
||||
for (astgen.compile_errors.items) |item| {
|
||||
_ = astgen.addExtraAssumeCapacity(item);
|
||||
}
|
||||
}
|
||||
return .{
|
||||
.instructions = if (fatal) &.{} else try astgen.instructions.toOwnedSlice(gpa),
|
||||
.string_bytes = try astgen.string_bytes.toOwnedSlice(gpa),
|
||||
.globals = try astgen.globals.toOwnedSlice(gpa),
|
||||
.extra = try astgen.extra.toOwnedSlice(gpa),
|
||||
};
|
||||
}
|
||||
|
||||
fn deinit(astgen: *AstGen) void {
|
||||
const gpa = astgen.gpa;
|
||||
astgen.string_table.deinit(gpa);
|
||||
astgen.string_bytes.deinit(gpa);
|
||||
|
|
@ -34,54 +266,9 @@ pub fn deinit(astgen: *AstGen) void {
|
|||
astgen.instructions.deinit(gpa);
|
||||
astgen.extra.deinit(gpa);
|
||||
astgen.scratch.deinit(gpa);
|
||||
astgen.errors.deinit(gpa);
|
||||
astgen.compile_errors.deinit(gpa);
|
||||
}
|
||||
|
||||
const Scope = struct {
|
||||
parent: ?*Scope,
|
||||
astgen: *AstGen,
|
||||
namespace_prefix: Ir.NullTerminatedString,
|
||||
decls: std.AutoHashMapUnmanaged(Ir.NullTerminatedString, Decl),
|
||||
|
||||
const Decl = struct {
|
||||
decl_node: *const Ast.Node,
|
||||
inst_index: Ir.Inst.Index,
|
||||
};
|
||||
|
||||
fn deinit(self: *Scope) void {
|
||||
const gpa = self.astgen.gpa;
|
||||
self.decls.deinit(gpa);
|
||||
}
|
||||
|
||||
fn makeChild(parent_scope: *Scope) Scope {
|
||||
return .{
|
||||
.parent = parent_scope,
|
||||
.astgen = parent_scope.astgen,
|
||||
.namespace_prefix = parent_scope.namespace_prefix,
|
||||
.decls = .empty,
|
||||
};
|
||||
}
|
||||
|
||||
fn setNamespacePrefix(scope: *Scope, relative: Ir.NullTerminatedString) !void {
|
||||
const astgen = scope.astgen;
|
||||
scope.namespace_prefix = try astgen.qualifiedString(scope.namespace_prefix, relative);
|
||||
}
|
||||
|
||||
fn insert(self: *Scope, ref: Ir.NullTerminatedString, decl: Decl) !void {
|
||||
const gpa = self.astgen.gpa;
|
||||
return self.decls.put(gpa, ref, decl);
|
||||
}
|
||||
|
||||
fn lookup(self: *Scope, ref: Ir.NullTerminatedString) ?Decl {
|
||||
var current_scope: ?*Scope = self;
|
||||
while (current_scope) |scope| : (current_scope = scope.parent) {
|
||||
const result = scope.decls.get(ref);
|
||||
if (result) |symbol| return symbol;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const GenIr = struct {
|
||||
astgen: *AstGen,
|
||||
instructions: *std.ArrayListUnmanaged(Ir.Inst.Index),
|
||||
|
|
@ -121,14 +308,6 @@ const GenIr = struct {
|
|||
self.instructions.items[self.instructions_top..];
|
||||
}
|
||||
|
||||
fn fail(
|
||||
self: *GenIr,
|
||||
tag: Ast.Error.Tag,
|
||||
node: *const Ast.Node,
|
||||
) error{ SemanticError, OutOfMemory } {
|
||||
return self.astgen.fail(tag, node);
|
||||
}
|
||||
|
||||
fn makeSubBlock(self: *GenIr) GenIr {
|
||||
return .{
|
||||
.astgen = self.astgen,
|
||||
|
|
@ -326,50 +505,50 @@ const GenIr = struct {
|
|||
}
|
||||
};
|
||||
|
||||
/// Splat an IR data struct into the `extra` array.
|
||||
fn addExtra(astgen: *AstGen, extra: anytype) !u32 {
|
||||
const fields = std.meta.fields(@TypeOf(extra));
|
||||
try astgen.extra.ensureUnusedCapacity(astgen.gpa, fields.len);
|
||||
return addExtraAssumeCapacity(astgen, extra);
|
||||
}
|
||||
const Scope = struct {
|
||||
parent: ?*Scope,
|
||||
astgen: *AstGen,
|
||||
namespace_prefix: Ir.NullTerminatedString,
|
||||
decls: std.AutoHashMapUnmanaged(Ir.NullTerminatedString, Decl),
|
||||
|
||||
/// Splat an IR data struct into the `extra` array.
|
||||
fn addExtraAssumeCapacity(astgen: *AstGen, extra: anytype) u32 {
|
||||
const fields = std.meta.fields(@TypeOf(extra));
|
||||
const extra_index: u32 = @intCast(astgen.extra.items.len);
|
||||
astgen.extra.items.len += fields.len;
|
||||
setExtra(astgen, extra_index, extra);
|
||||
return extra_index;
|
||||
}
|
||||
const Decl = struct {
|
||||
decl_node: *const Ast.Node,
|
||||
inst_index: Ir.Inst.Index,
|
||||
};
|
||||
|
||||
fn setExtra(astgen: *AstGen, index: usize, extra: anytype) void {
|
||||
const fields = std.meta.fields(@TypeOf(extra));
|
||||
var i = index;
|
||||
inline for (fields) |field| {
|
||||
astgen.extra.items[i] = switch (field.type) {
|
||||
u32 => @field(extra, field.name),
|
||||
Ir.Inst.Index => @intFromEnum(@field(extra, field.name)),
|
||||
Ir.Inst.Ref => @intFromEnum(@field(extra, field.name)),
|
||||
Ir.NullTerminatedString => @intFromEnum(@field(extra, field.name)),
|
||||
else => @compileError("bad field type"),
|
||||
fn deinit(self: *Scope) void {
|
||||
const gpa = self.astgen.gpa;
|
||||
self.decls.deinit(gpa);
|
||||
}
|
||||
|
||||
fn makeChild(parent_scope: *Scope) Scope {
|
||||
return .{
|
||||
.parent = parent_scope,
|
||||
.astgen = parent_scope.astgen,
|
||||
.namespace_prefix = parent_scope.namespace_prefix,
|
||||
.decls = .empty,
|
||||
};
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
|
||||
fn appendBlockBody(astgen: *AstGen, body: []const Ir.Inst.Index) void {
|
||||
return appendBlockBodyArrayList(astgen, &astgen.extra, body);
|
||||
}
|
||||
|
||||
fn appendBlockBodyArrayList(
|
||||
_: *AstGen,
|
||||
list: *std.ArrayListUnmanaged(u32),
|
||||
body: []const Ir.Inst.Index,
|
||||
) void {
|
||||
for (body) |inst_index| {
|
||||
list.appendAssumeCapacity(@intFromEnum(inst_index));
|
||||
fn setNamespacePrefix(scope: *Scope, relative: Ir.NullTerminatedString) !void {
|
||||
const astgen = scope.astgen;
|
||||
scope.namespace_prefix = try astgen.qualifiedString(scope.namespace_prefix, relative);
|
||||
}
|
||||
}
|
||||
|
||||
fn insert(self: *Scope, ref: Ir.NullTerminatedString, decl: Decl) !void {
|
||||
const gpa = self.astgen.gpa;
|
||||
return self.decls.put(gpa, ref, decl);
|
||||
}
|
||||
|
||||
fn lookup(self: *Scope, ref: Ir.NullTerminatedString) ?Decl {
|
||||
var current_scope: ?*Scope = self;
|
||||
while (current_scope) |scope| : (current_scope = scope.parent) {
|
||||
const result = scope.decls.get(ref);
|
||||
if (result) |symbol| return symbol;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
fn setDeclaration(
|
||||
decl_index: Ir.Inst.Index,
|
||||
|
|
@ -392,7 +571,7 @@ fn setDeclaration(
|
|||
try astgen.global_ref_table.ensureUnusedCapacity(gpa, 1);
|
||||
|
||||
if (astgen.global_ref_table.get(args.name)) |_| {
|
||||
return astgen.fail(.redefined_identifier, args.decl_node);
|
||||
return fail(astgen, args.decl_node, "redefined identifier", .{});
|
||||
}
|
||||
|
||||
const inst_data = &astgen.instructions.items[@intFromEnum(decl_index)].data;
|
||||
|
|
@ -439,98 +618,6 @@ fn setCondBrPayload(
|
|||
astgen.appendBlockBody(else_body);
|
||||
}
|
||||
|
||||
fn fail(
|
||||
self: *AstGen,
|
||||
tag: Ast.Error.Tag,
|
||||
source_node: *const Ast.Node,
|
||||
) error{ SemanticError, OutOfMemory } {
|
||||
const gpa = self.gpa;
|
||||
const err: Ast.Error = .{
|
||||
.tag = tag,
|
||||
.loc = .{
|
||||
.start = source_node.loc.start,
|
||||
.end = source_node.loc.end,
|
||||
},
|
||||
};
|
||||
|
||||
try self.errors.append(gpa, err);
|
||||
return error.SemanticError;
|
||||
}
|
||||
|
||||
fn sliceFromNode(astgen: *const AstGen, node: *const Ast.Node) []const u8 {
|
||||
assert(node.loc.start <= node.loc.end);
|
||||
const source_bytes = astgen.tree.source;
|
||||
return source_bytes[node.loc.start..node.loc.end];
|
||||
}
|
||||
|
||||
fn stringFromBytes(astgen: *AstGen, bytes: []const u8) error{OutOfMemory}!Ir.NullTerminatedString {
|
||||
const gpa = astgen.gpa;
|
||||
const string_bytes = &astgen.string_bytes;
|
||||
const str_index: u32 = @intCast(string_bytes.items.len);
|
||||
try string_bytes.appendSlice(gpa, bytes);
|
||||
|
||||
const key: []const u8 = string_bytes.items[str_index..];
|
||||
const gop = try astgen.string_table.getOrPutContextAdapted(gpa, key, StringIndexAdapter{
|
||||
.bytes = string_bytes,
|
||||
}, StringIndexContext{
|
||||
.bytes = string_bytes,
|
||||
});
|
||||
if (gop.found_existing) {
|
||||
string_bytes.shrinkRetainingCapacity(str_index);
|
||||
return @enumFromInt(gop.key_ptr.*);
|
||||
} else {
|
||||
gop.key_ptr.* = str_index;
|
||||
try string_bytes.append(gpa, 0);
|
||||
return @enumFromInt(str_index);
|
||||
}
|
||||
}
|
||||
|
||||
fn stringFromNode(astgen: *AstGen, node: *const Ast.Node) !Ir.NullTerminatedString {
|
||||
const name_bytes = sliceFromNode(astgen, node);
|
||||
assert(name_bytes.len > 0);
|
||||
return astgen.stringFromBytes(name_bytes);
|
||||
}
|
||||
|
||||
fn nullTerminatedString(astgen: *AstGen, str: Ir.NullTerminatedString) [:0]const u8 {
|
||||
const slice = astgen.string_bytes.items[@intFromEnum(str)..];
|
||||
return slice[0..std.mem.indexOfScalar(u8, slice, 0).? :0];
|
||||
}
|
||||
|
||||
fn qualifiedString(
|
||||
astgen: *AstGen,
|
||||
prefix: Ir.NullTerminatedString,
|
||||
relative: Ir.NullTerminatedString,
|
||||
) !Ir.NullTerminatedString {
|
||||
const gpa = astgen.gpa;
|
||||
const string_bytes = &astgen.string_bytes;
|
||||
const string_table = &astgen.string_table;
|
||||
const str_index: u32 = @intCast(string_bytes.items.len);
|
||||
|
||||
switch (prefix) {
|
||||
.empty => return relative,
|
||||
else => |prev| {
|
||||
try string_bytes.appendSlice(gpa, nullTerminatedString(astgen, prev));
|
||||
try string_bytes.append(gpa, '.');
|
||||
try string_bytes.appendSlice(gpa, nullTerminatedString(astgen, relative));
|
||||
|
||||
const key: []const u8 = string_bytes.items[str_index..];
|
||||
const gop = try string_table.getOrPutContextAdapted(gpa, key, StringIndexAdapter{
|
||||
.bytes = string_bytes,
|
||||
}, StringIndexContext{
|
||||
.bytes = string_bytes,
|
||||
});
|
||||
if (gop.found_existing) {
|
||||
string_bytes.shrinkRetainingCapacity(str_index);
|
||||
return @enumFromInt(gop.key_ptr.*);
|
||||
} else {
|
||||
gop.key_ptr.* = str_index;
|
||||
try string_bytes.append(gpa, 0);
|
||||
return @enumFromInt(str_index);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn unaryOp(
|
||||
gi: *GenIr,
|
||||
scope: *Scope,
|
||||
|
|
@ -581,10 +668,10 @@ fn logicalOp(
|
|||
gen.setLabel(else_label);
|
||||
}
|
||||
|
||||
fn numberLiteral(gen: *GenIr, node: *const Ast.Node) InnerError!Ir.Inst.Ref {
|
||||
const lexeme = sliceFromNode(gen.astgen, node);
|
||||
fn numberLiteral(block: *GenIr, node: *const Ast.Node) InnerError!Ir.Inst.Ref {
|
||||
const lexeme = block.astgen.tree.nodeSlice(node);
|
||||
const int_value = try std.fmt.parseUnsigned(u64, lexeme, 10);
|
||||
return gen.addInt(int_value);
|
||||
return block.addInt(int_value);
|
||||
}
|
||||
|
||||
fn stringLiteral(gi: *GenIr, node: *const Ast.Node) InnerError!Ir.Inst.Ref {
|
||||
|
|
@ -694,6 +781,7 @@ fn inlineLogicExpr(gi: *GenIr, scope: *Scope, node: *const Ast.Node) InnerError!
|
|||
}
|
||||
|
||||
fn validateSwitchProngs(gen: *GenIr, stmt_node: *const Ast.Node) InnerError!void {
|
||||
const astgen = gen.astgen;
|
||||
var stmt_has_block: bool = false;
|
||||
var stmt_has_else: bool = false;
|
||||
const case_list = stmt_node.data.switch_stmt.cases;
|
||||
|
|
@ -708,10 +796,10 @@ fn validateSwitchProngs(gen: *GenIr, stmt_node: *const Ast.Node) InnerError!void
|
|||
},
|
||||
.else_branch => {
|
||||
if (case_stmt != last_prong) {
|
||||
return gen.fail(.invalid_else_stmt, case_stmt);
|
||||
return fail(astgen, case_stmt, "invalid else stmt", .{});
|
||||
}
|
||||
if (stmt_has_else) {
|
||||
return gen.fail(.unexpected_else_stmt, case_stmt);
|
||||
return fail(astgen, case_stmt, "duplicate else stmt", .{});
|
||||
}
|
||||
stmt_has_else = true;
|
||||
},
|
||||
|
|
@ -845,7 +933,7 @@ fn switchStmt(
|
|||
.number_literal => try numberLiteral(parent_block, case_expr),
|
||||
.true_literal => .bool_true,
|
||||
.false_literal => .bool_false,
|
||||
else => return parent_block.fail(.invalid_switch_case, case_stmt),
|
||||
else => return fail(astgen, case_expr, "invalid switch case", .{}),
|
||||
};
|
||||
var case_block = parent_block.makeSubBlock();
|
||||
defer case_block.unstack();
|
||||
|
|
@ -934,7 +1022,7 @@ fn assignStmt(gi: *GenIr, scope: *Scope, node: *const Ast.Node) InnerError!void
|
|||
_ = try gi.addBinaryNode(.store, decl.inst_index.toRef(), expr_result);
|
||||
return;
|
||||
}
|
||||
return gi.fail(.unknown_identifier, identifier_node);
|
||||
return fail(astgen, identifier_node, "unknown identifier", .{});
|
||||
}
|
||||
|
||||
fn choiceStarStmt(gi: *GenIr, _: *Scope, node: *const Ast.Node) InnerError!Ir.Inst.Ref {
|
||||
|
|
@ -1174,12 +1262,13 @@ fn divertStmt(gi: *GenIr, scope: *Scope, node: *const Ast.Node) !void {
|
|||
}
|
||||
|
||||
fn tempDecl(gi: *GenIr, scope: *Scope, decl_node: *const Ast.Node) !void {
|
||||
const astgen = gi.astgen;
|
||||
const identifier_node = decl_node.data.bin.lhs.?;
|
||||
const expr_node = decl_node.data.bin.rhs.?;
|
||||
const name_ref = try gi.astgen.stringFromNode(identifier_node);
|
||||
const name_ref = try astgen.stringFromNode(identifier_node);
|
||||
|
||||
if (scope.lookup(name_ref)) |_| {
|
||||
return gi.fail(.redefined_identifier, decl_node);
|
||||
return fail(astgen, decl_node, "duplicate identifier", .{});
|
||||
}
|
||||
|
||||
const alloc_inst = try gi.add(.{ .tag = .alloc, .data = undefined });
|
||||
|
|
@ -1371,9 +1460,7 @@ fn file(gi: *GenIr, scope: *Scope, file_node: *const Ast.Node) InnerError!void {
|
|||
const file_inst = try gi.addAsIndex(.{
|
||||
.tag = .file,
|
||||
.data = .{
|
||||
.payload = .{
|
||||
.payload_index = undefined,
|
||||
},
|
||||
.payload = .{ .payload_index = undefined },
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -1403,46 +1490,3 @@ fn file(gi: *GenIr, scope: *Scope, file_node: *const Ast.Node) InnerError!void {
|
|||
}
|
||||
return file_scope.setBlockBody(file_inst);
|
||||
}
|
||||
|
||||
/// Perform code generation via tree-walk.
|
||||
pub fn generate(gpa: std.mem.Allocator, tree: *const Ast) !Ir {
|
||||
var astgen: AstGen = .{
|
||||
.gpa = gpa,
|
||||
.tree = tree,
|
||||
};
|
||||
defer astgen.deinit();
|
||||
|
||||
// First entry is reserved for Ir.NullTerminatedString.empty.
|
||||
try astgen.string_bytes.append(gpa, 0);
|
||||
|
||||
var instructions: std.ArrayListUnmanaged(Ir.Inst.Index) = .empty;
|
||||
defer instructions.deinit(gpa);
|
||||
|
||||
var file_scope: Scope = .{
|
||||
.parent = null,
|
||||
.decls = .empty,
|
||||
.namespace_prefix = .empty,
|
||||
.astgen = &astgen,
|
||||
};
|
||||
var gen: GenIr = .{
|
||||
.astgen = &astgen,
|
||||
.instructions = &instructions,
|
||||
.instructions_top = 0,
|
||||
};
|
||||
defer gen.unstack();
|
||||
|
||||
// TODO: Make sure this is never null.
|
||||
const root_node = tree.root.?;
|
||||
file(&gen, &file_scope, root_node) catch |err| switch (err) {
|
||||
error.SemanticError => {},
|
||||
else => |e| return e,
|
||||
};
|
||||
|
||||
return .{
|
||||
.string_bytes = try astgen.string_bytes.toOwnedSlice(gpa),
|
||||
.instructions = try astgen.instructions.toOwnedSlice(gpa),
|
||||
.globals = try astgen.globals.toOwnedSlice(gpa),
|
||||
.extra = try astgen.extra.toOwnedSlice(gpa),
|
||||
.errors = try astgen.errors.toOwnedSlice(gpa),
|
||||
};
|
||||
}
|
||||
|
|
|
|||
215
src/Ir.zig
215
src/Ir.zig
|
|
@ -4,16 +4,122 @@ const Writer = @import("print_ir.zig").Writer;
|
|||
const assert = std.debug.assert;
|
||||
const Ir = @This();
|
||||
|
||||
string_bytes: []u8,
|
||||
/// List of IR instructions for the translation unit.
|
||||
instructions: []Inst,
|
||||
globals: []Global,
|
||||
/// Interned string bytes. All strings are null-terminated.
|
||||
/// Index 0 is reserved for the empty string.
|
||||
string_bytes: []u8,
|
||||
/// Ancillary data for instructions. The meaning of this data is determined by
|
||||
/// the value of `Inst.Tag`. See `ExtraIndex` for the values of reserved indexes.
|
||||
extra: []u32,
|
||||
errors: []Ast.Error,
|
||||
globals: []Global,
|
||||
|
||||
pub const ExtraIndex = enum(u32) {
|
||||
/// If this is 0, no compile errors. Otherwise there is a `CompileErrors`
|
||||
/// payload at this index.
|
||||
compile_errors,
|
||||
_,
|
||||
};
|
||||
|
||||
fn ExtraData(comptime T: type) type {
|
||||
return struct {
|
||||
data: T,
|
||||
end: usize,
|
||||
};
|
||||
}
|
||||
|
||||
/// Extract a slice of `extra` into an `Inst` payload structure.
|
||||
pub fn extraData(ir: Ir, comptime T: type, index: usize) ExtraData(T) {
|
||||
const fields = @typeInfo(T).@"struct".fields;
|
||||
var i: usize = index;
|
||||
var result: T = undefined;
|
||||
inline for (fields) |field| {
|
||||
@field(result, field.name) = switch (field.type) {
|
||||
u32 => ir.extra[i],
|
||||
Inst.Index => @enumFromInt(ir.extra[i]),
|
||||
Inst.Ref => @enumFromInt(ir.extra[i]),
|
||||
NullTerminatedString => @enumFromInt(ir.extra[i]),
|
||||
else => @compileError("bad field type"),
|
||||
};
|
||||
i += 1;
|
||||
}
|
||||
return .{ .data = result, .end = i };
|
||||
}
|
||||
|
||||
pub const NullTerminatedString = enum(u32) {
|
||||
empty,
|
||||
_,
|
||||
};
|
||||
|
||||
pub fn nullTerminatedString(ir: Ir, index: NullTerminatedString) [:0]const u8 {
|
||||
const slice = ir.string_bytes[@intFromEnum(index)..];
|
||||
return slice[0..std.mem.indexOfScalar(u8, slice, 0).? :0];
|
||||
}
|
||||
|
||||
pub fn bodySlice(ir: Ir, start: usize, len: usize) []Inst.Index {
|
||||
return @ptrCast(ir.extra[start..][0..len]);
|
||||
}
|
||||
|
||||
pub fn hasCompileErrors(ir: Ir) bool {
|
||||
if (ir.extra[@intFromEnum(ExtraIndex.compile_errors)] != 0) {
|
||||
return true;
|
||||
} else {
|
||||
assert(ir.instructions.len != 0);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render(ir: Ir, writer: *std.Io.Writer) !void {
|
||||
if (ir.instructions.len > 0) {
|
||||
var w: Writer = .{ .code = ir };
|
||||
try w.writeInst(writer, .file_inst);
|
||||
return writer.flush();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dumpInfo(ir: Ir, writer: *std.Io.Writer) !void {
|
||||
const bytes = ir.string_bytes;
|
||||
|
||||
var start: usize = 0;
|
||||
while (start < bytes.len) {
|
||||
const end = std.mem.indexOfScalarPos(u8, bytes, start, 0) orelse break;
|
||||
const str = bytes[start..end];
|
||||
|
||||
try writer.print("[{d:04}] ", .{start});
|
||||
for (str) |b| try writer.print("{x:02} ", .{b});
|
||||
try writer.print("00: {s}\n", .{str});
|
||||
start = end + 1;
|
||||
}
|
||||
for (ir.globals) |global| {
|
||||
try writer.print("{any}\n", .{global});
|
||||
}
|
||||
return writer.flush();
|
||||
}
|
||||
|
||||
pub fn deinit(ir: *Ir, gpa: std.mem.Allocator) void {
|
||||
gpa.free(ir.string_bytes);
|
||||
gpa.free(ir.instructions);
|
||||
gpa.free(ir.globals);
|
||||
gpa.free(ir.extra);
|
||||
ir.* = undefined;
|
||||
}
|
||||
|
||||
pub const Global = struct {
|
||||
tag: Tag,
|
||||
name: Ir.NullTerminatedString,
|
||||
is_constant: bool,
|
||||
|
||||
pub const Tag = enum {
|
||||
knot,
|
||||
variable,
|
||||
};
|
||||
};
|
||||
|
||||
pub const Inst = struct {
|
||||
tag: Tag,
|
||||
data: Data,
|
||||
|
||||
/// An index to an IR instruction. Some values are reserved.
|
||||
pub const Index = enum(u32) {
|
||||
file_inst,
|
||||
ref_start_index = 32,
|
||||
|
|
@ -24,6 +130,15 @@ pub const Inst = struct {
|
|||
}
|
||||
};
|
||||
|
||||
/// A reference to an IR instruction, or to an statically interned value,
|
||||
/// or neither.
|
||||
///
|
||||
/// If the integer tag value is < `Index.ref_start_index`, then it
|
||||
/// corresponds to an interned value. Otherwise, this refers to a IR
|
||||
/// instruction.
|
||||
///
|
||||
/// The tag type is specified so that it is safe to bitcast between `[]u32`
|
||||
/// and `[]Ref`.
|
||||
pub const Ref = enum(u32) {
|
||||
bool_true,
|
||||
bool_false,
|
||||
|
|
@ -174,93 +289,13 @@ pub const Inst = struct {
|
|||
obj_ptr: Ref,
|
||||
field_name_start: NullTerminatedString,
|
||||
};
|
||||
};
|
||||
|
||||
pub const Global = struct {
|
||||
tag: Tag,
|
||||
name: Ir.NullTerminatedString,
|
||||
is_constant: bool,
|
||||
pub const CompileErrors = struct {
|
||||
items_len: u32,
|
||||
|
||||
pub const Tag = enum {
|
||||
knot,
|
||||
variable,
|
||||
};
|
||||
};
|
||||
|
||||
pub const NullTerminatedString = enum(u32) {
|
||||
empty,
|
||||
_,
|
||||
};
|
||||
|
||||
pub const IndexSlice = struct {
|
||||
index: u32,
|
||||
len: u32,
|
||||
};
|
||||
|
||||
pub fn deinit(ir: *Ir, gpa: std.mem.Allocator) void {
|
||||
gpa.free(ir.string_bytes);
|
||||
gpa.free(ir.instructions);
|
||||
gpa.free(ir.globals);
|
||||
gpa.free(ir.extra);
|
||||
gpa.free(ir.errors);
|
||||
ir.* = undefined;
|
||||
}
|
||||
|
||||
pub fn render(ir: Ir, writer: *std.Io.Writer) !void {
|
||||
assert(ir.instructions.len > 0);
|
||||
var w: Writer = .{ .code = ir };
|
||||
try w.writeInst(writer, .file_inst);
|
||||
return writer.flush();
|
||||
}
|
||||
|
||||
pub fn dumpInfo(ir: Ir, writer: *std.Io.Writer) !void {
|
||||
const bytes = ir.string_bytes;
|
||||
|
||||
var start: usize = 0;
|
||||
while (start < bytes.len) {
|
||||
const end = std.mem.indexOfScalarPos(u8, bytes, start, 0) orelse break;
|
||||
const str = bytes[start..end];
|
||||
|
||||
try writer.print("[{d:04}] ", .{start});
|
||||
for (str) |b| try writer.print("{x:02} ", .{b});
|
||||
try writer.print("00: {s}\n", .{str});
|
||||
start = end + 1;
|
||||
}
|
||||
for (ir.globals) |global| {
|
||||
try writer.print("{any}\n", .{global});
|
||||
}
|
||||
return writer.flush();
|
||||
}
|
||||
|
||||
fn ExtraData(comptime T: type) type {
|
||||
return struct {
|
||||
data: T,
|
||||
end: usize,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn extraData(ir: Ir, comptime T: type, index: usize) ExtraData(T) {
|
||||
const fields = @typeInfo(T).@"struct".fields;
|
||||
var i: usize = index;
|
||||
var result: T = undefined;
|
||||
inline for (fields) |field| {
|
||||
@field(result, field.name) = switch (field.type) {
|
||||
u32 => ir.extra[i],
|
||||
Inst.Index => @enumFromInt(ir.extra[i]),
|
||||
Inst.Ref => @enumFromInt(ir.extra[i]),
|
||||
NullTerminatedString => @enumFromInt(ir.extra[i]),
|
||||
else => @compileError("bad field type"),
|
||||
pub const Item = struct {
|
||||
msg: NullTerminatedString,
|
||||
byte_offset: u32,
|
||||
};
|
||||
i += 1;
|
||||
}
|
||||
return .{ .data = result, .end = i };
|
||||
}
|
||||
|
||||
pub fn nullTerminatedString(ir: Ir, index: NullTerminatedString) [:0]const u8 {
|
||||
const slice = ir.string_bytes[@intFromEnum(index)..];
|
||||
return slice[0..std.mem.indexOfScalar(u8, slice, 0).? :0];
|
||||
}
|
||||
|
||||
pub fn bodySlice(ir: Ir, start: usize, len: usize) []Inst.Index {
|
||||
return @ptrCast(ir.extra[start..][0..len]);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
|
|||
110
src/Sema.zig
110
src/Sema.zig
|
|
@ -1,16 +1,16 @@
|
|||
const std = @import("std");
|
||||
const Ir = @import("Ir.zig");
|
||||
const Story = @import("Story.zig");
|
||||
const Object = Story.Object;
|
||||
const Compilation = @import("compile.zig").Compilation;
|
||||
const assert = std.debug.assert;
|
||||
const Sema = @This();
|
||||
|
||||
gpa: std.mem.Allocator,
|
||||
ir: *const Ir,
|
||||
constants: std.ArrayListUnmanaged(CompiledStory.Constant) = .empty,
|
||||
constant_map: std.AutoHashMapUnmanaged(CompiledStory.Constant, u32) = .empty,
|
||||
constants: std.ArrayListUnmanaged(Compilation.Constant) = .empty,
|
||||
constant_map: std.AutoHashMapUnmanaged(Compilation.Constant, u32) = .empty,
|
||||
knots: std.ArrayListUnmanaged(Compilation.Knot) = .empty,
|
||||
globals: std.ArrayListUnmanaged(u32) = .empty,
|
||||
knots: std.ArrayListUnmanaged(CompiledStory.Knot) = .empty,
|
||||
|
||||
const InnerError = error{
|
||||
OutOfMemory,
|
||||
|
|
@ -28,7 +28,7 @@ const Ref = union(enum) {
|
|||
local: u32,
|
||||
};
|
||||
|
||||
fn deinit(sema: *Sema) void {
|
||||
pub fn deinit(sema: *Sema) void {
|
||||
const gpa = sema.gpa;
|
||||
sema.constants.deinit(gpa);
|
||||
sema.constant_map.deinit(gpa);
|
||||
|
|
@ -41,7 +41,7 @@ fn fail(_: *Sema, message: []const u8) InnerError {
|
|||
@panic(message);
|
||||
}
|
||||
|
||||
fn getConstant(sema: *Sema, data: CompiledStory.Constant) !Ref {
|
||||
fn getConstant(sema: *Sema, data: Compilation.Constant) !Ref {
|
||||
const gpa = sema.gpa;
|
||||
|
||||
if (sema.constant_map.get(data)) |index| {
|
||||
|
|
@ -343,7 +343,7 @@ fn irDeclKnot(
|
|||
const data = sema.ir.instructions[@intFromEnum(inst)].data.payload;
|
||||
const extra = sema.ir.extraData(Ir.Inst.Knot, data.payload_index);
|
||||
|
||||
var knot: CompiledStory.Knot = .{
|
||||
var knot: Compilation.Knot = .{
|
||||
.name = name_ref,
|
||||
.arity = 0,
|
||||
.stack_size = 0,
|
||||
|
|
@ -505,7 +505,7 @@ fn blockBodyInner(sema: *Sema, chunk: *Chunk, body: []const Ir.Inst.Index) Inner
|
|||
}
|
||||
}
|
||||
|
||||
fn file(sema: *Sema, inst: Ir.Inst.Index) !void {
|
||||
pub fn analyzeFile(sema: *Sema, inst: Ir.Inst.Index) !void {
|
||||
const data = sema.ir.instructions[@intFromEnum(inst)].data.payload;
|
||||
const extra = sema.ir.extraData(Ir.Inst.Block, data.payload_index);
|
||||
const body = sema.ir.bodySlice(extra.end, extra.data.body_len);
|
||||
|
|
@ -520,7 +520,7 @@ fn file(sema: *Sema, inst: Ir.Inst.Index) !void {
|
|||
|
||||
const Chunk = struct {
|
||||
sema: *Sema,
|
||||
knot: *CompiledStory.Knot,
|
||||
knot: *Compilation.Knot,
|
||||
labels: std.ArrayListUnmanaged(Label) = .empty,
|
||||
fixups: std.ArrayListUnmanaged(Fixup) = .empty,
|
||||
inst_map: std.AutoHashMapUnmanaged(Ir.Inst.Index, Ref) = .empty,
|
||||
|
|
@ -666,95 +666,3 @@ const Chunk = struct {
|
|||
}
|
||||
}
|
||||
};
|
||||
|
||||
pub const CompiledStory = struct {
|
||||
knots: []Knot,
|
||||
constants: []Constant,
|
||||
globals: []u32,
|
||||
|
||||
pub const Knot = struct {
|
||||
name: Ir.NullTerminatedString,
|
||||
arity: u32,
|
||||
stack_size: u32,
|
||||
constants: std.ArrayListUnmanaged(u32) = .empty,
|
||||
bytecode: std.ArrayListUnmanaged(u8) = .empty,
|
||||
};
|
||||
|
||||
pub const Constant = union(enum) {
|
||||
integer: u64,
|
||||
string: Ir.NullTerminatedString,
|
||||
};
|
||||
|
||||
pub fn deinit(self: *CompiledStory, gpa: std.mem.Allocator) void {
|
||||
for (self.knots) |*knot| {
|
||||
knot.constants.deinit(gpa);
|
||||
knot.bytecode.deinit(gpa);
|
||||
}
|
||||
|
||||
gpa.free(self.knots);
|
||||
gpa.free(self.globals);
|
||||
gpa.free(self.constants);
|
||||
self.* = undefined;
|
||||
}
|
||||
|
||||
pub fn buildRuntime(
|
||||
self: *CompiledStory,
|
||||
gpa: std.mem.Allocator,
|
||||
ir: *Ir,
|
||||
story: *Story,
|
||||
) !void {
|
||||
const globals_len = self.globals.len + self.knots.len;
|
||||
const constants_pool = &story.constants_pool;
|
||||
try constants_pool.ensureUnusedCapacity(gpa, self.constants.len);
|
||||
try story.globals.ensureUnusedCapacity(gpa, @intCast(globals_len));
|
||||
|
||||
for (self.constants) |constant| {
|
||||
switch (constant) {
|
||||
.integer => |value| {
|
||||
const object: *Object.Number = try .create(story, .{
|
||||
.integer = @intCast(value),
|
||||
});
|
||||
constants_pool.appendAssumeCapacity(&object.base);
|
||||
},
|
||||
.string => |ref| {
|
||||
const bytes = ir.nullTerminatedString(ref);
|
||||
const object: *Object.String = try .create(story, bytes);
|
||||
constants_pool.appendAssumeCapacity(&object.base);
|
||||
},
|
||||
}
|
||||
}
|
||||
for (self.globals) |global_index| {
|
||||
const str = self.constants[global_index];
|
||||
const name_bytes = ir.nullTerminatedString(str.string);
|
||||
story.globals.putAssumeCapacity(name_bytes, null);
|
||||
}
|
||||
for (self.knots) |*knot| {
|
||||
const knot_name = ir.nullTerminatedString(knot.name);
|
||||
const runtime_chunk: *Object.ContentPath = try .create(story, .{
|
||||
.name = try .create(story, knot_name),
|
||||
.arity = @intCast(knot.arity),
|
||||
.locals_count = @intCast(knot.stack_size - knot.arity),
|
||||
.const_pool = try knot.constants.toOwnedSlice(gpa),
|
||||
.bytes = try knot.bytecode.toOwnedSlice(gpa),
|
||||
});
|
||||
story.globals.putAssumeCapacity(knot_name, &runtime_chunk.base);
|
||||
}
|
||||
story.string_bytes = ir.string_bytes;
|
||||
ir.string_bytes = &.{};
|
||||
}
|
||||
};
|
||||
|
||||
pub fn compile(gpa: std.mem.Allocator, ir: *const Ir) !CompiledStory {
|
||||
var sema: Sema = .{
|
||||
.gpa = gpa,
|
||||
.ir = ir,
|
||||
};
|
||||
defer sema.deinit();
|
||||
|
||||
try file(&sema, .file_inst);
|
||||
return .{
|
||||
.constants = try sema.constants.toOwnedSlice(gpa),
|
||||
.globals = try sema.globals.toOwnedSlice(gpa),
|
||||
.knots = try sema.knots.toOwnedSlice(gpa),
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
//! Virtual machine state for story execution.
|
||||
const std = @import("std");
|
||||
const Compilation = @import("compile.zig").Compilation;
|
||||
const tokenizer = @import("tokenizer.zig");
|
||||
const Ast = @import("Ast.zig");
|
||||
const AstGen = @import("AstGen.zig");
|
||||
|
|
@ -521,7 +522,7 @@ fn divert(vm: *Story, knot_name: []const u8) !void {
|
|||
|
||||
pub const LoadOptions = struct {
|
||||
dump_writer: ?*std.Io.Writer = null,
|
||||
stderr_writer: *std.Io.Writer,
|
||||
error_writer: *std.Io.Writer,
|
||||
use_color: bool = true,
|
||||
dump_ast: bool = false,
|
||||
dump_ir: bool = false,
|
||||
|
|
@ -538,46 +539,22 @@ pub fn loadFromString(
|
|||
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();
|
||||
const ast = try Ast.parse(gpa, arena, source_bytes, "<STDIN>", 0);
|
||||
var comp = try Compilation.compile(gpa, .{
|
||||
.source_bytes = source_bytes,
|
||||
.filename = "<STDIN>",
|
||||
.dump_writer = options.dump_writer,
|
||||
.dump_use_color = options.use_color,
|
||||
.dump_ast = options.dump_ast,
|
||||
.dump_ir = options.dump_ir,
|
||||
});
|
||||
defer comp.deinit();
|
||||
|
||||
if (options.dump_ast) {
|
||||
if (options.dump_writer) |w| {
|
||||
try w.writeAll("=== AST ===\n");
|
||||
try ast.render(gpa, w, .{
|
||||
.use_color = options.use_color,
|
||||
});
|
||||
if (comp.errors.len > 0) {
|
||||
for (comp.errors) |err| {
|
||||
try comp.renderError(options.error_writer, err);
|
||||
}
|
||||
return error.Fail;
|
||||
}
|
||||
if (ast.errors.len > 0) {
|
||||
try ast.renderErrors(gpa, options.stderr_writer, .{
|
||||
.use_color = options.use_color,
|
||||
});
|
||||
return error.Invalid;
|
||||
}
|
||||
|
||||
var sem_ir = try AstGen.generate(gpa, &ast);
|
||||
defer sem_ir.deinit(gpa);
|
||||
|
||||
if (sem_ir.errors.len != 0) {
|
||||
for (sem_ir.errors) |err| {
|
||||
try options.stderr_writer.print("{any}\n", .{err});
|
||||
}
|
||||
try options.stderr_writer.flush();
|
||||
return error.CompilationFailed;
|
||||
}
|
||||
if (options.dump_ir) {
|
||||
if (options.dump_writer) |w| {
|
||||
try w.writeAll("=== Semantic IR ===\n");
|
||||
try sem_ir.dumpInfo(w);
|
||||
try sem_ir.render(w);
|
||||
}
|
||||
}
|
||||
|
||||
var compiled = try Sema.compile(gpa, &sem_ir);
|
||||
defer compiled.deinit(gpa);
|
||||
|
||||
var story: Story = .{
|
||||
.allocator = gpa,
|
||||
|
|
@ -585,9 +562,7 @@ pub fn loadFromString(
|
|||
.dump_writer = options.dump_writer,
|
||||
};
|
||||
errdefer story.deinit();
|
||||
|
||||
try compiled.buildRuntime(gpa, &sem_ir, &story);
|
||||
|
||||
try comp.setupStoryRuntime(gpa, &story);
|
||||
if (story.getKnot(Story.default_knot_name)) |knot| {
|
||||
try story.divertToKnot(knot);
|
||||
story.can_advance = true;
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ fn testRunner(gpa: std.mem.Allocator, source_bytes: [:0]const u8, options: Optio
|
|||
const io_r = options.input_reader;
|
||||
const io_w = options.transcript_writer;
|
||||
var story = try ink.Story.loadFromString(gpa, source_bytes, .{
|
||||
.stderr_writer = options.error_writer,
|
||||
.error_writer = options.error_writer,
|
||||
});
|
||||
defer story.deinit();
|
||||
|
||||
|
|
|
|||
229
src/compile.zig
Normal file
229
src/compile.zig
Normal file
|
|
@ -0,0 +1,229 @@
|
|||
const std = @import("std");
|
||||
const Ast = @import("Ast.zig");
|
||||
const AstGen = @import("AstGen.zig");
|
||||
const Sema = @import("Sema.zig");
|
||||
const Ir = @import("Ir.zig");
|
||||
const Story = @import("Story.zig");
|
||||
const Object = Story.Object;
|
||||
const assert = std.debug.assert;
|
||||
|
||||
pub const Compilation = struct {
|
||||
gpa: std.mem.Allocator,
|
||||
arena: std.heap.ArenaAllocator,
|
||||
tree: Ast,
|
||||
ir: Ir,
|
||||
errors: []Error,
|
||||
knots: []Knot,
|
||||
constants: []Constant,
|
||||
globals: []u32,
|
||||
|
||||
pub const Error = struct {
|
||||
line: usize,
|
||||
column: usize,
|
||||
snippet: []const u8,
|
||||
message: []const u8,
|
||||
};
|
||||
|
||||
pub const Knot = struct {
|
||||
name: Ir.NullTerminatedString,
|
||||
arity: u32,
|
||||
stack_size: u32,
|
||||
constants: std.ArrayListUnmanaged(u32) = .empty,
|
||||
bytecode: std.ArrayListUnmanaged(u8) = .empty,
|
||||
};
|
||||
|
||||
pub const Constant = union(enum) {
|
||||
integer: u64,
|
||||
string: Ir.NullTerminatedString,
|
||||
};
|
||||
|
||||
pub fn renderError(cu: *const Compilation, w: *std.Io.Writer, compile_error: Error) !void {
|
||||
const filename = cu.tree.filename;
|
||||
const line = compile_error.line + 1;
|
||||
const column = compile_error.column + 1;
|
||||
|
||||
try w.print(
|
||||
"{s}:{d}:{d}: error: {s}\n",
|
||||
.{ filename, line, column, compile_error.message },
|
||||
);
|
||||
try w.print("{d:<4} | {s}\n", .{ line, compile_error.snippet });
|
||||
try w.writeAll(" | ");
|
||||
|
||||
if (column > 1) {
|
||||
try w.splatByteAll(' ', column - 1);
|
||||
}
|
||||
try w.writeAll("^\n");
|
||||
}
|
||||
|
||||
pub const CompileOptions = struct {
|
||||
source_bytes: [:0]const u8,
|
||||
filename: [:0]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, options: CompileOptions) !Compilation {
|
||||
var arena_allocator = std.heap.ArenaAllocator.init(gpa);
|
||||
errdefer arena_allocator.deinit();
|
||||
|
||||
var errors: std.ArrayListUnmanaged(Error) = .empty;
|
||||
defer errors.deinit(gpa);
|
||||
|
||||
const arena = arena_allocator.allocator();
|
||||
const ast = try Ast.parse(gpa, arena, options.source_bytes, options.filename, 0);
|
||||
var ir = try AstGen.generate(gpa, &ast);
|
||||
errdefer ir.deinit(gpa);
|
||||
|
||||
var sema: Sema = .{
|
||||
.gpa = gpa,
|
||||
.ir = &ir,
|
||||
};
|
||||
defer sema.deinit();
|
||||
|
||||
if (options.dump_writer) |w| {
|
||||
if (options.dump_ast) {
|
||||
try w.writeAll("=== AST ===\n");
|
||||
try ast.render(gpa, w, .{
|
||||
.use_color = options.dump_use_color,
|
||||
});
|
||||
}
|
||||
if (options.dump_ir) {
|
||||
try w.writeAll("=== Semantic IR ===\n");
|
||||
try ir.dumpInfo(w);
|
||||
try ir.render(w);
|
||||
}
|
||||
}
|
||||
|
||||
const fatal = if (ir.hasCompileErrors()) fatal: {
|
||||
const payload_index = ir.extra[@intFromEnum(Ir.ExtraIndex.compile_errors)];
|
||||
assert(payload_index != 0);
|
||||
|
||||
const header = ir.extraData(Ir.Inst.CompileErrors, payload_index);
|
||||
const items_len = header.data.items_len;
|
||||
var extra_index = header.end;
|
||||
|
||||
// TODO: Make an iterator for this?
|
||||
for (0..items_len) |_| {
|
||||
const item = ir.extraData(Ir.Inst.CompileErrors.Item, extra_index);
|
||||
extra_index = item.end;
|
||||
|
||||
const loc = findLineColumn(ast.source, item.data.byte_offset);
|
||||
try errors.append(gpa, .{
|
||||
.line = loc.line,
|
||||
.column = loc.column,
|
||||
.snippet = loc.source_line,
|
||||
.message = ir.nullTerminatedString(item.data.msg),
|
||||
});
|
||||
}
|
||||
break :fatal true;
|
||||
} else fatal: {
|
||||
try sema.analyzeFile(.file_inst);
|
||||
break :fatal false;
|
||||
};
|
||||
return .{
|
||||
.gpa = gpa,
|
||||
.arena = arena_allocator,
|
||||
.tree = ast,
|
||||
.ir = ir,
|
||||
.errors = try errors.toOwnedSlice(gpa),
|
||||
.constants = if (fatal) &.{} else try sema.constants.toOwnedSlice(gpa),
|
||||
.globals = if (fatal) &.{} else try sema.globals.toOwnedSlice(gpa),
|
||||
.knots = if (fatal) &.{} else try sema.knots.toOwnedSlice(gpa),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn setupStoryRuntime(cu: *Compilation, gpa: std.mem.Allocator, story: *Story) !void {
|
||||
assert(cu.errors.len == 0);
|
||||
|
||||
const globals_len = cu.globals.len + cu.knots.len;
|
||||
const constants_pool = &story.constants_pool;
|
||||
try constants_pool.ensureUnusedCapacity(gpa, cu.constants.len);
|
||||
try story.globals.ensureUnusedCapacity(gpa, @intCast(globals_len));
|
||||
|
||||
for (cu.constants) |constant| {
|
||||
switch (constant) {
|
||||
.integer => |value| {
|
||||
const object: *Object.Number = try .create(story, .{
|
||||
.integer = @intCast(value),
|
||||
});
|
||||
constants_pool.appendAssumeCapacity(&object.base);
|
||||
},
|
||||
.string => |ref| {
|
||||
const bytes = cu.ir.nullTerminatedString(ref);
|
||||
const object: *Object.String = try .create(story, bytes);
|
||||
constants_pool.appendAssumeCapacity(&object.base);
|
||||
},
|
||||
}
|
||||
}
|
||||
for (cu.globals) |global_index| {
|
||||
const str = cu.constants[global_index];
|
||||
const name_bytes = cu.ir.nullTerminatedString(str.string);
|
||||
story.globals.putAssumeCapacity(name_bytes, null);
|
||||
}
|
||||
for (cu.knots) |*knot| {
|
||||
const knot_name = cu.ir.nullTerminatedString(knot.name);
|
||||
const runtime_chunk: *Object.ContentPath = try .create(story, .{
|
||||
.name = try .create(story, knot_name),
|
||||
.arity = @intCast(knot.arity),
|
||||
.locals_count = @intCast(knot.stack_size - knot.arity),
|
||||
.const_pool = try knot.constants.toOwnedSlice(gpa),
|
||||
.bytes = try knot.bytecode.toOwnedSlice(gpa),
|
||||
});
|
||||
story.globals.putAssumeCapacity(knot_name, &runtime_chunk.base);
|
||||
}
|
||||
story.string_bytes = cu.ir.string_bytes;
|
||||
cu.ir.string_bytes = &.{};
|
||||
}
|
||||
|
||||
pub fn deinit(cu: *Compilation) void {
|
||||
const gpa = cu.gpa;
|
||||
for (cu.knots) |*knot| {
|
||||
knot.constants.deinit(gpa);
|
||||
knot.bytecode.deinit(gpa);
|
||||
}
|
||||
|
||||
gpa.free(cu.knots);
|
||||
gpa.free(cu.errors);
|
||||
gpa.free(cu.globals);
|
||||
gpa.free(cu.constants);
|
||||
|
||||
cu.ir.deinit(gpa);
|
||||
cu.arena.deinit();
|
||||
cu.* = undefined;
|
||||
}
|
||||
};
|
||||
|
||||
const Loc = struct {
|
||||
line: usize,
|
||||
column: usize,
|
||||
source_line: []const u8,
|
||||
};
|
||||
|
||||
fn findLineColumn(source: []const u8, byte_offset: usize) Loc {
|
||||
var line: usize = 0;
|
||||
var column: usize = 0;
|
||||
var line_start: usize = 0;
|
||||
var i: usize = 0;
|
||||
while (i < byte_offset) : (i += 1) {
|
||||
switch (source[i]) {
|
||||
'\n' => {
|
||||
line += 1;
|
||||
column = 0;
|
||||
line_start = i + 1;
|
||||
},
|
||||
else => {
|
||||
column += 1;
|
||||
},
|
||||
}
|
||||
}
|
||||
while (i < source.len and source[i] != '\n') {
|
||||
i += 1;
|
||||
}
|
||||
return .{
|
||||
.line = line,
|
||||
.column = column,
|
||||
.source_line = source[line_start..i],
|
||||
};
|
||||
}
|
||||
37
src/error_tests.zig
Normal file
37
src/error_tests.zig
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
const std = @import("std");
|
||||
const Compilation = @import("compile.zig").Compilation;
|
||||
|
||||
test "compiler: global variable shadowing" {
|
||||
try testEqual(
|
||||
\\VAR a = 0
|
||||
\\VAR b = 2
|
||||
\\VAR a = 1
|
||||
,
|
||||
\\<STDIN>:3:1: error: redefined identifier
|
||||
\\3 | VAR a = 1
|
||||
\\ | ^
|
||||
\\
|
||||
,
|
||||
);
|
||||
}
|
||||
|
||||
fn testEqual(source_bytes: [:0]const u8, expected_error: []const u8) !void {
|
||||
const gpa = std.testing.allocator;
|
||||
var allocating = std.io.Writer.Allocating.init(gpa);
|
||||
defer allocating.deinit();
|
||||
const io_w = &allocating.writer;
|
||||
|
||||
var c = try Compilation.compile(gpa, .{
|
||||
.source_bytes = source_bytes,
|
||||
.filename = "<STDIN>",
|
||||
.dump_writer = null,
|
||||
.dump_use_color = false,
|
||||
.dump_ast = false,
|
||||
.dump_ir = false,
|
||||
});
|
||||
defer c.deinit();
|
||||
|
||||
try std.testing.expect(c.errors.len > 0);
|
||||
for (c.errors) |err| try c.renderError(io_w, err);
|
||||
return std.testing.expectEqualSlices(u8, expected_error, allocating.written());
|
||||
}
|
||||
19
src/main.zig
19
src/main.zig
|
|
@ -79,28 +79,33 @@ fn mainArgs(
|
|||
};
|
||||
};
|
||||
|
||||
const stdin = std.fs.File.stdin();
|
||||
var stdin_reader = stdin.reader(&stdin_buffer);
|
||||
|
||||
const stdout = std.fs.File.stdout();
|
||||
var stdout_writer = stdout.writer(&stdout_buffer);
|
||||
|
||||
const stderr = std.fs.File.stderr();
|
||||
var stderr_writer = stderr.writer(&stderr_buffer);
|
||||
|
||||
var story = try ink.Story.loadFromString(gpa, source_bytes, .{
|
||||
.stderr_writer = &stderr_writer.interface,
|
||||
var story = ink.Story.loadFromString(gpa, source_bytes, .{
|
||||
.error_writer = &stderr_writer.interface,
|
||||
.dump_writer = &stdout_writer.interface,
|
||||
.use_color = use_color,
|
||||
.dump_ast = dump_ast,
|
||||
.dump_ir = dump_ir,
|
||||
});
|
||||
}) catch |err| switch (err) {
|
||||
error.Fail => std.process.exit(1),
|
||||
else => |e| return e,
|
||||
};
|
||||
defer story.deinit();
|
||||
|
||||
if (dump_story) {
|
||||
try story.dump(&stderr_writer.interface);
|
||||
}
|
||||
if (compile_only) return;
|
||||
return if (!compile_only) run(gpa, &story);
|
||||
}
|
||||
|
||||
fn run(gpa: std.mem.Allocator, story: *ink.Story) !void {
|
||||
const stdin = std.fs.File.stdin();
|
||||
var stdin_reader = stdin.reader(&stdin_buffer);
|
||||
|
||||
while (!story.is_exited and story.can_advance) {
|
||||
while (story.can_advance) {
|
||||
|
|
|
|||
|
|
@ -7,5 +7,6 @@ test {
|
|||
_ = Ast;
|
||||
_ = Story;
|
||||
_ = @import("parser_tests.zig");
|
||||
_ = @import("error_tests.zig");
|
||||
_ = @import("Story/runtime_tests.zig");
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue