feat: ast structure and utilities

This commit is contained in:
Brett Broadhurst 2026-02-26 18:46:16 -07:00
parent 662c38b360
commit 5268a53148
Failed to generate hash of commit
5 changed files with 970 additions and 5 deletions

311
src/Ast.zig Normal file
View 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;
}