//! 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; }