315 lines
7.9 KiB
Zig
315 lines
7.9 KiB
Zig
//! 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: []const Error,
|
|
|
|
pub const Node = struct {
|
|
tag: Tag,
|
|
loc: Span,
|
|
data: Data,
|
|
|
|
pub const Span = struct {
|
|
start: usize,
|
|
end: usize,
|
|
};
|
|
|
|
pub const Tag = enum {
|
|
file,
|
|
true_literal,
|
|
false_literal,
|
|
number_literal,
|
|
string_literal,
|
|
string_expr,
|
|
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,
|
|
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,
|
|
},
|
|
};
|
|
|
|
fn create(gpa: std.mem.Allocator, tag: Tag, loc: Span) !*Node {
|
|
const node = try gpa.create(Node);
|
|
node.* = .{ .tag = tag, .loc = loc, .data = undefined };
|
|
return node;
|
|
}
|
|
|
|
pub fn createLeaf(gpa: std.mem.Allocator, tag: Tag, loc: Span) !*Node {
|
|
const node = try Node.create(gpa, tag, loc);
|
|
node.data = .{ .leaf = undefined };
|
|
return node;
|
|
}
|
|
|
|
pub fn createBinary(
|
|
gpa: std.mem.Allocator,
|
|
tag: Tag,
|
|
span: Span,
|
|
lhs: ?*Node,
|
|
rhs: ?*Node,
|
|
) !*Node {
|
|
const node = try Node.create(gpa, tag, span);
|
|
node.data = .{
|
|
.bin = .{ .lhs = lhs, .rhs = rhs },
|
|
};
|
|
return node;
|
|
}
|
|
|
|
pub fn createList(
|
|
gpa: std.mem.Allocator,
|
|
tag: Tag,
|
|
span: Span,
|
|
items: ?[]*Node,
|
|
) !*Node {
|
|
const node = try Node.create(gpa, tag, span);
|
|
node.data = .{
|
|
.list = .{ .items = items },
|
|
};
|
|
return node;
|
|
}
|
|
|
|
pub fn createChoice(
|
|
gpa: std.mem.Allocator,
|
|
tag: Tag,
|
|
span: Span,
|
|
start_expr: ?*Node,
|
|
option_expr: ?*Node,
|
|
inner_expr: ?*Node,
|
|
) !*Node {
|
|
const node = try Node.create(gpa, tag, span);
|
|
node.data = .{
|
|
.choice_expr = .{
|
|
.start_expr = start_expr,
|
|
.option_expr = option_expr,
|
|
.inner_expr = inner_expr,
|
|
},
|
|
};
|
|
return node;
|
|
}
|
|
|
|
pub fn createSwitch(
|
|
gpa: std.mem.Allocator,
|
|
tag: Tag,
|
|
loc: Span,
|
|
condition_expr: ?*Node,
|
|
cases_list: []*Node,
|
|
) !*Node {
|
|
const node = try Node.create(gpa, tag, loc);
|
|
node.data = .{
|
|
.switch_stmt = .{
|
|
.condition_expr = condition_expr,
|
|
.cases = cases_list,
|
|
},
|
|
};
|
|
return node;
|
|
}
|
|
|
|
pub fn createKnot(
|
|
gpa: std.mem.Allocator,
|
|
tag: Tag,
|
|
loc: Span,
|
|
prototype: *Node,
|
|
children: ?[]*Node,
|
|
) !*Node {
|
|
const node = try Node.create(gpa, tag, loc);
|
|
node.data = .{
|
|
.knot_decl = .{
|
|
.prototype = prototype,
|
|
.children = children,
|
|
},
|
|
};
|
|
return node;
|
|
}
|
|
};
|
|
|
|
pub const Error = struct {
|
|
tag: Tag,
|
|
loc: Location,
|
|
|
|
pub const Location = struct {
|
|
start: usize,
|
|
end: usize,
|
|
};
|
|
|
|
pub const Tag = enum {
|
|
expected_token,
|
|
expected_newline,
|
|
expected_double_quote,
|
|
expected_identifier,
|
|
expected_expression,
|
|
invalid_expression,
|
|
invalid_lvalue,
|
|
too_many_arguments,
|
|
too_many_parameters,
|
|
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(
|
|
gpa: std.mem.Allocator,
|
|
arena: std.mem.Allocator,
|
|
source: [:0]const u8,
|
|
filename: []const u8,
|
|
_: u32,
|
|
) !Ast {
|
|
var parser: Parse = .{
|
|
.gpa = gpa,
|
|
.arena = arena,
|
|
.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 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 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;
|
|
}
|