feat: ast structure and utilities
This commit is contained in:
parent
662c38b360
commit
5268a53148
5 changed files with 970 additions and 5 deletions
311
src/Ast.zig
Normal file
311
src/Ast.zig
Normal file
|
|
@ -0,0 +1,311 @@
|
|||
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();
|
||||
|
||||
filename: []const u8,
|
||||
source: []const u8,
|
||||
root: ?*Node = null,
|
||||
errors: []Error,
|
||||
|
||||
pub const Node = struct {
|
||||
tag: Tag,
|
||||
source_start: usize,
|
||||
source_end: usize,
|
||||
data: Data,
|
||||
|
||||
pub const Tag = enum {
|
||||
file,
|
||||
false_literal,
|
||||
true_literal,
|
||||
number_literal,
|
||||
string_literal,
|
||||
empty_string,
|
||||
identifier,
|
||||
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,
|
||||
call_expr,
|
||||
choice_expr,
|
||||
choice_start_expr,
|
||||
choice_option_expr,
|
||||
choice_inner_expr,
|
||||
divert_expr,
|
||||
selector_expr,
|
||||
assign_stmt,
|
||||
block_stmt,
|
||||
content_stmt,
|
||||
divert_stmt,
|
||||
return_stmt,
|
||||
expr_stmt,
|
||||
choice_stmt,
|
||||
choice_star_stmt,
|
||||
choice_plus_stmt,
|
||||
gather_point_stmt,
|
||||
gathered_stmt,
|
||||
function_prototype,
|
||||
stitch_prototype,
|
||||
knot_prototype,
|
||||
function_decl,
|
||||
stitch_decl,
|
||||
knot_decl,
|
||||
const_decl,
|
||||
var_decl,
|
||||
list_decl,
|
||||
temp_decl,
|
||||
parameter_decl,
|
||||
ref_parameter_decl,
|
||||
string_expr,
|
||||
argument_list,
|
||||
parameter_list,
|
||||
switch_stmt,
|
||||
switch_case,
|
||||
if_stmt,
|
||||
multi_if_stmt,
|
||||
if_branch,
|
||||
else_branch,
|
||||
content,
|
||||
// TODO: Rename to SubstitutionExpr
|
||||
inline_logic_expr,
|
||||
invalid,
|
||||
};
|
||||
|
||||
pub const Data = union {
|
||||
leaf: void,
|
||||
bin: struct {
|
||||
lhs: ?*Node,
|
||||
rhs: ?*Node,
|
||||
},
|
||||
list: struct {
|
||||
items: ?[]*Node,
|
||||
},
|
||||
choice_expr: struct {
|
||||
start_expr: ?*Node,
|
||||
option_expr: ?*Node,
|
||||
inner_expr: ?*Node,
|
||||
},
|
||||
switch_stmt: struct {
|
||||
condition_expr: ?*Node,
|
||||
cases: ?[]*Node,
|
||||
},
|
||||
knot_decl: struct {
|
||||
prototype: *Node,
|
||||
children: ?[]*Node,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
pub const Error = struct {
|
||||
tag: Tag,
|
||||
loc: Location,
|
||||
|
||||
pub const Location = struct {
|
||||
start: usize,
|
||||
end: usize,
|
||||
};
|
||||
|
||||
pub const Tag = enum {
|
||||
panic,
|
||||
unexpected_token,
|
||||
unknown_identifier,
|
||||
assignment_to_const,
|
||||
expected_newline,
|
||||
expected_expression,
|
||||
expected_double_quote,
|
||||
expected_identifier,
|
||||
invalid_expression,
|
||||
invalid_lvalue,
|
||||
too_many_arguments,
|
||||
too_many_parameters,
|
||||
};
|
||||
};
|
||||
|
||||
fn create(
|
||||
gpa: std.mem.Allocator,
|
||||
tag: Node.Tag,
|
||||
source_start: usize,
|
||||
source_end: usize,
|
||||
) !*Node {
|
||||
const node = try gpa.create(Node);
|
||||
node.* = .{
|
||||
.tag = tag,
|
||||
.source_start = source_start,
|
||||
.source_end = source_end,
|
||||
.data = undefined,
|
||||
};
|
||||
return node;
|
||||
}
|
||||
|
||||
pub fn createLeafNode(
|
||||
gpa: std.mem.Allocator,
|
||||
tag: Node.Tag,
|
||||
source_start: usize,
|
||||
source_end: usize,
|
||||
) !*Node {
|
||||
const node = try Ast.create(gpa, tag, source_start, source_end);
|
||||
node.*.data = .{ .leaf = undefined };
|
||||
return node;
|
||||
}
|
||||
|
||||
pub fn createBinaryNode(
|
||||
gpa: std.mem.Allocator,
|
||||
tag: Node.Tag,
|
||||
source_start: usize,
|
||||
source_end: usize,
|
||||
lhs: ?*Node,
|
||||
rhs: ?*Node,
|
||||
) !*Node {
|
||||
const node = try Ast.create(gpa, tag, source_start, source_end);
|
||||
node.*.data = .{
|
||||
.bin = .{
|
||||
.lhs = lhs,
|
||||
.rhs = rhs,
|
||||
},
|
||||
};
|
||||
return node;
|
||||
}
|
||||
|
||||
pub fn createListNode(
|
||||
gpa: std.mem.Allocator,
|
||||
tag: Node.Tag,
|
||||
source_start: usize,
|
||||
source_end: usize,
|
||||
items: ?[]*Node,
|
||||
) !*Node {
|
||||
const node = try Ast.create(gpa, tag, source_start, source_end);
|
||||
node.*.data = .{
|
||||
.list = .{ .items = items },
|
||||
};
|
||||
return node;
|
||||
}
|
||||
|
||||
pub fn createChoiceExprNode(
|
||||
gpa: std.mem.Allocator,
|
||||
tag: Node.Tag,
|
||||
source_start: usize,
|
||||
source_end: usize,
|
||||
start_expr: ?*Node,
|
||||
option_expr: ?*Node,
|
||||
inner_expr: ?*Node,
|
||||
) !*Node {
|
||||
const node = try Ast.create(gpa, tag, source_start, source_end);
|
||||
node.*.data = .{
|
||||
.choice_expr = .{
|
||||
.start_expr = start_expr,
|
||||
.option_expr = option_expr,
|
||||
.inner_expr = inner_expr,
|
||||
},
|
||||
};
|
||||
return node;
|
||||
}
|
||||
|
||||
pub fn createSwitchNode(
|
||||
gpa: std.mem.Allocator,
|
||||
tag: Node.Tag,
|
||||
source_start: usize,
|
||||
source_end: usize,
|
||||
condition_expr: ?*Node,
|
||||
cases_list: ?[]*Node,
|
||||
) !*Node {
|
||||
const node = try Ast.create(gpa, tag, source_start, source_end);
|
||||
node.*.data = .{
|
||||
.switch_stmt = .{
|
||||
.condition_expr = condition_expr,
|
||||
.cases = cases_list,
|
||||
},
|
||||
};
|
||||
return node;
|
||||
}
|
||||
|
||||
pub fn createKnotDeclNode(
|
||||
gpa: std.mem.Allocator,
|
||||
tag: Node.Tag,
|
||||
source_start: usize,
|
||||
source_end: usize,
|
||||
prototype: *Node,
|
||||
children: ?[]*Node,
|
||||
) !*Node {
|
||||
const node = try Ast.create(gpa, tag, source_start, source_end);
|
||||
node.*.data = .{
|
||||
.knot_decl = .{
|
||||
.prototype = prototype,
|
||||
.children = children,
|
||||
},
|
||||
};
|
||||
return node;
|
||||
}
|
||||
|
||||
pub fn parse(
|
||||
gpa: std.mem.Allocator,
|
||||
source: [:0]const u8,
|
||||
filename: []const u8,
|
||||
_: u32,
|
||||
) !Ast {
|
||||
var arena_allocator = std.heap.ArenaAllocator.init(gpa);
|
||||
defer arena_allocator.deinit();
|
||||
|
||||
var parser: Parse = .{
|
||||
.gpa = gpa,
|
||||
.arena = arena_allocator.allocator(),
|
||||
.tokenizer = Tokenizer.init(source),
|
||||
.token = .{
|
||||
.tag = .invalid,
|
||||
.loc = .{ .start = 0, .end = 0 },
|
||||
},
|
||||
.scratch = .empty,
|
||||
.grammar_stack = .empty,
|
||||
.block_stack = .empty,
|
||||
.choice_stack = .empty,
|
||||
.errors = .empty,
|
||||
.panic_mode = false,
|
||||
.flags = 0,
|
||||
.max_parse_depth = 128,
|
||||
.max_argument_count = 128,
|
||||
};
|
||||
defer parser.deinit();
|
||||
|
||||
const root = try parser.parseFile();
|
||||
return Ast{
|
||||
.filename = filename,
|
||||
.source = source,
|
||||
.root = root,
|
||||
.errors = try parser.errors.toOwnedSlice(gpa),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn render(
|
||||
ast: Ast,
|
||||
gpa: std.mem.Allocator,
|
||||
writer: *std.Io.Writer,
|
||||
options: Render.Options,
|
||||
) !void {
|
||||
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 deinit(ast: *Ast, gpa: std.mem.Allocator) void {
|
||||
gpa.free(ast.errors);
|
||||
ast.* = undefined;
|
||||
}
|
||||
567
src/Ast/Render.zig
Normal file
567
src/Ast/Render.zig
Normal file
|
|
@ -0,0 +1,567 @@
|
|||
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",
|
||||
};
|
||||
}
|
||||
|
||||
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"),
|
||||
.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}'"),
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
const bytes = r.tree.source;
|
||||
const lexeme = bytes[node.source_start..node.source_end];
|
||||
try r.tty_config.setColor(writer, .yellow);
|
||||
try writer.print("`{s}`", .{lexeme});
|
||||
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.source_start);
|
||||
const line_end = r.lines.calculateLine(node.source_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.source_start);
|
||||
const line_range = r.lines.ranges.items[line_start];
|
||||
const column_start = (node.source_start - line_range.start);
|
||||
const column_end = (node.source_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.source_start);
|
||||
const line_range = r.lines.ranges.items[line_start];
|
||||
const column_start = (node.source_start - line_range.start);
|
||||
const column_end = (node.source_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;
|
||||
if (list) |items| for (items) |n| {
|
||||
try children.append(r.gpa, n);
|
||||
};
|
||||
},
|
||||
.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();
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
67
src/Parse.zig
Normal file
67
src/Parse.zig
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
const std = @import("std");
|
||||
const assert = std.debug.assert;
|
||||
const tok = @import("tokenizer.zig");
|
||||
const Token = tok.Token;
|
||||
const Tokenizer = tok.Tokenizer;
|
||||
const Ast = @import("Ast.zig");
|
||||
|
||||
const Parse = @This();
|
||||
|
||||
gpa: std.mem.Allocator,
|
||||
arena: std.mem.Allocator,
|
||||
tokenizer: Tokenizer,
|
||||
token: Token,
|
||||
scratch: std.ArrayListUnmanaged(*Ast.Node),
|
||||
grammar_stack: std.ArrayListUnmanaged(ScanContext),
|
||||
block_stack: std.ArrayListUnmanaged(State),
|
||||
choice_stack: std.ArrayListUnmanaged(State),
|
||||
errors: std.ArrayListUnmanaged(Ast.Error),
|
||||
panic_mode: bool,
|
||||
flags: u32,
|
||||
max_parse_depth: usize,
|
||||
max_argument_count: usize,
|
||||
knot_offset: usize = 0,
|
||||
|
||||
pub const Error = error{
|
||||
ParseError,
|
||||
OutOfMemory,
|
||||
};
|
||||
|
||||
pub const State = struct {
|
||||
level: usize,
|
||||
scratch_offset: usize,
|
||||
source_offset: usize,
|
||||
};
|
||||
|
||||
pub const StmtContext = struct {
|
||||
tag: Tag,
|
||||
is_block_created: bool = false,
|
||||
expression_node: ?*Ast.Node = null,
|
||||
level: usize = 0,
|
||||
blocks_top: usize,
|
||||
choices_top: usize,
|
||||
scratch_top: usize,
|
||||
|
||||
pub const Tag = enum {
|
||||
block,
|
||||
conditional,
|
||||
};
|
||||
};
|
||||
|
||||
pub const ScanContext = struct {
|
||||
grammar: Tokenizer.Grammar,
|
||||
source_offset: usize,
|
||||
};
|
||||
|
||||
pub fn deinit(p: *Parse) void {
|
||||
p.scratch.deinit(p.gpa);
|
||||
p.grammar_stack.deinit(p.gpa);
|
||||
p.block_stack.deinit(p.gpa);
|
||||
p.choice_stack.deinit(p.gpa);
|
||||
p.errors.deinit(p.gpa);
|
||||
p.* = undefined;
|
||||
}
|
||||
|
||||
pub fn parseFile(_: *Parse) Error!*Ast.Node {
|
||||
return error.OutOfMemory;
|
||||
}
|
||||
|
|
@ -58,7 +58,12 @@ fn mainArgs(
|
|||
};
|
||||
};
|
||||
|
||||
var story = try ink.Story.loadFromString(gpa, source_bytes, .{});
|
||||
const stdout = std.fs.File.stdout();
|
||||
var stdout_writer = stdout.writer(&stdout_buffer);
|
||||
|
||||
var story = try ink.Story.loadFromString(gpa, source_bytes, .{
|
||||
.stream_writer = &stdout_writer.interface,
|
||||
});
|
||||
defer story.deinit();
|
||||
}
|
||||
|
||||
|
|
|
|||
23
src/root.zig
23
src/root.zig
|
|
@ -1,15 +1,30 @@
|
|||
const std = @import("std");
|
||||
const tokenizer = @import("tokenizer.zig");
|
||||
pub const Ast = @import("Ast.zig");
|
||||
|
||||
pub const Story = struct {
|
||||
pub const LoadOptions = struct {};
|
||||
pub const LoadOptions = struct {
|
||||
stream_writer: *std.Io.Writer,
|
||||
use_color: bool = true,
|
||||
};
|
||||
|
||||
pub fn loadFromString(
|
||||
_: std.mem.Allocator,
|
||||
gpa: std.mem.Allocator,
|
||||
source_bytes: [:0]const u8,
|
||||
_: LoadOptions,
|
||||
options: LoadOptions,
|
||||
) !Story {
|
||||
std.debug.print("{s}\n", .{source_bytes});
|
||||
var ast = try Ast.parse(gpa, source_bytes, "<STDIN>", 0);
|
||||
defer ast.deinit(gpa);
|
||||
|
||||
try ast.render(gpa, options.stream_writer, .{
|
||||
.use_color = options.use_color,
|
||||
});
|
||||
if (ast.errors.len > 0) {
|
||||
try ast.renderErrors(gpa, options.stream_writer, .{
|
||||
.use_color = options.use_color,
|
||||
});
|
||||
return error.Invalid;
|
||||
}
|
||||
return .{};
|
||||
}
|
||||
pub fn deinit(_: *Story) void {}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue