Added symbol tables

This commit is contained in:
Alexandros Naskos 2020-06-10 16:12:00 +03:00
parent c2f8537d7b
commit 0a9cf87585
2 changed files with 413 additions and 4 deletions

View File

@ -1013,9 +1013,7 @@ pub fn declsFromIndexInternal(
} }
} }
}, },
.Use => { .Use => {},
},
else => {}, else => {},
} }
} }
@ -1288,3 +1286,401 @@ pub fn getDocumentSymbols(allocator: *std.mem.Allocator, tree: *ast.Tree) ![]typ
return symbols.items; return symbols.items;
} }
const DocumentStore = @import("document_store.zig");
// TODO Will I need to store a handle with the nodes?
pub const Declaration = union(enum) {
ast_node: *ast.Node,
param_decl: *ast.Node.FnProto.ParamDecl,
pointer_payload: struct {
node: *ast.Node.PointerPayload,
condition: *ast.Node,
},
array_payload: struct {
identifier: *ast.Node,
array_expr: *ast.Node,
},
switch_payload: struct {
node: *ast.Node.PointerPayload,
items: []const *ast.Node,
},
};
pub const DocumentScope = struct {
scopes: []const Scope,
// TODO Methods
// TODO When looking up a symbol, have an accept_field argument.
pub fn debugPrint(self: DocumentScope) void {
for (self.scopes) |scope| {
std.debug.warn(
\\--------------------------
\\Scope {}, range: [{}, {})
\\ {} usingnamespaces
\\Decls:
, .{
scope.data,
scope.range.start,
scope.range.end,
scope.uses.len,
});
var decl_it = scope.decls.iterator();
var idx: usize = 0;
while (decl_it.next()) |name_decl| : (idx += 1) {
if (idx != 0) std.debug.warn(", ", .{});
std.debug.warn("{}", .{name_decl.key});
}
std.debug.warn("\n--------------------------\n", .{});
}
}
pub fn deinit(self: DocumentScope, allocator: *std.mem.Allocator) void {
for (self.scopes) |scope| {
scope.decls.deinit();
allocator.free(scope.uses);
}
allocator.free(self.scopes);
}
};
pub const Scope = struct {
pub const Data = union(enum) {
container: *ast.Node, // .id is ContainerDecl or Root
function: *ast.Node, // .id is FnProto
block: *ast.Node, // .id is Block
other,
};
range: SourceRange,
decls: std.StringHashMap(Declaration),
uses: []const *ast.Node.Use,
data: Data,
// TODO Methods
};
pub fn makeDocumentScope(allocator: *std.mem.Allocator, tree: *ast.Tree) !DocumentScope {
var scopes = std.ArrayList(Scope).init(allocator);
errdefer scopes.deinit();
try makeScopeInternal(allocator, &scopes, tree, &tree.root_node.base);
return DocumentScope{
.scopes = scopes.toOwnedSlice(),
};
}
fn nodeSourceRange(tree: *ast.Tree, node: *ast.Node) SourceRange {
return SourceRange{
.start = tree.token_locs[node.firstToken()].start,
.end = tree.token_locs[node.lastToken()].end,
};
}
fn makeScopeInternal(
allocator: *std.mem.Allocator,
scopes: *std.ArrayList(Scope),
tree: *ast.Tree,
node: *ast.Node,
) error{OutOfMemory}!void {
if (node.id == .Root or node.id == .ContainerDecl) {
const ast_decls = switch (node.id) {
.ContainerDecl => node.cast(ast.Node.ContainerDecl).?.fieldsAndDeclsConst(),
.Root => node.cast(ast.Node.Root).?.declsConst(),
else => unreachable,
};
(try scopes.addOne()).* = .{
.range = nodeSourceRange(tree, node),
.decls = std.StringHashMap(Declaration).init(allocator),
.uses = &[0]*ast.Node.Use{},
.data = .{ .container = node },
};
var scope_idx = scopes.items.len - 1;
var uses = std.ArrayList(*ast.Node.Use).init(allocator);
errdefer {
scopes.items[scope_idx].decls.deinit();
uses.deinit();
}
for (ast_decls) |decl| {
if (decl.cast(ast.Node.Use)) |use| {
try uses.append(use);
continue;
}
try makeScopeInternal(allocator, scopes, tree, decl);
const name = getDeclName(tree, decl) orelse continue;
if (try scopes.items[scope_idx].decls.put(name, .{ .ast_node = decl })) |existing| {
// TODO Record a redefinition error.
}
}
scopes.items[scope_idx].uses = uses.toOwnedSlice();
return;
}
switch (node.id) {
.FnProto => {
const func = node.cast(ast.Node.FnProto).?;
(try scopes.addOne()).* = .{
.range = nodeSourceRange(tree, node),
.decls = std.StringHashMap(Declaration).init(allocator),
.uses = &[0]*ast.Node.Use{},
.data = .{ .function = node },
};
var scope_idx = scopes.items.len - 1;
errdefer scopes.items[scope_idx].decls.deinit();
for (func.params()) |*param| {
if (param.name_token) |name_tok| {
if (try scopes.items[scope_idx].decls.put(tree.tokenSlice(name_tok), .{ .param_decl = param })) |existing| {
// TODO Record a redefinition error
}
}
}
if (func.body_node) |body| {
try makeScopeInternal(allocator, scopes, tree, body);
}
return;
},
.TestDecl => {
return try makeScopeInternal(allocator, scopes, tree, node.cast(ast.Node.TestDecl).?.body_node);
},
.Block => {
(try scopes.addOne()).* = .{
.range = nodeSourceRange(tree, node),
.decls = std.StringHashMap(Declaration).init(allocator),
.uses = &[0]*ast.Node.Use{},
.data = .{ .block = node },
};
var scope_idx = scopes.items.len - 1;
var uses = std.ArrayList(*ast.Node.Use).init(allocator);
errdefer {
scopes.items[scope_idx].decls.deinit();
uses.deinit();
}
var child_idx: usize = 0;
while (node.iterate(child_idx)) |child_node| : (child_idx += 1) {
if (child_node.cast(ast.Node.Use)) |use| {
try uses.append(use);
continue;
}
try makeScopeInternal(allocator, scopes, tree, child_node);
if (child_node.cast(ast.Node.VarDecl)) |var_decl| {
const name = tree.tokenSlice(var_decl.name_token);
if (try scopes.items[scope_idx].decls.put(name, .{ .ast_node = child_node })) |existing| {
// TODO Record a redefinition error.
}
}
}
scopes.items[scope_idx].uses = uses.toOwnedSlice();
return;
},
.Comptime => {
return try makeScopeInternal(allocator, scopes, tree, node.cast(ast.Node.Comptime).?.expr);
},
.If => {
const if_node = node.cast(ast.Node.If).?;
if (if_node.payload) |payload| {
std.debug.assert(payload.id == .PointerPayload);
var scope = try scopes.addOne();
scope.* = .{
.range = .{
.start = tree.token_locs[payload.firstToken()].start,
.end = tree.token_locs[if_node.body.lastToken()].end,
},
.decls = std.StringHashMap(Declaration).init(allocator),
.uses = &[0]*ast.Node.Use{},
.data = .other,
};
errdefer scope.decls.deinit();
const ptr_payload = payload.cast(ast.Node.PointerPayload).?;
std.debug.assert(ptr_payload.value_symbol.id == .Identifier);
const name = tree.tokenSlice(ptr_payload.value_symbol.firstToken());
try scope.decls.putNoClobber(name, .{
.pointer_payload = .{
.node = ptr_payload,
.condition = if_node.condition,
},
});
}
try makeScopeInternal(allocator, scopes, tree, if_node.body);
if (if_node.@"else") |else_node| {
if (else_node.payload) |payload| {
std.debug.assert(payload.id == .Payload);
var scope = try scopes.addOne();
scope.* = .{
.range = .{
.start = tree.token_locs[payload.firstToken()].start,
.end = tree.token_locs[else_node.body.lastToken()].end,
},
.decls = std.StringHashMap(Declaration).init(allocator),
.uses = &[0]*ast.Node.Use{},
.data = .other,
};
errdefer scope.decls.deinit();
const err_payload = payload.cast(ast.Node.Payload).?;
std.debug.assert(err_payload.error_symbol.id == .Identifier);
const name = tree.tokenSlice(err_payload.error_symbol.firstToken());
try scope.decls.putNoClobber(name, .{ .ast_node = payload });
}
try makeScopeInternal(allocator, scopes, tree, else_node.body);
}
},
.While => {
const while_node = node.cast(ast.Node.While).?;
if (while_node.payload) |payload| {
std.debug.assert(payload.id == .PointerPayload);
var scope = try scopes.addOne();
scope.* = .{
.range = .{
.start = tree.token_locs[payload.firstToken()].start,
.end = tree.token_locs[while_node.body.lastToken()].end,
},
.decls = std.StringHashMap(Declaration).init(allocator),
.uses = &[0]*ast.Node.Use{},
.data = .other,
};
errdefer scope.decls.deinit();
const ptr_payload = payload.cast(ast.Node.PointerPayload).?;
std.debug.assert(ptr_payload.value_symbol.id == .Identifier);
const name = tree.tokenSlice(ptr_payload.value_symbol.firstToken());
try scope.decls.putNoClobber(name, .{
.pointer_payload = .{
.node = ptr_payload,
.condition = while_node.condition,
},
});
}
try makeScopeInternal(allocator, scopes, tree, while_node.body);
if (while_node.@"else") |else_node| {
if (else_node.payload) |payload| {
std.debug.assert(payload.id == .Payload);
var scope = try scopes.addOne();
scope.* = .{
.range = .{
.start = tree.token_locs[payload.firstToken()].start,
.end = tree.token_locs[else_node.body.lastToken()].end,
},
.decls = std.StringHashMap(Declaration).init(allocator),
.uses = &[0]*ast.Node.Use{},
.data = .other,
};
errdefer scope.decls.deinit();
const err_payload = payload.cast(ast.Node.Payload).?;
std.debug.assert(err_payload.error_symbol.id == .Identifier);
const name = tree.tokenSlice(err_payload.error_symbol.firstToken());
try scope.decls.putNoClobber(name, .{ .ast_node = payload });
}
try makeScopeInternal(allocator, scopes, tree, else_node.body);
}
},
.For => {
const for_node = node.cast(ast.Node.For).?;
std.debug.assert(for_node.payload.id == .PointerIndexPayload);
const ptr_idx_payload = for_node.payload.cast(ast.Node.PointerIndexPayload).?;
std.debug.assert(ptr_idx_payload.value_symbol.id == .Identifier);
var scope = try scopes.addOne();
scope.* = .{
.range = .{
.start = tree.token_locs[ptr_idx_payload.firstToken()].start,
.end = tree.token_locs[for_node.body.lastToken()].end,
},
.decls = std.StringHashMap(Declaration).init(allocator),
.uses = &[0]*ast.Node.Use{},
.data = .other,
};
errdefer scope.decls.deinit();
const value_name = tree.tokenSlice(ptr_idx_payload.value_symbol.firstToken());
try scope.decls.putNoClobber(value_name, .{
.array_payload = .{
.identifier = ptr_idx_payload.value_symbol,
.array_expr = for_node.array_expr,
},
});
if (ptr_idx_payload.index_symbol) |index_symbol| {
std.debug.assert(index_symbol.id == .Identifier);
const index_name = tree.tokenSlice(index_symbol.firstToken());
if (try scope.decls.put(index_name, .{ .ast_node = index_symbol })) |existing| {
// TODO Record a redefinition error
}
}
try makeScopeInternal(allocator, scopes, tree, for_node.body);
if (for_node.@"else") |else_node| {
std.debug.assert(else_node.payload == null);
try makeScopeInternal(allocator, scopes, tree, else_node.body);
}
},
.Switch => {
const switch_node = node.cast(ast.Node.Switch).?;
for (switch_node.casesConst()) |case| {
if (case.*.cast(ast.Node.SwitchCase)) |case_node| {
if (case_node.payload) |payload| {
std.debug.assert(payload.id == .PointerPayload);
var scope = try scopes.addOne();
scope.* = .{
.range = .{
.start = tree.token_locs[payload.firstToken()].start,
.end = tree.token_locs[case_node.expr.lastToken()].end,
},
.decls = std.StringHashMap(Declaration).init(allocator),
.uses = &[0]*ast.Node.Use{},
.data = .other,
};
errdefer scope.decls.deinit();
const ptr_payload = payload.cast(ast.Node.PointerPayload).?;
std.debug.assert(ptr_payload.value_symbol.id == .Identifier);
const name = tree.tokenSlice(ptr_payload.value_symbol.firstToken());
try scope.decls.putNoClobber(name, .{
.switch_payload = .{
.node = ptr_payload,
.items = case_node.itemsConst(),
},
});
}
try makeScopeInternal(allocator, scopes, tree, case_node.expr);
}
}
},
.VarDecl => {
const var_decl = node.cast(ast.Node.VarDecl).?;
if (var_decl.type_node) |type_node| {
try makeScopeInternal(allocator, scopes, tree, type_node);
}
if (var_decl.init_node) |init_node| {
try makeScopeInternal(allocator, scopes, tree, init_node);
}
},
else => {
var child_idx: usize = 0;
while (node.iterate(child_idx)) |child_node| : (child_idx += 1) {
try makeScopeInternal(allocator, scopes, tree, child_node);
}
},
}
}

