Reworked position context to use tokenizer, works in more contexts

This commit is contained in:
Alexandros Naskos
2020-05-27 01:45:18 +03:00
parent 3c0ee21337
commit f6df35e069
2 changed files with 202 additions and 180 deletions

View File

@@ -433,37 +433,48 @@ pub fn collectImports(import_arr: *std.ArrayList([]const u8), tree: *ast.Tree) !
pub fn getFieldAccessTypeNode(
analysis_ctx: *AnalysisContext,
tokenizer: *std.zig.Tokenizer,
line_length: usize,
) ?*ast.Node {
var current_node = analysis_ctx.in_container;
var current_container = analysis_ctx.in_container;
while (true) {
var next = tokenizer.next();
switch (next.id) {
const tok = tokenizer.next();
switch (tok.id) {
.Eof => return current_node,
.Identifier => {
if (getChildOfSlice(analysis_ctx.tree(), analysis_ctx.scope_nodes, tokenizer.buffer[next.loc.start..next.loc.end])) |child| {
if (getChildOfSlice(analysis_ctx.tree(), analysis_ctx.scope_nodes, tokenizer.buffer[tok.loc.start..tok.loc.end])) |child| {
if (resolveTypeOfNode(analysis_ctx, child)) |node_type| {
current_node = node_type;
} else return null;
} else return null;
},
.Period => {
var after_period = tokenizer.next();
if (after_period.id == .Eof or after_period.id == .Comma) {
return current_node;
} else if (after_period.id == .Identifier) {
// TODO: This works for now, maybe we should filter based on the partial identifier ourselves?
if (after_period.loc.end == line_length) return current_node;
if (getChild(analysis_ctx.tree(), current_node, tokenizer.buffer[after_period.loc.start..after_period.loc.end])) |child| {
if (resolveTypeOfNode(analysis_ctx, child)) |child_type| {
current_node = child_type;
const after_period = tokenizer.next();
switch (after_period.id) {
.Eof => return current_node,
.Identifier => {
// TODO: This works for now, maybe we should filter based on the partial identifier ourselves?
if (after_period.loc.end == tokenizer.buffer.len) return current_node;
if (getChild(analysis_ctx.tree(), current_node, tokenizer.buffer[after_period.loc.start..after_period.loc.end])) |child| {
if (resolveTypeOfNode(analysis_ctx, child)) |child_type| {
current_node = child_type;
} else return null;
} else return null;
} else return null;
},
.QuestionMark => {
std.debug.warn("TODO: .? support\n", .{});
return null;
},
else => {
std.debug.warn("Unrecognized token {} after period.\n", .{after_period.id});
return null;
},
}
},
.PeriodAsterisk => {
std.debug.warn("TODO: .* support\n", .{});
return null;
},
.LParen => {
switch (current_node.id) {
.FnProto => {
@@ -472,7 +483,7 @@ pub fn getFieldAccessTypeNode(
current_node = ret;
// Skip to the right paren
var paren_count: usize = 1;
next = tokenizer.next();
var next = tokenizer.next();
while (next.id != .Eof) : (next = tokenizer.next()) {
if (next.id == .RParen) {
paren_count -= 1;
@@ -480,25 +491,21 @@ pub fn getFieldAccessTypeNode(
} else if (next.id == .LParen) {
paren_count += 1;
}
} else return null;
} else {
return null;
}
} else unreachable;
} else return null;
},
else => {},
}
},
.Keyword_const, .Keyword_var => {
next = tokenizer.next();
if (next.id == .Identifier) {
next = tokenizer.next();
if (next.id != .Equal) return null;
continue;
}
.LBracket => {
std.debug.warn("TODO: Implement [] support\n", .{});
return null;
},
else => std.debug.warn("Not implemented; {}\n", .{next.id}),
else => {
std.debug.warn("Unimplemented token: {}\n", .{tok.id});
return null;
}
}
if (current_node.id == .ContainerDecl or current_node.id == .Root) {
current_container = current_node;
}
@@ -763,3 +770,147 @@ pub fn getImportStr(tree: *ast.Tree, source_index: usize) ?[]const u8 {
}
return null;
}
const types = @import("types.zig");
pub const SourceRange = std.zig.Token.Loc;
const PositionContext = union(enum) {
builtin: SourceRange,
comment,
string_literal: SourceRange,
field_access: SourceRange,
var_access: SourceRange,
enum_literal,
other,
empty,
// @TODO remove pub
pub fn range(self: PositionContext) ?SourceRange {
return switch (self) {
.builtin => |r| r,
.comment => null,
.string_literal => |r| r,
.field_access => |r| r,
.var_access => |r| r,
.enum_literal => null,
.other => null,
.empty => null,
};
}
};
const StackState = struct {
ctx: PositionContext,
stack_id: enum { Paren, Bracket, Global },
};
fn peek(arr: *std.ArrayList(StackState)) !*StackState {
if (arr.items.len == 0) {
try arr.append(.{ .ctx = .empty, .stack_id = .Global });
}
return &arr.items[arr.items.len - 1];
}
fn tokenRange(token: std.zig.Token) SourceRange {
return .{
.start = token.loc.start,
.end = token.loc.end,
};
}
fn tokenRangeAppend(prev: SourceRange, token: std.zig.Token) SourceRange {
return .{
.start = prev.start,
.end = token.loc.end,
};
}
// @TODO Test this thoroughly
pub fn documentPositionContext(allocator: *std.mem.Allocator, document: types.TextDocument, position: types.Position) !PositionContext {
const line = try document.getLine(@intCast(usize, position.line));
const pos_char = @intCast(usize, position.character) + 1;
const idx = if (pos_char > line.len) line.len else pos_char;
var arena = std.heap.ArenaAllocator.init(allocator);
defer arena.deinit();
var tokenizer = std.zig.Tokenizer.init(line[0..idx]);
var stack = try std.ArrayList(StackState).initCapacity(&arena.allocator, 8);
// @TODO What happens when we don't close a string? (probably an .Eof)
while (true) {
const tok = tokenizer.next();
// Early exits.
switch (tok.id) {
.Invalid, .Invalid_ampersands => {
// Single '@' do not return a builtin token so we check this on our own.
if (line[idx - 1] == '@') {
return PositionContext{
.builtin = .{
.start = idx - 1,
.end = idx,
},
};
}
return .other;
},
.LineComment, .DocComment, .ContainerDocComment => return .comment,
.Eof => break,
else => {},
}
// State changes
var curr_ctx = try peek(&stack);
switch (tok.id) {
.StringLiteral, .MultilineStringLiteralLine => curr_ctx.ctx = .{ .string_literal = tokenRange(tok) },
.Identifier => switch (curr_ctx.ctx) {
.empty => curr_ctx.ctx = .{ .var_access = tokenRange(tok) },
else => {},
},
.Builtin => switch (curr_ctx.ctx) {
.empty => curr_ctx.ctx = .{ .builtin = tokenRange(tok) },
else => {},
},
.Period, .PeriodAsterisk => switch (curr_ctx.ctx) {
.empty => curr_ctx.ctx = .enum_literal,
.enum_literal => curr_ctx.ctx = .empty,
.field_access => {},
.other => {},
else => curr_ctx.ctx = .{
.field_access = tokenRangeAppend(curr_ctx.ctx.range().?, tok),
},
},
.QuestionMark => switch (curr_ctx.ctx) {
.field_access => {},
else => curr_ctx.ctx = .empty,
},
.LParen => try stack.append(.{ .ctx = .empty, .stack_id = .Paren }),
.LBracket => try stack.append(.{ .ctx = .empty, .stack_id = .Bracket }),
.RParen => {
_ = stack.pop();
if (curr_ctx.stack_id != .Paren) {
(try peek(&stack)).ctx = .empty;
}
},
.RBracket => {
_ = stack.pop();
if (curr_ctx.stack_id != .Bracket) {
(try peek(&stack)).ctx = .empty;
}
},
else => curr_ctx.ctx = .empty,
}
switch (curr_ctx.ctx) {
.field_access => |r| curr_ctx.ctx = .{
.field_access = tokenRangeAppend(r, tok),
},
else => {},
}
}
return block: {
if (stack.popOrNull()) |state| break :block state.ctx;
break :block .empty;
};
}