From d0837ffc518f4fe4b9692f87f8f07aa4a783789e Mon Sep 17 00:00:00 2001 From: SuperAuguste Date: Mon, 11 May 2020 08:28:08 -0400 Subject: [PATCH 1/4] Start implementing completion --- src/analysis.zig | 134 +++++++++++++++++++++++++++++++++++++++++++++++ src/main.zig | 34 ++++++++++++ 2 files changed, 168 insertions(+) diff --git a/src/analysis.zig b/src/analysis.zig index e5c3c0e..82290b7 100644 --- a/src/analysis.zig +++ b/src/analysis.zig @@ -129,3 +129,137 @@ pub fn isCamelCase(name: []const u8) bool { pub fn isPascalCase(name: []const u8) bool { return std.ascii.isUpper(name[0]) and std.mem.indexOf(u8, name, "_") == null; } + +// ANALYSIS ENGINE +// Very WIP, only works on global values at the moment + +// TODO Major rework needed. This works in a sandbox, but not in the actual editor as completion +// stubs (e.g. "completeThis", "completeThis.", etc.) causes errors in the Zig parser. + +pub fn getDecl(tree: *std.zig.ast.Tree, name: []const u8) ?*std.zig.ast.Node { + var decls = tree.root_node.decls.iterator(0); + while (decls.next()) |decl_ptr| { + var decl = decl_ptr.*; + switch (decl.id) { + .VarDecl => { + const vari = decl.cast(std.zig.ast.Node.VarDecl).?; + if (std.mem.eql(u8, tree.tokenSlice(vari.name_token), name)) return decl; + }, + .FnProto => { + const func = decl.cast(std.zig.ast.Node.FnProto).?; + if (func.name_token != null and std.mem.eql(u8, tree.tokenSlice(func.name_token.?), name)) return decl; + }, + else => {} + } + } + return null; +} + +pub fn getContainerField(tree: *std.zig.ast.Tree, container: *std.zig.ast.ContainerDecl, name: []const u8) ?*std.zig.ast.Node { + var decls = container.decls.iterator(0); + while (decls.next()) |decl_ptr| { + var decl = decl_ptr.*; + switch (decl.id) { + .VarDecl => { + const vari = decl.cast(std.zig.ast.Node.VarDecl).?; + if (std.mem.eql(u8, tree.tokenSlice(vari.name_token), name)) return decl; + }, + .FnProto => { + const func = decl.cast(std.zig.ast.Node.FnProto).?; + if (func.name_token != null and std.mem.eql(u8, tree.tokenSlice(func.name_token.?), name)) return decl; + }, + else => {} + } + } + return null; +} + +pub fn resolveNode(tree: *std.zig.ast.Tree, node: *std.zig.ast.Node) ?*std.zig.ast.Node { + switch (node.id) { + .Identifier => { + var id = node.cast(std.zig.ast.Node.Identifier).?; + var maybe_decl = getDecl(tree, tree.tokenSlice(id.token)); + if (maybe_decl) |decl| { + return decl; + } + }, + else => {} + } + + return null; +} + +pub fn getCompletionsForValue(allocator: *std.mem.Allocator, tree: *std.zig.ast.Tree, node: *std.zig.ast.Node) ![]*std.zig.ast.Node { + var nodes = std.ArrayList(*std.zig.ast.Node).init(allocator); + + switch (node.id) { + .ContainerDecl => { + var cont = node.cast(std.zig.ast.Node.ContainerDecl).?; + var decl_it = cont.fields_and_decls.iterator(0); + + while (decl_it.next()) |decl| { + try nodes.append(decl.*); + } + }, + .ContainerField => { + var field = node.cast(std.zig.ast.Node.ContainerField).?; + + }, + .VarDecl => { + var vari = node.cast(std.zig.ast.Node.VarDecl).?; + // if (vari.type_node) |type_node| return getCompletionsForValue(allocator, tree, type_node); + // if (vari.init_node) |init_node| return getCompletionsForValue(allocator, tree, init_node); + return getCompletionsForValue(allocator, tree, vari.type_node orelse vari.init_node.?); + }, + .Identifier => { + var id = node.cast(std.zig.ast.Node.Identifier).?; + var maybe_decl = getDecl(tree, tree.tokenSlice(id.token)); + if (maybe_decl) |decl| return getCompletionsForValue(allocator, tree, decl); + }, + .FnProto => { + var func = node.cast(std.zig.ast.Node.FnProto).?; + // std.debug.warn("{}", .{func.return_type}); + switch (func.return_type) { + .Explicit, .InferErrorSet => |return_node| { + if (resolveNode(tree, return_node)) |resolved_node| { + return getCompletionsForValue(allocator, tree, resolved_node); + } + } + } + }, + .InfixOp => { + var infix = node.cast(std.zig.ast.Node.InfixOp).?; + + // std.debug.warn("{}", .{infix}); + // switch (infix.op) { + // .Period => { + // std.debug.warn("\n{}.{}\n", .{ + // getCompletionsForValue(allocator, tree, infix.lhs), + // infix.rhs + // }); + // return getCompletionsForValue(allocator, tree, infix.lhs); + // }, + // else => {} + // } + }, + .SuffixOp => { + var suffix = node.cast(std.zig.ast.Node.SuffixOp).?; + + // std.debug.warn("{}", .{suffix}); + + switch (suffix.op) { + .Call => { + if (resolveNode(tree, suffix.lhs.node)) |rnode| { + return getCompletionsForValue(allocator, tree, rnode); + } + }, + else => {} + } + }, + else => { + std.debug.warn("{}\n\n", .{node.id}); + } + } + + return nodes.toOwnedSlice(); +} diff --git a/src/main.zig b/src/main.zig index 59bb7ac..d3da550 100644 --- a/src/main.zig +++ b/src/main.zig @@ -298,6 +298,38 @@ fn completeGlobal(id: i64, document: *types.TextDocument, config: Config) !void }); } +fn completeFieldAccess(id: i64, document: *types.TextDocument, index: usize, config: Config) !void { + // The tree uses its own arena, so we just pass our main allocator. + var tree = try std.zig.parse(allocator, document.text); + + if (tree.errors.len > 0) { + if (document.sane_text) |sane_text| { + tree.deinit(); + tree = try std.zig.parse(allocator, sane_text); + } else return try respondGeneric(id, no_completions_response); + } + else try cacheSane(document); + + defer tree.deinit(); + + // We use a local arena allocator to deallocate all temporary data without iterating + var arena = std.heap.ArenaAllocator.init(allocator); + var completions = std.ArrayList(types.CompletionItem).init(&arena.allocator); + // Deallocate all temporary data. + defer arena.deinit(); + + try log("{}", .{}); + + try send(types.Response{ + .id = .{.Integer = id}, + .result = .{ + .CompletionList = .{ + .isIncomplete = false, + .items = completions.items, + }, + }, + }); +} // Compute builtin completions at comptime. const builtin_completions = block: { @@ -565,6 +597,8 @@ fn processJsonRpc(parser: *std.json.Parser, json: []const u8, config: Config) !v }); } else if (pos_context == .var_access or pos_context == .empty) { try completeGlobal(id, document, config); + } else if (pos_context == .field_access) { + try completeFieldAccess(id, document, pos_index, config); } else { try respondGeneric(id, no_completions_response); } From a3067f88b139712a5b0134c5bf319b2aee462e83 Mon Sep 17 00:00:00 2001 From: SuperAuguste Date: Wed, 13 May 2020 09:03:33 -0400 Subject: [PATCH 2/4] Add new analysis engine --- src/analysis.zig | 228 ++++++++++++++++++++++++++--------------------- src/main.zig | 12 +-- 2 files changed, 125 insertions(+), 115 deletions(-) diff --git a/src/analysis.zig b/src/analysis.zig index 82290b7..40e327b 100644 --- a/src/analysis.zig +++ b/src/analysis.zig @@ -131,135 +131,155 @@ pub fn isPascalCase(name: []const u8) bool { } // ANALYSIS ENGINE -// Very WIP, only works on global values at the moment -// TODO Major rework needed. This works in a sandbox, but not in the actual editor as completion -// stubs (e.g. "completeThis", "completeThis.", etc.) causes errors in the Zig parser. - -pub fn getDecl(tree: *std.zig.ast.Tree, name: []const u8) ?*std.zig.ast.Node { - var decls = tree.root_node.decls.iterator(0); - while (decls.next()) |decl_ptr| { - var decl = decl_ptr.*; - switch (decl.id) { +/// Gets the child of node +pub fn getChild(tree: *std.zig.ast.Tree, node: *std.zig.ast.Node, name: []const u8) ?*std.zig.ast.Node { + var index: usize = 0; + while (node.iterate(index)) |child| { + switch (child.id) { .VarDecl => { - const vari = decl.cast(std.zig.ast.Node.VarDecl).?; - if (std.mem.eql(u8, tree.tokenSlice(vari.name_token), name)) return decl; + const vari = child.cast(std.zig.ast.Node.VarDecl).?; + if (std.mem.eql(u8, tree.tokenSlice(vari.name_token), name)) return child; }, .FnProto => { - const func = decl.cast(std.zig.ast.Node.FnProto).?; - if (func.name_token != null and std.mem.eql(u8, tree.tokenSlice(func.name_token.?), name)) return decl; + const func = child.cast(std.zig.ast.Node.FnProto).?; + if (func.name_token != null and std.mem.eql(u8, tree.tokenSlice(func.name_token.?), name)) return child; + }, + .ContainerField => { + const field = child.cast(std.zig.ast.Node.ContainerField).?; + if (std.mem.eql(u8, tree.tokenSlice(field.name_token), name)) return child; }, else => {} } + index += 1; } return null; } -pub fn getContainerField(tree: *std.zig.ast.Tree, container: *std.zig.ast.ContainerDecl, name: []const u8) ?*std.zig.ast.Node { - var decls = container.decls.iterator(0); - while (decls.next()) |decl_ptr| { - var decl = decl_ptr.*; - switch (decl.id) { - .VarDecl => { - const vari = decl.cast(std.zig.ast.Node.VarDecl).?; - if (std.mem.eql(u8, tree.tokenSlice(vari.name_token), name)) return decl; - }, - .FnProto => { - const func = decl.cast(std.zig.ast.Node.FnProto).?; - if (func.name_token != null and std.mem.eql(u8, tree.tokenSlice(func.name_token.?), name)) return decl; - }, - else => {} - } - } - return null; -} - -pub fn resolveNode(tree: *std.zig.ast.Tree, node: *std.zig.ast.Node) ?*std.zig.ast.Node { +/// Resolves the type of a node +pub fn resolveTypeOfNode(tree: *std.zig.ast.Tree, node: *std.zig.ast.Node) ?*std.zig.ast.Node { switch (node.id) { - .Identifier => { - var id = node.cast(std.zig.ast.Node.Identifier).?; - var maybe_decl = getDecl(tree, tree.tokenSlice(id.token)); - if (maybe_decl) |decl| { - return decl; - } - }, - else => {} - } - - return null; -} - -pub fn getCompletionsForValue(allocator: *std.mem.Allocator, tree: *std.zig.ast.Tree, node: *std.zig.ast.Node) ![]*std.zig.ast.Node { - var nodes = std.ArrayList(*std.zig.ast.Node).init(allocator); - - switch (node.id) { - .ContainerDecl => { - var cont = node.cast(std.zig.ast.Node.ContainerDecl).?; - var decl_it = cont.fields_and_decls.iterator(0); - - while (decl_it.next()) |decl| { - try nodes.append(decl.*); - } - }, - .ContainerField => { - var field = node.cast(std.zig.ast.Node.ContainerField).?; - - }, .VarDecl => { - var vari = node.cast(std.zig.ast.Node.VarDecl).?; - // if (vari.type_node) |type_node| return getCompletionsForValue(allocator, tree, type_node); - // if (vari.init_node) |init_node| return getCompletionsForValue(allocator, tree, init_node); - return getCompletionsForValue(allocator, tree, vari.type_node orelse vari.init_node.?); - }, - .Identifier => { - var id = node.cast(std.zig.ast.Node.Identifier).?; - var maybe_decl = getDecl(tree, tree.tokenSlice(id.token)); - if (maybe_decl) |decl| return getCompletionsForValue(allocator, tree, decl); + const vari = node.cast(std.zig.ast.Node.VarDecl).?; + return resolveTypeOfNode(tree, vari.type_node orelse vari.init_node.?) orelse null; }, .FnProto => { - var func = node.cast(std.zig.ast.Node.FnProto).?; - // std.debug.warn("{}", .{func.return_type}); + const func = node.cast(std.zig.ast.Node.FnProto).?; switch (func.return_type) { - .Explicit, .InferErrorSet => |return_node| { - if (resolveNode(tree, return_node)) |resolved_node| { - return getCompletionsForValue(allocator, tree, resolved_node); - } - } + .Explicit, .InferErrorSet => |return_type| {return resolveTypeOfNode(tree, return_type);} + } + }, + .Identifier => { + if (getChild(tree, &tree.root_node.base, tree.getNodeSource(node))) |child| { + return resolveTypeOfNode(tree, child); + } else return null; + }, + .ContainerDecl => { + return node; + }, + .ContainerField => { + const field = node.cast(std.zig.ast.Node.ContainerField).?; + return resolveTypeOfNode(tree, field.type_expr.?); + }, + .SuffixOp => { + const suffix_op = node.cast(std.zig.ast.Node.SuffixOp).?; + switch (suffix_op.op) { + .Call => { + return resolveTypeOfNode(tree, suffix_op.lhs.node); + }, + else => {} } }, .InfixOp => { - var infix = node.cast(std.zig.ast.Node.InfixOp).?; - - // std.debug.warn("{}", .{infix}); - // switch (infix.op) { - // .Period => { - // std.debug.warn("\n{}.{}\n", .{ - // getCompletionsForValue(allocator, tree, infix.lhs), - // infix.rhs - // }); - // return getCompletionsForValue(allocator, tree, infix.lhs); - // }, - // else => {} - // } - }, - .SuffixOp => { - var suffix = node.cast(std.zig.ast.Node.SuffixOp).?; - - // std.debug.warn("{}", .{suffix}); - - switch (suffix.op) { - .Call => { - if (resolveNode(tree, suffix.lhs.node)) |rnode| { - return getCompletionsForValue(allocator, tree, rnode); - } + const infix_op = node.cast(std.zig.ast.Node.InfixOp).?; + switch (infix_op.op) { + .Period => { + var left = resolveTypeOfNode(tree, infix_op.lhs).?; + return getChild(tree, left, nodeToString(tree, infix_op.rhs)); }, else => {} } }, else => { - std.debug.warn("{}\n\n", .{node.id}); + std.debug.warn("Type resolution case not implemented; {}\n", .{node.id}); + } + } + return null; +} + +pub fn getNodeFromTokens(tree: *std.zig.ast.Tree, node: *std.zig.ast.Node, tokenizer: *std.zig.Tokenizer) ?*std.zig.ast.Node { + var current_node = node; + + while (true) { + var next = tokenizer.next(); + switch (next.id) { + .Eof => { + return current_node; + }, + .Identifier => { + // var root = current_node.cast(std.zig.ast.Node.Root).?; + // current_node. + if (getChild(tree, current_node, tokenizer.buffer[next.start..next.end])) |child| { + if (resolveTypeOfNode(tree, child)) |node_type| { + current_node = resolveTypeOfNode(tree, child).?; + } + } else return null; + }, + .Period => { + var after_period = tokenizer.next(); + if (after_period.id == .Eof) { + return current_node; + } else if (after_period.id == .Identifier) { + if (getChild(tree, current_node, tokenizer.buffer[after_period.start..after_period.end])) |child| { + // std.debug.warn("{}", .{child}); + current_node = resolveTypeOfNode(tree, child).?; + } else return null; + } + }, + else => { + std.debug.warn("Not implemented; {}\n", .{next.id}); + } } } - return nodes.toOwnedSlice(); + return current_node; +} + +pub fn getCompletionsFromNode(allocator: *std.mem.Allocator, tree: *std.zig.ast.Tree, node: *std.zig.ast.Node) ![]*std.zig.ast.Node { + var nodes = std.ArrayList(*std.zig.ast.Node).init(allocator); + + var index: usize = 0; + while (node.iterate(index)) |child_node| { + try nodes.append(child_node); + + index += 1; + } + + return nodes.items; +} + +pub fn nodeToString(tree: *std.zig.ast.Tree, node: *std.zig.ast.Node) []const u8 { + switch (node.id) { + .ContainerField => { + const field = node.cast(std.zig.ast.Node.ContainerField).?; + return tree.tokenSlice(field.name_token); + }, + .Identifier => { + const field = node.cast(std.zig.ast.Node.Identifier).?; + return tree.tokenSlice(field.token); + }, + else => { + std.debug.warn("INVALID: {}\n", .{node.id}); + } + } + + return ""; +} + +pub fn nodesToString(tree: *std.zig.ast.Tree, maybe_nodes: ?[]*std.zig.ast.Node) void { + if (maybe_nodes) |nodes| { + for (nodes) |node| { + std.debug.warn("- {}\n", .{nodeToString(tree, node)}); + } + } else std.debug.warn("No nodes\n", .{}); } diff --git a/src/main.zig b/src/main.zig index d3da550..c4fb530 100644 --- a/src/main.zig +++ b/src/main.zig @@ -299,17 +299,7 @@ fn completeGlobal(id: i64, document: *types.TextDocument, config: Config) !void } fn completeFieldAccess(id: i64, document: *types.TextDocument, index: usize, config: Config) !void { - // The tree uses its own arena, so we just pass our main allocator. - var tree = try std.zig.parse(allocator, document.text); - - if (tree.errors.len > 0) { - if (document.sane_text) |sane_text| { - tree.deinit(); - tree = try std.zig.parse(allocator, sane_text); - } else return try respondGeneric(id, no_completions_response); - } - else try cacheSane(document); - + var tree = try std.zig.parse(allocator, sane_text); defer tree.deinit(); // We use a local arena allocator to deallocate all temporary data without iterating From 9ae912efddc7012b8aa0939d3122280832180000 Mon Sep 17 00:00:00 2001 From: SuperAuguste Date: Wed, 13 May 2020 10:10:20 -0400 Subject: [PATCH 3/4] super basic completion, tons of things to iron out --- src/main.zig | 62 ++++++++++++++++++++++++++++++++++++--------------- src/types.zig | 12 ++++++++++ 2 files changed, 56 insertions(+), 18 deletions(-) diff --git a/src/main.zig b/src/main.zig index c4fb530..af6fc2c 100644 --- a/src/main.zig +++ b/src/main.zig @@ -298,27 +298,53 @@ fn completeGlobal(id: i64, document: *types.TextDocument, config: Config) !void }); } -fn completeFieldAccess(id: i64, document: *types.TextDocument, index: usize, config: Config) !void { - var tree = try std.zig.parse(allocator, sane_text); - defer tree.deinit(); +fn completeFieldAccess(id: i64, document: *types.TextDocument, position: types.Position, config: Config) !void { + if (document.sane_text) |sane_text| { + var tree = try std.zig.parse(allocator, sane_text); + defer tree.deinit(); - // We use a local arena allocator to deallocate all temporary data without iterating - var arena = std.heap.ArenaAllocator.init(allocator); - var completions = std.ArrayList(types.CompletionItem).init(&arena.allocator); - // Deallocate all temporary data. - defer arena.deinit(); + // We use a local arena allocator to deallocate all temporary data without iterating + var arena = std.heap.ArenaAllocator.init(allocator); + var completions = std.ArrayList(types.CompletionItem).init(&arena.allocator); + // Deallocate all temporary data. + defer arena.deinit(); - try log("{}", .{}); + var line = try document.getLine(@intCast(usize, position.line)); + try log("{}", .{line}); + var tokenizer = std.zig.Tokenizer.init(line); - try send(types.Response{ - .id = .{.Integer = id}, - .result = .{ - .CompletionList = .{ - .isIncomplete = false, - .items = completions.items, + if (analysis.getNodeFromTokens(tree, &tree.root_node.base, &tokenizer)) |node| { + var index: usize = 0; + while (node.iterate(index)) |child_node| { + try completions.append(.{ + .label = analysis.nodeToString(tree, child_node), + .kind = .Variable, + }); + + index += 1; + } + } + + try send(types.Response{ + .id = .{.Integer = id}, + .result = .{ + .CompletionList = .{ + .isIncomplete = false, + .items = completions.items, + }, }, - }, - }); + }); + } else { + return try send(types.Response{ + .id = .{.Integer = id}, + .result = .{ + .CompletionList = .{ + .isIncomplete = false, + .items = &[_]types.CompletionItem{}, + }, + }, + }); + } } // Compute builtin completions at comptime. @@ -588,7 +614,7 @@ fn processJsonRpc(parser: *std.json.Parser, json: []const u8, config: Config) !v } else if (pos_context == .var_access or pos_context == .empty) { try completeGlobal(id, document, config); } else if (pos_context == .field_access) { - try completeFieldAccess(id, document, pos_index, config); + try completeFieldAccess(id, document, pos, config); } else { try respondGeneric(id, no_completions_response); } diff --git a/src/types.zig b/src/types.zig index 856fc8c..8375cec 100644 --- a/src/types.zig +++ b/src/types.zig @@ -158,6 +158,18 @@ pub const TextDocument = struct { return @intCast(usize, index); } + + pub fn getLine(self: TextDocument, target_line: usize) ![]const u8 { + var split_iterator = std.mem.split(self.text, "\n"); + + var line: i64 = 0; + while (line < target_line) : (line += 1) { + _ = split_iterator.next() orelse return error.InvalidParams; + } + if (split_iterator.next()) |next| { + return next; + } else return error.InvalidParams; + } }; pub const TextEdit = struct { From dc292e74ee0f0cbe5d87f88458b5fbf6d2a4e7f3 Mon Sep 17 00:00:00 2001 From: SuperAuguste Date: Wed, 13 May 2020 11:43:28 -0400 Subject: [PATCH 4/4] null bug fixes --- src/analysis.zig | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/analysis.zig b/src/analysis.zig index 40e327b..9b87ea4 100644 --- a/src/analysis.zig +++ b/src/analysis.zig @@ -221,7 +221,9 @@ pub fn getNodeFromTokens(tree: *std.zig.ast.Tree, node: *std.zig.ast.Node, token // current_node. if (getChild(tree, current_node, tokenizer.buffer[next.start..next.end])) |child| { if (resolveTypeOfNode(tree, child)) |node_type| { - current_node = resolveTypeOfNode(tree, child).?; + if (resolveTypeOfNode(tree, child)) |child_type| { + current_node = child_type; + } else return null; } } else return null; }, @@ -231,8 +233,9 @@ pub fn getNodeFromTokens(tree: *std.zig.ast.Tree, node: *std.zig.ast.Node, token return current_node; } else if (after_period.id == .Identifier) { if (getChild(tree, current_node, tokenizer.buffer[after_period.start..after_period.end])) |child| { - // std.debug.warn("{}", .{child}); - current_node = resolveTypeOfNode(tree, child).?; + if (resolveTypeOfNode(tree, child)) |child_type| { + current_node = child_type; + } else return null; } else return null; } },