493 lines
15 KiB
Zig
493 lines
15 KiB
Zig
const std = @import("std");
|
|
const ink = @import("../root.zig");
|
|
const Ast = ink.Ast;
|
|
const Render = @This();
|
|
|
|
gpa: std.mem.Allocator,
|
|
tty_config: std.Io.tty.Config,
|
|
tree: *const Ast,
|
|
prefix: Prefix,
|
|
lines: LineCache,
|
|
|
|
const LineCache = struct {
|
|
ranges: std.ArrayListUnmanaged(SourceRange) = .empty,
|
|
|
|
pub fn deinit(self: *LineCache, gpa: std.mem.Allocator) void {
|
|
self.ranges.deinit(gpa);
|
|
}
|
|
|
|
pub fn build(gpa: std.mem.Allocator, bytes: []const u8) !LineCache {
|
|
var lines: LineCache = .{};
|
|
var start: usize = 0;
|
|
|
|
for (bytes, 0..) |c, i| {
|
|
if (c == '\n') {
|
|
try lines.ranges.append(gpa, .{ .start = start, .end = i });
|
|
start = i + 1;
|
|
}
|
|
}
|
|
if (start != bytes.len or bytes.len == 0) {
|
|
try lines.ranges.append(gpa, .{ .start = start, .end = bytes.len });
|
|
}
|
|
return lines;
|
|
}
|
|
|
|
pub fn calculateLine(self: *const LineCache, offset: usize) usize {
|
|
for (self.ranges.items, 0..) |r, i| {
|
|
if (offset >= r.start and offset <= r.end)
|
|
return i;
|
|
}
|
|
return self.ranges.items.len - 1;
|
|
}
|
|
};
|
|
|
|
pub const SourceRange = struct {
|
|
start: usize,
|
|
end: usize,
|
|
};
|
|
|
|
pub const ErrorInfo = struct {
|
|
filename: []const u8,
|
|
message: []const u8,
|
|
snippet: []const u8,
|
|
line: usize,
|
|
column: usize,
|
|
};
|
|
|
|
fn nodeTagToString(tag: Ast.Node.Tag) []const u8 {
|
|
return switch (tag) {
|
|
.file => "File",
|
|
.false_literal => "FalseLiteral",
|
|
.true_literal => "TrueLiteral",
|
|
.number_literal => "NumberLiteral",
|
|
.string_literal => "StringLiteral",
|
|
.empty_string => "EmptyString",
|
|
.identifier => "Identifier",
|
|
.add_expr => "AddExpr",
|
|
.subtract_expr => "SubtractExpr",
|
|
.multiply_expr => "MultiplyExpr",
|
|
.divide_expr => "DivideExpr",
|
|
.mod_expr => "ModExpr",
|
|
.negate_expr => "NegateExpr",
|
|
.logical_equality_expr => "LogicalEqualityExpr",
|
|
.logical_inequality_expr => "LogicalInequalityExpr",
|
|
.logical_and_expr => "LogicalAndExpr",
|
|
.logical_or_expr => "LogicalOrExpr",
|
|
.logical_not_expr => "LogicalNotExpr",
|
|
.logical_greater_expr => "LogicalGreaterThanExpr",
|
|
.logical_greater_or_equal_expr => "LogicalGreaterThanOrEqualExpr",
|
|
.logical_lesser_or_equal_expr => "LogicalLesserThanOrEqualExpr",
|
|
.logical_lesser_expr => "LogicalLesserThanExpr",
|
|
.divert_expr => "Divert",
|
|
.selector_expr => "SelectorExpr",
|
|
.call_expr => "CallExpr",
|
|
.choice_expr => "ChoiceContentExpr",
|
|
.choice_start_expr => "ChoiceStartContentExpr",
|
|
.choice_option_expr => "ChoiceOptionContentExpr",
|
|
.choice_inner_expr => "ChoiceInnerContentExpr",
|
|
.assign_stmt => "AssignStmt",
|
|
.block_stmt => "BlockStmt",
|
|
.content_stmt => "ContentStmt",
|
|
.divert_stmt => "DivertStmt",
|
|
.return_stmt => "ReturnStmt",
|
|
.expr_stmt => "ExprStmt",
|
|
.choice_stmt => "ChoiceStmt",
|
|
.choice_star_stmt => "ChoiceStarStmt",
|
|
.choice_plus_stmt => "ChoicePlusStmt",
|
|
.gather_point_stmt => "GatherPointStmt",
|
|
.gathered_stmt => "GatheredStmt",
|
|
.function_prototype => "FunctionProto",
|
|
.stitch_prototype => "StitchProto",
|
|
.knot_prototype => "KnotProto",
|
|
.function_decl => "FunctionDecl",
|
|
.stitch_decl => "StitchDecl",
|
|
.knot_decl => "KnotDecl",
|
|
.const_decl => "ConstDecl",
|
|
.var_decl => "VarDecl",
|
|
.list_decl => "ListDecl",
|
|
.temp_decl => "TempDecl",
|
|
.parameter_decl => "ParamDecl",
|
|
.ref_parameter_decl => "RefParamDecl",
|
|
.argument_list => "ArgumentList",
|
|
.parameter_list => "ParamList",
|
|
.string_expr => "StringExpr",
|
|
.switch_stmt => "SwitchStmt",
|
|
.switch_case => "SwitchCase",
|
|
.multi_if_stmt => "MultiIfStmt",
|
|
.if_stmt => "IfStmt",
|
|
.if_branch => "IfBranch",
|
|
.else_branch => "ElseBranch",
|
|
.inline_logic_expr => "InlineLogicExpr",
|
|
.content => "Content",
|
|
.invalid => "Invalid",
|
|
};
|
|
}
|
|
|
|
const Prefix = struct {
|
|
buf: std.ArrayListUnmanaged(u8) = .empty,
|
|
|
|
pub fn deinit(self: *Prefix, gpa: std.mem.Allocator) void {
|
|
self.buf.deinit(gpa);
|
|
}
|
|
|
|
pub fn writeIndent(self: *const Prefix, writer: anytype) !void {
|
|
try writer.writeAll(self.buf.items);
|
|
}
|
|
|
|
pub fn pushChildPrefix(self: *Prefix, gpa: std.mem.Allocator, is_last: bool) !usize {
|
|
const old_len = self.buf.items.len;
|
|
const seg: []const u8 = if (is_last) " " else "| ";
|
|
try self.buf.appendSlice(gpa, seg);
|
|
return old_len;
|
|
}
|
|
|
|
pub fn restore(self: *Prefix, new_len: usize) void {
|
|
self.buf.shrinkRetainingCapacity(new_len);
|
|
}
|
|
};
|
|
|
|
fn writeType(r: *Render, writer: *std.Io.Writer, node: *const Ast.Node) !void {
|
|
const tag = nodeTagToString(node.tag);
|
|
try r.tty_config.setColor(writer, .magenta);
|
|
try r.tty_config.setColor(writer, .bold);
|
|
try writer.writeAll(tag);
|
|
try r.tty_config.setColor(writer, .reset);
|
|
}
|
|
|
|
fn writeLexeme(r: *Render, writer: *std.Io.Writer, node: *const Ast.Node) !void {
|
|
try r.tty_config.setColor(writer, .yellow);
|
|
try writer.print("`{s}`", .{r.tree.nodeSlice(node)});
|
|
try r.tty_config.setColor(writer, .reset);
|
|
}
|
|
|
|
fn writeLineSpan(r: *Render, writer: *std.Io.Writer, node: *const Ast.Node) !void {
|
|
const line_start = r.lines.calculateLine(node.loc.start);
|
|
const line_end = r.lines.calculateLine(node.loc.end);
|
|
|
|
try r.tty_config.setColor(writer, .white);
|
|
try writer.writeByte('<');
|
|
try r.tty_config.setColor(writer, .reset);
|
|
|
|
try r.tty_config.setColor(writer, .yellow);
|
|
try writer.print("line:{d}", .{line_start + 1});
|
|
try r.tty_config.setColor(writer, .reset);
|
|
|
|
try r.tty_config.setColor(writer, .white);
|
|
try writer.writeByte(',');
|
|
try r.tty_config.setColor(writer, .reset);
|
|
|
|
try writer.writeByte(' ');
|
|
|
|
try r.tty_config.setColor(writer, .yellow);
|
|
try writer.print("line:{d}", .{line_end + 1});
|
|
try r.tty_config.setColor(writer, .reset);
|
|
|
|
try r.tty_config.setColor(writer, .white);
|
|
try writer.writeByte('>');
|
|
try r.tty_config.setColor(writer, .reset);
|
|
}
|
|
|
|
fn writeColumnSpan(r: *Render, writer: *std.Io.Writer, node: *const Ast.Node) !void {
|
|
const line_start = r.lines.calculateLine(node.loc.start);
|
|
const line_range = r.lines.ranges.items[line_start];
|
|
const column_start = (node.loc.start - line_range.start);
|
|
const column_end = (node.loc.end - line_range.start);
|
|
|
|
try r.tty_config.setColor(writer, .white);
|
|
try writer.writeByte('<');
|
|
try r.tty_config.setColor(writer, .reset);
|
|
|
|
try r.tty_config.setColor(writer, .yellow);
|
|
try writer.print("col:{d}", .{column_start + 1});
|
|
try r.tty_config.setColor(writer, .reset);
|
|
|
|
try r.tty_config.setColor(writer, .white);
|
|
try writer.writeByte(',');
|
|
try r.tty_config.setColor(writer, .reset);
|
|
|
|
try writer.writeByte(' ');
|
|
|
|
try r.tty_config.setColor(writer, .yellow);
|
|
try writer.print("col:{d}", .{column_end + 1});
|
|
try r.tty_config.setColor(writer, .reset);
|
|
|
|
try r.tty_config.setColor(writer, .white);
|
|
try writer.writeByte('>');
|
|
try r.tty_config.setColor(writer, .reset);
|
|
}
|
|
|
|
fn writeLineColumnSpan(r: *Render, writer: *std.Io.Writer, node: *const Ast.Node) !void {
|
|
const line_start = r.lines.calculateLine(node.loc.start);
|
|
const line_range = r.lines.ranges.items[line_start];
|
|
const column_start = (node.loc.start - line_range.start);
|
|
const column_end = (node.loc.end - line_range.start);
|
|
|
|
try r.tty_config.setColor(writer, .white);
|
|
try writer.writeByte('<');
|
|
try r.tty_config.setColor(writer, .reset);
|
|
|
|
try r.tty_config.setColor(writer, .yellow);
|
|
try writer.print("line:{d}", .{line_start + 1});
|
|
|
|
try r.tty_config.setColor(writer, .white);
|
|
try writer.writeByte(',');
|
|
try r.tty_config.setColor(writer, .reset);
|
|
|
|
try writer.writeByte(' ');
|
|
|
|
try r.tty_config.setColor(writer, .yellow);
|
|
try writer.print("col:{d}:{d}", .{ column_start + 1, column_end + 1 });
|
|
try r.tty_config.setColor(writer, .reset);
|
|
|
|
try r.tty_config.setColor(writer, .white);
|
|
try writer.writeByte('>');
|
|
try r.tty_config.setColor(writer, .reset);
|
|
}
|
|
|
|
const NodeOptions = struct {
|
|
is_last: bool,
|
|
is_root: bool,
|
|
};
|
|
|
|
fn renderAstNode(
|
|
r: *Render,
|
|
writer: *std.Io.Writer,
|
|
node: *const Ast.Node,
|
|
options: NodeOptions,
|
|
) !void {
|
|
if (!options.is_root) {
|
|
try r.tty_config.setColor(writer, .blue);
|
|
try r.prefix.writeIndent(writer);
|
|
if (options.is_last) {
|
|
try writer.writeAll("`--");
|
|
} else {
|
|
try writer.writeAll("|--");
|
|
}
|
|
try r.tty_config.setColor(writer, .reset);
|
|
}
|
|
|
|
switch (node.tag) {
|
|
.file => {
|
|
try r.writeType(writer, node);
|
|
try writer.writeByte(' ');
|
|
try writer.writeByte('"');
|
|
try writer.writeAll(r.tree.filename);
|
|
try writer.writeByte('"');
|
|
},
|
|
.block_stmt,
|
|
.choice_stmt,
|
|
.function_decl,
|
|
.gathered_stmt,
|
|
.knot_decl,
|
|
.stitch_decl,
|
|
.if_stmt,
|
|
.multi_if_stmt,
|
|
.switch_stmt,
|
|
=> {
|
|
try r.writeType(writer, node);
|
|
try writer.writeByte(' ');
|
|
try r.writeLineSpan(writer, node);
|
|
},
|
|
.assign_stmt,
|
|
.choice_plus_stmt,
|
|
.choice_star_stmt,
|
|
.const_decl,
|
|
.content_stmt,
|
|
.divert_stmt,
|
|
.expr_stmt,
|
|
.gather_point_stmt,
|
|
.list_decl,
|
|
.return_stmt,
|
|
.temp_decl,
|
|
.var_decl,
|
|
=> {
|
|
try r.writeType(writer, node);
|
|
try writer.writeByte(' ');
|
|
try r.writeLineColumnSpan(writer, node);
|
|
},
|
|
.choice_start_expr,
|
|
.choice_option_expr,
|
|
.choice_inner_expr,
|
|
.identifier,
|
|
.number_literal,
|
|
.parameter_decl,
|
|
.ref_parameter_decl,
|
|
.string_literal,
|
|
.string_expr,
|
|
=> {
|
|
try r.writeType(writer, node);
|
|
try writer.writeByte(' ');
|
|
try r.writeLexeme(writer, node);
|
|
try writer.writeByte(' ');
|
|
try r.writeColumnSpan(writer, node);
|
|
},
|
|
else => {
|
|
try r.writeType(writer, node);
|
|
try writer.writeByte(' ');
|
|
try r.writeColumnSpan(writer, node);
|
|
},
|
|
}
|
|
try writer.writeByte('\n');
|
|
return writer.flush();
|
|
}
|
|
|
|
fn renderAstWalk(
|
|
r: *Render,
|
|
writer: *std.Io.Writer,
|
|
node: *const Ast.Node,
|
|
options: NodeOptions,
|
|
) !void {
|
|
var children: std.ArrayListUnmanaged(?*const Ast.Node) = .empty;
|
|
defer children.deinit(r.gpa);
|
|
|
|
try renderAstNode(r, writer, node, options);
|
|
switch (node.tag) {
|
|
.false_literal,
|
|
.true_literal,
|
|
.number_literal,
|
|
.string_literal,
|
|
.empty_string,
|
|
.identifier,
|
|
.parameter_decl,
|
|
.ref_parameter_decl,
|
|
.choice_start_expr,
|
|
.choice_option_expr,
|
|
.choice_inner_expr,
|
|
=> {},
|
|
.file,
|
|
.argument_list,
|
|
.parameter_list,
|
|
.block_stmt,
|
|
.choice_stmt,
|
|
.content,
|
|
=> {
|
|
const list = node.data.list.items;
|
|
if (list) |items| for (items) |n| {
|
|
try children.append(r.gpa, n);
|
|
};
|
|
},
|
|
.choice_expr => {
|
|
const lhs = node.data.choice_expr.start_expr;
|
|
const mhs = node.data.choice_expr.option_expr;
|
|
const rhs = node.data.choice_expr.inner_expr;
|
|
|
|
if (lhs) |n| try children.append(r.gpa, n);
|
|
if (mhs) |n| try children.append(r.gpa, n);
|
|
if (rhs) |n| try children.append(r.gpa, n);
|
|
},
|
|
.add_expr,
|
|
.subtract_expr,
|
|
.multiply_expr,
|
|
.divide_expr,
|
|
.mod_expr,
|
|
.negate_expr,
|
|
.logical_equality_expr,
|
|
.logical_inequality_expr,
|
|
.logical_and_expr,
|
|
.logical_or_expr,
|
|
.logical_not_expr,
|
|
.logical_greater_expr,
|
|
.logical_greater_or_equal_expr,
|
|
.logical_lesser_or_equal_expr,
|
|
.logical_lesser_expr,
|
|
.assign_stmt,
|
|
.divert_stmt,
|
|
.return_stmt,
|
|
.expr_stmt,
|
|
.const_decl,
|
|
.var_decl,
|
|
.list_decl,
|
|
.temp_decl,
|
|
.content_stmt,
|
|
.choice_star_stmt,
|
|
.choice_plus_stmt,
|
|
.gather_point_stmt,
|
|
.gathered_stmt,
|
|
.function_prototype,
|
|
.stitch_prototype,
|
|
.knot_prototype,
|
|
.function_decl,
|
|
.stitch_decl,
|
|
.string_expr,
|
|
.divert_expr,
|
|
.selector_expr,
|
|
.call_expr,
|
|
.switch_case,
|
|
.if_branch,
|
|
.else_branch,
|
|
.invalid,
|
|
=> {
|
|
const lhs = node.data.bin.lhs;
|
|
const rhs = node.data.bin.rhs;
|
|
|
|
if (lhs) |n| try children.append(r.gpa, n);
|
|
if (rhs) |n| try children.append(r.gpa, n);
|
|
},
|
|
.knot_decl => {
|
|
const lhs = node.data.knot_decl.prototype;
|
|
try children.append(r.gpa, lhs);
|
|
|
|
const list = node.data.knot_decl.children;
|
|
if (list) |items| for (items) |n| {
|
|
try children.append(r.gpa, n);
|
|
};
|
|
},
|
|
.switch_stmt, .if_stmt, .multi_if_stmt => {
|
|
const expr = node.data.switch_stmt.condition_expr;
|
|
if (expr) |n| try children.append(r.gpa, n);
|
|
|
|
const list = node.data.switch_stmt.cases;
|
|
for (list) |case_stmt| {
|
|
try children.append(r.gpa, case_stmt);
|
|
}
|
|
},
|
|
.inline_logic_expr => {
|
|
const lhs = node.data.bin.lhs;
|
|
if (lhs) |n| try children.append(r.gpa, n);
|
|
},
|
|
}
|
|
for (children.items, 0..) |maybe_child, i| {
|
|
const old_len = if (!options.is_root)
|
|
try r.prefix.pushChildPrefix(r.gpa, options.is_last)
|
|
else
|
|
0;
|
|
const child_is_last = (i == children.items.len - 1);
|
|
|
|
if (maybe_child) |child| {
|
|
try renderAstWalk(r, writer, child, .{
|
|
.is_last = child_is_last,
|
|
.is_root = false,
|
|
});
|
|
}
|
|
if (!options.is_root) r.prefix.restore(old_len);
|
|
}
|
|
}
|
|
|
|
pub const Options = struct {
|
|
use_color: bool,
|
|
};
|
|
|
|
pub fn renderTree(
|
|
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);
|
|
|
|
if (ast.root) |root| {
|
|
try r.renderAstWalk(writer, root, .{
|
|
.is_root = true,
|
|
.is_last = true,
|
|
});
|
|
}
|
|
try writer.flush();
|
|
}
|