View File

@ -21,6 +21,7 @@ pub const Handle = struct {
count: usize, count: usize,
import_uris: std.ArrayList([]const u8), import_uris: std.ArrayList([]const u8),
tree: *std.zig.ast.Tree, tree: *std.zig.ast.Tree,
document_scope: analysis.DocumentScope,
associated_build_file: ?*BuildFile, associated_build_file: ?*BuildFile,
is_build_file: ?*BuildFile, is_build_file: ?*BuildFile,
@ -191,6 +192,12 @@ fn newDocument(self: *DocumentStore, uri: []const u8, text: []u8) anyerror!*Hand
var handle = try self.allocator.create(Handle); var handle = try self.allocator.create(Handle);
errdefer self.allocator.destroy(handle); errdefer self.allocator.destroy(handle);
const tree = try std.zig.parse(self.allocator, text);
errdefer tree.deinit();
const document_scope = try analysis.makeDocumentScope(self.allocator, tree);
errdefer document_scope.deinit(self.allocator);
handle.* = Handle{ handle.* = Handle{
.count = 1, .count = 1,
.import_uris = std.ArrayList([]const u8).init(self.allocator), .import_uris = std.ArrayList([]const u8).init(self.allocator),
@ -199,7 +206,8 @@ fn newDocument(self: *DocumentStore, uri: []const u8, text: []u8) anyerror!*Hand
.text = text, .text = text,
.mem = text, .mem = text,
}, },
.tree = try std.zig.parse(self.allocator, text), .tree = tree,
.document_scope = document_scope,
.associated_build_file = null, .associated_build_file = null,
.is_build_file = null, .is_build_file = null,
}; };
@ -362,6 +370,9 @@ fn refreshDocument(self: *DocumentStore, handle: *Handle, zig_lib_path: ?[]const
handle.tree.deinit(); handle.tree.deinit();
handle.tree = try std.zig.parse(self.allocator, handle.document.text); handle.tree = try std.zig.parse(self.allocator, handle.document.text);
handle.document_scope.deinit(self.allocator);
handle.document_scope = try analysis.makeDocumentScope(self.allocator, handle.tree);
// TODO: Better algorithm or data structure? // TODO: Better algorithm or data structure?
// Removing the imports is costly since they live in an array list // Removing the imports is costly since they live in an array list
// Perhaps we should use an AutoHashMap([]const u8, {}) ? // Perhaps we should use an AutoHashMap([]const u8, {}) ?
@ -700,6 +711,8 @@ pub fn deinit(self: *DocumentStore) void {
entry.value.import_uris.deinit(); entry.value.import_uris.deinit();
self.allocator.free(entry.key); self.allocator.free(entry.key);
self.allocator.destroy(entry.value); self.allocator.destroy(entry.value);
entry.value.document_scope.deinit(self.allocator);
} }
self.handles.deinit(); self.handles.deinit();