rework nodesAtLoc

This commit is contained in:
Techatrix 2023-03-06 17:43:20 +01:00 committed by Lee Cannon
parent 397b5bc78f
commit d5ac6b9734

View File

@ -1584,41 +1584,92 @@ pub fn nodeChildrenRecursiveAlloc(allocator: std.mem.Allocator, tree: Ast, node:
pub fn nodesAtLoc(allocator: std.mem.Allocator, tree: Ast, loc: offsets.Loc) error{OutOfMemory}![]Ast.Node.Index { pub fn nodesAtLoc(allocator: std.mem.Allocator, tree: Ast, loc: offsets.Loc) error{OutOfMemory}![]Ast.Node.Index {
std.debug.assert(loc.start <= loc.end and loc.end <= tree.source.len); std.debug.assert(loc.start <= loc.end and loc.end <= tree.source.len);
var nodes = std.ArrayListUnmanaged(Ast.Node.Index){}; const Context = struct {
errdefer nodes.deinit(allocator); allocator: std.mem.Allocator,
nodes: std.ArrayListUnmanaged(Ast.Node.Index) = .{},
locs: std.ArrayListUnmanaged(offsets.Loc) = .{},
pub fn append(self: *@This(), ast: Ast, node: Ast.Node.Index) !void {
if (node == 0) return;
try self.nodes.append(self.allocator, node);
try self.locs.append(self.allocator, offsets.nodeToLoc(ast, node));
}
};
var context: Context = .{ .allocator = allocator };
defer context.nodes.deinit(allocator);
defer context.locs.deinit(allocator);
try context.nodes.ensureTotalCapacity(allocator, 32);
var parent: Ast.Node.Index = 0; // root node var parent: Ast.Node.Index = 0; // root node
try nodes.ensureTotalCapacity(allocator, 32);
while (true) { while (true) {
const children = try nodeChildrenAlloc(allocator, tree, parent); try iterateChildren(tree, parent, &context, error{OutOfMemory}, Context.append);
defer allocator.free(children);
var children_loc: ?offsets.Loc = null; if (smallestEnclosingSubrange(context.locs.items, loc)) |subslice| {
for (children) |child_node| { std.debug.assert(subslice.len != 0);
const child_loc = offsets.nodeToLoc(tree, child_node); const nodes = context.nodes.items[subslice.start .. subslice.start + subslice.len];
if (nodes.len == 1) { // recurse over single child node
const merge_child = offsets.locIntersect(loc, child_loc) or offsets.locInside(child_loc, loc); parent = nodes[0];
context.nodes.clearRetainingCapacity();
if (merge_child) { context.locs.clearRetainingCapacity();
children_loc = if (children_loc) |l| offsets.locMerge(l, child_loc) else child_loc; continue;
try nodes.append(allocator, child_node); } else { // end-condition: found enclosing children
} else { return try allocator.dupe(Ast.Node.Index, nodes);
if (nodes.items.len != 0) break;
} }
} } else { // the children cannot enclose the given source location
context.nodes.clearRetainingCapacity();
if (children_loc == null or !offsets.locInside(loc, children_loc.?)) { context.nodes.appendAssumeCapacity(parent); // capacity is never 0
nodes.clearRetainingCapacity(); return try context.nodes.toOwnedSlice(allocator);
nodes.appendAssumeCapacity(parent); // capacity is never 0
return try nodes.toOwnedSlice(allocator);
}
if (nodes.items.len == 1) {
parent = nodes.items[0];
nodes.clearRetainingCapacity();
} else {
return try nodes.toOwnedSlice(allocator);
} }
} }
} }
/// the following code can be described as the the following problem:
/// @param children a non-intersecting list of source ranges
/// @param loc be a source range
///
/// @return a slice of #children
///
/// Return the smallest possible subrange of #children whose
/// combined source range is inside #loc.
/// If #children cannot contain #loc i.e #loc is too large, return null.
/// @see tests/utility.ast.zig for usage examples
pub fn smallestEnclosingSubrange(children: []const offsets.Loc, loc: offsets.Loc) ?struct {
start: usize,
len: usize,
} {
switch (children.len) {
0 => return null,
1 => return if (offsets.locInside(loc, children[0])) .{ .start = 0, .len = 1 } else null,
else => {
for (children[0 .. children.len - 1], children[1..]) |previous_loc, current_loc| {
std.debug.assert(previous_loc.end <= current_loc.start); // must by sorted
std.debug.assert(!offsets.locIntersect(previous_loc, current_loc)); // must be non-intersecting
}
},
}
var i: usize = 0;
const start: usize = while (i < children.len) : (i += 1) {
const child_loc = children[i];
if (child_loc.end < loc.start) continue;
if (child_loc.start <= loc.start) {
break i;
} else if (i != 0) {
break i - 1;
} else {
return null;
}
} else return null;
const end = while (i < children.len) : (i += 1) {
const child_loc = children[i];
if (loc.end <= child_loc.end) break i + 1;
} else return null;
return .{
.start = start,
.len = end - start,
};
}