Merge pull request #793 from Techatrix/intern-pool
ComptimeInterpreter: Intern Pool
This commit is contained in:
commit
c3f58538e8
20
build.zig
20
build.zig
@ -178,17 +178,19 @@ pub fn build(b: *std.build.Builder) !void {
|
|||||||
const test_step = b.step("test", "Run all the tests");
|
const test_step = b.step("test", "Run all the tests");
|
||||||
test_step.dependOn(b.getInstallStep());
|
test_step.dependOn(b.getInstallStep());
|
||||||
|
|
||||||
|
const test_filter = b.option(
|
||||||
|
[]const u8,
|
||||||
|
"test-filter",
|
||||||
|
"Skip tests that do not match filter",
|
||||||
|
);
|
||||||
|
|
||||||
var tests = b.addTest(.{
|
var tests = b.addTest(.{
|
||||||
.root_source_file = .{ .path = "tests/tests.zig" },
|
.root_source_file = .{ .path = "tests/tests.zig" },
|
||||||
.target = target,
|
.target = target,
|
||||||
.optimize = .Debug,
|
.optimize = .Debug,
|
||||||
});
|
});
|
||||||
|
|
||||||
tests.setFilter(b.option(
|
tests.setFilter(test_filter);
|
||||||
[]const u8,
|
|
||||||
"test-filter",
|
|
||||||
"Skip tests that do not match filter",
|
|
||||||
));
|
|
||||||
|
|
||||||
if (coverage) {
|
if (coverage) {
|
||||||
const src_dir = b.pathJoin(&.{ build_root_path, "src" });
|
const src_dir = b.pathJoin(&.{ build_root_path, "src" });
|
||||||
@ -218,6 +220,14 @@ pub fn build(b: *std.build.Builder) !void {
|
|||||||
tests.addModule("diffz", diffz_module);
|
tests.addModule("diffz", diffz_module);
|
||||||
|
|
||||||
test_step.dependOn(&tests.step);
|
test_step.dependOn(&tests.step);
|
||||||
|
|
||||||
|
var src_tests = b.addTest(.{
|
||||||
|
.root_source_file = .{ .path = "src/zls.zig" },
|
||||||
|
.target = target,
|
||||||
|
.optimize = .Debug,
|
||||||
|
});
|
||||||
|
src_tests.setFilter(test_filter);
|
||||||
|
test_step.dependOn(&src_tests.step);
|
||||||
}
|
}
|
||||||
|
|
||||||
const CheckSubmodulesStep = struct {
|
const CheckSubmodulesStep = struct {
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -68,6 +68,10 @@ pub const Handle = struct {
|
|||||||
associated_build_file: ?Uri = null,
|
associated_build_file: ?Uri = null,
|
||||||
|
|
||||||
pub fn deinit(self: *Handle, allocator: std.mem.Allocator) void {
|
pub fn deinit(self: *Handle, allocator: std.mem.Allocator) void {
|
||||||
|
if (self.interpreter) |interpreter| {
|
||||||
|
interpreter.deinit();
|
||||||
|
allocator.destroy(interpreter);
|
||||||
|
}
|
||||||
self.document_scope.deinit(allocator);
|
self.document_scope.deinit(allocator);
|
||||||
self.tree.deinit(allocator);
|
self.tree.deinit(allocator);
|
||||||
allocator.free(self.text);
|
allocator.free(self.text);
|
||||||
@ -1014,16 +1018,26 @@ pub fn enumCompletionItems(self: DocumentStore, arena: std.mem.Allocator, handle
|
|||||||
return try self.tagStoreCompletionItems(arena, handle, "enum_completions");
|
return try self.tagStoreCompletionItems(arena, handle, "enum_completions");
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn ensureInterpreterExists(self: *DocumentStore, uri: Uri) !void {
|
pub fn ensureInterpreterExists(self: *DocumentStore, uri: Uri) !*ComptimeInterpreter {
|
||||||
var handle = self.handles.get(uri).?;
|
var handle = self.handles.get(uri).?;
|
||||||
if (handle.interpreter == null) {
|
if (handle.interpreter != null) return handle.interpreter.?;
|
||||||
var int = try self.allocator.create(ComptimeInterpreter);
|
|
||||||
int.* = ComptimeInterpreter{
|
{
|
||||||
|
var interpreter = try self.allocator.create(ComptimeInterpreter);
|
||||||
|
errdefer self.allocator.destroy(interpreter);
|
||||||
|
|
||||||
|
var ip = try ComptimeInterpreter.InternPool.init(self.allocator);
|
||||||
|
errdefer ip.deinit(self.allocator);
|
||||||
|
|
||||||
|
interpreter.* = ComptimeInterpreter{
|
||||||
.allocator = self.allocator,
|
.allocator = self.allocator,
|
||||||
|
.ip = ip,
|
||||||
.document_store = self,
|
.document_store = self,
|
||||||
.uri = uri,
|
.uri = uri,
|
||||||
};
|
};
|
||||||
handle.interpreter = int;
|
handle.interpreter = interpreter;
|
||||||
_ = try int.interpret(0, null, .{});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_ = try handle.interpreter.?.interpret(0, .none, .{});
|
||||||
|
return handle.interpreter.?;
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,7 @@ const tracy = @import("tracy.zig");
|
|||||||
const uri_utils = @import("uri.zig");
|
const uri_utils = @import("uri.zig");
|
||||||
const diff = @import("diff.zig");
|
const diff = @import("diff.zig");
|
||||||
const ComptimeInterpreter = @import("ComptimeInterpreter.zig");
|
const ComptimeInterpreter = @import("ComptimeInterpreter.zig");
|
||||||
|
const analyser = @import("analyser/analyser.zig");
|
||||||
|
|
||||||
const data = @import("data/data.zig");
|
const data = @import("data/data.zig");
|
||||||
const snipped_data = @import("data/snippets.zig");
|
const snipped_data = @import("data/snippets.zig");
|
||||||
@ -362,7 +363,7 @@ fn generateDiagnostics(server: *Server, handle: DocumentStore.Handle) error{OutO
|
|||||||
var err_it = int.errors.iterator();
|
var err_it = int.errors.iterator();
|
||||||
|
|
||||||
while (err_it.next()) |err| {
|
while (err_it.next()) |err| {
|
||||||
try diagnostics.append(allocator, .{
|
diagnostics.appendAssumeCapacity(.{
|
||||||
.range = offsets.nodeToRange(tree, err.key_ptr.*, server.offset_encoding),
|
.range = offsets.nodeToRange(tree, err.key_ptr.*, server.offset_encoding),
|
||||||
.severity = .Error,
|
.severity = .Error,
|
||||||
.code = .{ .string = err.value_ptr.code },
|
.code = .{ .string = err.value_ptr.code },
|
||||||
@ -597,30 +598,10 @@ fn typeToCompletion(
|
|||||||
),
|
),
|
||||||
.primitive, .array_index => {},
|
.primitive, .array_index => {},
|
||||||
.@"comptime" => |co| {
|
.@"comptime" => |co| {
|
||||||
const ti = co.type.getTypeInfo();
|
if (type_handle.type.is_type_val) {
|
||||||
switch (ti) {
|
try analyser.completions.dotCompletions(allocator, list, &co.interpreter.ip, co.value.ty, co.value.val, co.value.node_idx);
|
||||||
.@"struct" => |st| {
|
} else {
|
||||||
var fit = st.fields.iterator();
|
try analyser.completions.dotCompletions(allocator, list, &co.interpreter.ip, co.value.val, .none, co.value.node_idx);
|
||||||
while (fit.next()) |entry| {
|
|
||||||
try list.append(allocator, .{
|
|
||||||
.label = entry.key_ptr.*,
|
|
||||||
.kind = .Field,
|
|
||||||
.insertText = entry.key_ptr.*,
|
|
||||||
.insertTextFormat = .PlainText,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
var it = st.scope.declarations.iterator();
|
|
||||||
while (it.next()) |entry| {
|
|
||||||
try list.append(allocator, .{
|
|
||||||
.label = entry.key_ptr.*,
|
|
||||||
.kind = if (entry.value_ptr.isConstant()) .Constant else .Variable,
|
|
||||||
.insertText = entry.key_ptr.*,
|
|
||||||
.insertTextFormat = .PlainText,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
else => {},
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -939,7 +920,7 @@ fn hoverSymbol(server: *Server, decl_handle: analysis.DeclWithHandle) error{OutO
|
|||||||
|
|
||||||
const resolved_type_str = if (resolved_type) |rt|
|
const resolved_type_str = if (resolved_type) |rt|
|
||||||
if (rt.type.is_type_val) switch (rt.type.data) {
|
if (rt.type.is_type_val) switch (rt.type.data) {
|
||||||
.@"comptime" => |*co| try std.fmt.allocPrint(server.arena.allocator(), "{ }", .{co.interpreter.formatTypeInfo(co.type.getTypeInfo())}),
|
.@"comptime" => |co| try std.fmt.allocPrint(server.arena.allocator(), "{}", .{co.value.ty.fmtType(co.interpreter.ip)}),
|
||||||
else => "type",
|
else => "type",
|
||||||
} else switch (rt.type.data) { // TODO: Investigate random weird numbers like 897 that cause index of bounds
|
} else switch (rt.type.data) { // TODO: Investigate random weird numbers like 897 that cause index of bounds
|
||||||
.pointer,
|
.pointer,
|
||||||
|
3601
src/analyser/InternPool.zig
Normal file
3601
src/analyser/InternPool.zig
Normal file
File diff suppressed because it is too large
Load Diff
8
src/analyser/analyser.zig
Normal file
8
src/analyser/analyser.zig
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
pub const completions = @import("completions.zig");
|
||||||
|
pub const InternPool = @import("InternPool.zig");
|
||||||
|
pub const encoding = @import("encoding.zig");
|
||||||
|
|
||||||
|
comptime {
|
||||||
|
const std = @import("std");
|
||||||
|
std.testing.refAllDecls(@This());
|
||||||
|
}
|
209
src/analyser/completions.zig
Normal file
209
src/analyser/completions.zig
Normal file
@ -0,0 +1,209 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const InternPool = @import("InternPool.zig");
|
||||||
|
const types = @import("../lsp.zig");
|
||||||
|
|
||||||
|
const Ast = std.zig.Ast;
|
||||||
|
|
||||||
|
pub fn dotCompletions(
|
||||||
|
arena: std.mem.Allocator,
|
||||||
|
completions: *std.ArrayListUnmanaged(types.CompletionItem),
|
||||||
|
ip: *InternPool,
|
||||||
|
ty: InternPool.Index,
|
||||||
|
val: InternPool.Index,
|
||||||
|
node: ?Ast.Node.Index,
|
||||||
|
) error{OutOfMemory}!void {
|
||||||
|
_ = node;
|
||||||
|
|
||||||
|
const key = ip.indexToKey(ty);
|
||||||
|
const inner_key = switch (key) {
|
||||||
|
.pointer_type => |info| if (info.size == .One) ip.indexToKey(info.elem_type) else key,
|
||||||
|
else => key,
|
||||||
|
};
|
||||||
|
|
||||||
|
switch (inner_key) {
|
||||||
|
.simple_type => |simple| switch (simple) {
|
||||||
|
.type => {
|
||||||
|
const ty_key = ip.indexToKey(val);
|
||||||
|
const namespace = ty_key.getNamespace(ip.*);
|
||||||
|
if (namespace != .none) {
|
||||||
|
// TODO lookup in namespace
|
||||||
|
}
|
||||||
|
switch (ty_key) {
|
||||||
|
.error_set_type => |error_set_info| {
|
||||||
|
for (error_set_info.names) |name| {
|
||||||
|
const error_name = ip.indexToKey(name).bytes;
|
||||||
|
try completions.append(arena, .{
|
||||||
|
.label = error_name,
|
||||||
|
.kind = .Constant,
|
||||||
|
.detail = try std.fmt.allocPrint(arena, "error.{s}", .{std.zig.fmtId(error_name)}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.union_type => {}, // TODO
|
||||||
|
.enum_type => |enum_index| {
|
||||||
|
const enum_info = ip.getEnum(enum_index);
|
||||||
|
var field_it = enum_info.fields.iterator();
|
||||||
|
while (field_it.next()) |entry| {
|
||||||
|
try completions.append(arena, .{
|
||||||
|
.label = entry.key_ptr.*,
|
||||||
|
.kind = .Constant,
|
||||||
|
// include field.val?
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
else => {},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
else => {},
|
||||||
|
},
|
||||||
|
.pointer_type => |pointer_info| {
|
||||||
|
if (pointer_info.size == .Slice) {
|
||||||
|
var many_ptr_info = InternPool.Key{ .pointer_type = pointer_info };
|
||||||
|
many_ptr_info.pointer_type.size = .Many;
|
||||||
|
|
||||||
|
try completions.append(arena, .{
|
||||||
|
.label = "ptr",
|
||||||
|
.kind = .Field,
|
||||||
|
.detail = try std.fmt.allocPrint(arena, "ptr: {}", .{many_ptr_info.fmtType(ip.*)}),
|
||||||
|
});
|
||||||
|
try completions.append(arena, .{
|
||||||
|
.label = "len",
|
||||||
|
.kind = .Field,
|
||||||
|
.detail = "len: usize",
|
||||||
|
});
|
||||||
|
} else if (ip.indexToKey(pointer_info.elem_type) == .array_type) {
|
||||||
|
try completions.append(arena, .{
|
||||||
|
.label = "len",
|
||||||
|
.kind = .Field,
|
||||||
|
.detail = "len: usize",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.array_type => |array_info| {
|
||||||
|
try completions.append(arena, types.CompletionItem{
|
||||||
|
.label = "len",
|
||||||
|
.kind = .Field,
|
||||||
|
.detail = try std.fmt.allocPrint(arena, "const len: usize ({d})", .{array_info.len}), // TODO how should this be displayed
|
||||||
|
});
|
||||||
|
},
|
||||||
|
.struct_type => |struct_index| {
|
||||||
|
const struct_info = ip.getStruct(struct_index);
|
||||||
|
try completions.ensureUnusedCapacity(arena, struct_info.fields.count());
|
||||||
|
var field_it = struct_info.fields.iterator();
|
||||||
|
while (field_it.next()) |entry| {
|
||||||
|
const label = entry.key_ptr.*;
|
||||||
|
const field = entry.value_ptr.*;
|
||||||
|
completions.appendAssumeCapacity(types.CompletionItem{
|
||||||
|
.label = label,
|
||||||
|
.kind = .Field,
|
||||||
|
.detail = try std.fmt.allocPrint(arena, "{s}: {}", .{
|
||||||
|
label,
|
||||||
|
fmtFieldDetail(field, ip),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.optional_type => |optional_info| {
|
||||||
|
try completions.append(arena, .{
|
||||||
|
.label = "?",
|
||||||
|
.kind = .Operator,
|
||||||
|
.detail = try std.fmt.allocPrint(arena, "{}", .{optional_info.payload_type.fmtType(ip.*)}),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
.enum_type => |enum_index| {
|
||||||
|
const enum_info = ip.getEnum(enum_index);
|
||||||
|
for (enum_info.fields.keys()) |field_name, i| {
|
||||||
|
const field_val = enum_info.values.keys()[i];
|
||||||
|
try completions.append(arena, .{
|
||||||
|
.label = field_name,
|
||||||
|
.kind = .Field,
|
||||||
|
.detail = try std.fmt.allocPrint(arena, "{}", .{field_val.fmtValue(enum_info.tag_type, ip.*)}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.union_type => |union_index| {
|
||||||
|
const union_info = ip.getUnion(union_index);
|
||||||
|
var field_it = union_info.fields.iterator();
|
||||||
|
while (field_it.next()) |entry| {
|
||||||
|
const label = entry.key_ptr.*;
|
||||||
|
const field = entry.value_ptr.*;
|
||||||
|
try completions.append(arena, .{
|
||||||
|
.label = label,
|
||||||
|
.kind = .Field,
|
||||||
|
.detail = if (field.alignment != 0)
|
||||||
|
try std.fmt.allocPrint(arena, "{s}: align({d}) {}", .{ label, field.alignment, field.ty.fmtType(ip.*) })
|
||||||
|
else
|
||||||
|
try std.fmt.allocPrint(arena, "{s}: {}", .{ label, field.ty.fmtType(ip.*) }),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.tuple_type => |tuple_info| {
|
||||||
|
for (tuple_info.types) |tuple_ty, i| {
|
||||||
|
try completions.append(arena, .{
|
||||||
|
.label = try std.fmt.allocPrint(arena, "{d}", .{i}),
|
||||||
|
.kind = .Field,
|
||||||
|
.detail = try std.fmt.allocPrint(arena, "{d}: {}", .{ i, tuple_ty.fmtType(ip.*) }),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.int_type,
|
||||||
|
.error_set_type,
|
||||||
|
.error_union_type,
|
||||||
|
.function_type,
|
||||||
|
.vector_type,
|
||||||
|
.anyframe_type,
|
||||||
|
=> {},
|
||||||
|
|
||||||
|
.simple_value,
|
||||||
|
.int_u64_value,
|
||||||
|
.int_i64_value,
|
||||||
|
.int_big_value,
|
||||||
|
.float_16_value,
|
||||||
|
.float_32_value,
|
||||||
|
.float_64_value,
|
||||||
|
.float_80_value,
|
||||||
|
.float_128_value,
|
||||||
|
=> unreachable,
|
||||||
|
|
||||||
|
.bytes,
|
||||||
|
.aggregate,
|
||||||
|
.union_value,
|
||||||
|
=> unreachable,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn FormatContext(comptime T: type) type {
|
||||||
|
return struct {
|
||||||
|
ip: *InternPool,
|
||||||
|
item: T,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn formatFieldDetail(
|
||||||
|
ctx: FormatContext(InternPool.Struct.Field),
|
||||||
|
comptime fmt: []const u8,
|
||||||
|
options: std.fmt.FormatOptions,
|
||||||
|
writer: anytype,
|
||||||
|
) @TypeOf(writer).Error!void {
|
||||||
|
_ = options;
|
||||||
|
if (fmt.len != 0) std.fmt.invalidFmtError(fmt, InternPool.Struct.Field);
|
||||||
|
|
||||||
|
const field = ctx.item;
|
||||||
|
if (field.is_comptime) {
|
||||||
|
try writer.writeAll("comptime ");
|
||||||
|
}
|
||||||
|
if (field.alignment != 0) {
|
||||||
|
try writer.print("align({d}) ", .{field.alignment});
|
||||||
|
}
|
||||||
|
try writer.print("{}", .{field.ty.fmtType(ctx.ip.*)});
|
||||||
|
if (field.default_value != .none) {
|
||||||
|
try writer.print(" = {},", .{field.default_value.fmtValue(field.ty, ctx.ip.*)});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fmtFieldDetail(field: InternPool.Struct.Field, ip: *InternPool) std.fmt.Formatter(formatFieldDetail) {
|
||||||
|
return .{ .data = .{
|
||||||
|
.ip = ip,
|
||||||
|
.item = field,
|
||||||
|
} };
|
||||||
|
}
|
210
src/analyser/encoding.zig
Normal file
210
src/analyser/encoding.zig
Normal file
@ -0,0 +1,210 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const builtin = @import("builtin");
|
||||||
|
const Allocator = std.mem.Allocator;
|
||||||
|
const assert = std.debug.assert;
|
||||||
|
|
||||||
|
const Index = usize;
|
||||||
|
|
||||||
|
pub fn encode(extra: *std.ArrayList(u8), comptime T: type, data: anytype) Allocator.Error!void {
|
||||||
|
switch (@typeInfo(T)) {
|
||||||
|
.Type,
|
||||||
|
.NoReturn,
|
||||||
|
.ComptimeFloat,
|
||||||
|
.ComptimeInt,
|
||||||
|
.Undefined,
|
||||||
|
.Null,
|
||||||
|
.ErrorUnion,
|
||||||
|
.ErrorSet,
|
||||||
|
.Fn,
|
||||||
|
.Opaque,
|
||||||
|
.Frame,
|
||||||
|
.AnyFrame,
|
||||||
|
.EnumLiteral,
|
||||||
|
=> @compileError("Unable to encode type " ++ @typeName(T)),
|
||||||
|
|
||||||
|
.Void => {},
|
||||||
|
.Bool => try encode(extra, u1, @boolToInt(data)),
|
||||||
|
.Int => try extra.appendSlice(std.mem.asBytes(&data)),
|
||||||
|
.Float => |info| switch (info.bits) {
|
||||||
|
16 => try encode(extra, u16, @bitCast(u16, data)),
|
||||||
|
32 => try encode(extra, u32, @bitCast(u32, data)),
|
||||||
|
64 => try encode(extra, u64, @bitCast(u64, data)),
|
||||||
|
80 => try encode(extra, u80, @bitCast(u80, data)),
|
||||||
|
128 => try encode(extra, u128, @bitCast(u128, data)),
|
||||||
|
else => @compileError("Unable to encode type " ++ @typeName(T)),
|
||||||
|
},
|
||||||
|
.Pointer => |info| {
|
||||||
|
switch (info.size) {
|
||||||
|
.One => {
|
||||||
|
if (comptime canEncodeAsBytes(info.child)) {
|
||||||
|
try extra.appendNTimes(undefined, std.mem.alignPointerOffset(extra.items.ptr + extra.items.len, info.alignment).?);
|
||||||
|
try encode(extra, info.child, data.*);
|
||||||
|
} else {
|
||||||
|
@compileError("Encoding " ++ @typeName(T) ++ " would require allocation");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.Slice => {
|
||||||
|
if (comptime canEncodeAsBytes(info.child)) {
|
||||||
|
try encode(extra, u32, @intCast(u32, data.len));
|
||||||
|
try extra.appendNTimes(undefined, std.mem.alignPointerOffset(extra.items.ptr + extra.items.len, info.alignment).?);
|
||||||
|
try extra.appendSlice(std.mem.sliceAsBytes(data));
|
||||||
|
} else {
|
||||||
|
@compileError("Encoding " ++ @typeName(T) ++ " would require allocation");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
.Many,
|
||||||
|
.C,
|
||||||
|
=> @compileError("Unable to encode type " ++ @typeName(T)),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.Array => |info| {
|
||||||
|
for (data) |item| {
|
||||||
|
try encode(extra, info.child, item);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.Struct => |info| {
|
||||||
|
switch (info.layout) {
|
||||||
|
.Packed,
|
||||||
|
.Extern,
|
||||||
|
=> return try extra.appendSlice(std.mem.asBytes(&data)),
|
||||||
|
.Auto => {
|
||||||
|
inline for (info.fields) |field| {
|
||||||
|
try encode(extra, field.type, @field(data, field.name));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.Optional => {
|
||||||
|
try encode(extra, bool, data == null);
|
||||||
|
if (data) |item| {
|
||||||
|
try encode(extra, item);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.Enum => |info| try encode(extra, info.tag_type, @enumToInt(data)),
|
||||||
|
.Union => @compileError("TODO"),
|
||||||
|
.Vector => |info| {
|
||||||
|
const array: [info.len]info.child = data;
|
||||||
|
try encode(extra, array);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn decode(extra: *[]const u8, comptime T: type) T {
|
||||||
|
return switch (@typeInfo(T)) {
|
||||||
|
.Type,
|
||||||
|
.NoReturn,
|
||||||
|
.ComptimeFloat,
|
||||||
|
.ComptimeInt,
|
||||||
|
.Undefined,
|
||||||
|
.Null,
|
||||||
|
.ErrorUnion,
|
||||||
|
.ErrorSet,
|
||||||
|
.Fn,
|
||||||
|
.Opaque,
|
||||||
|
.Frame,
|
||||||
|
.AnyFrame,
|
||||||
|
.EnumLiteral,
|
||||||
|
=> @compileError("Unable to decode type " ++ @typeName(T)),
|
||||||
|
|
||||||
|
.Void => {},
|
||||||
|
.Bool => decode(extra, u1) == 1,
|
||||||
|
.Int => std.mem.bytesToValue(T, readArray(extra, @sizeOf(T))),
|
||||||
|
.Float => |info| switch (info.bits) {
|
||||||
|
16 => @bitCast(T, decode(extra, u16)),
|
||||||
|
32 => @bitCast(T, decode(extra, u32)),
|
||||||
|
64 => @bitCast(T, decode(extra, u64)),
|
||||||
|
80 => @bitCast(T, decode(extra, u80)),
|
||||||
|
128 => @bitCast(T, decode(extra, u128)),
|
||||||
|
else => @compileError("Unable to decode type " ++ @typeName(T)),
|
||||||
|
},
|
||||||
|
.Pointer => |info| {
|
||||||
|
switch (info.size) {
|
||||||
|
.One => {
|
||||||
|
if (comptime canEncodeAsBytes(info.child)) {
|
||||||
|
extra.* = alignForward(extra.*, info.alignment);
|
||||||
|
return std.mem.bytesAsValue(T, readArray(extra, @sizeOf(info.child)));
|
||||||
|
} else {
|
||||||
|
@compileError("Decoding " ++ @typeName(T) ++ " would require allocation");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.Slice => {
|
||||||
|
if (comptime canEncodeAsBytes(info.child)) {
|
||||||
|
const len = decode(extra, u32);
|
||||||
|
extra.* = alignForward(extra.*, info.alignment);
|
||||||
|
const bytes = readBytes(extra, len * @sizeOf(info.child));
|
||||||
|
return std.mem.bytesAsSlice(info.child, @alignCast(info.alignment, bytes));
|
||||||
|
} else {
|
||||||
|
@compileError("Decoding " ++ @typeName(T) ++ " would require allocation");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
.Many,
|
||||||
|
.C,
|
||||||
|
=> @compileError("Unable to decode type " ++ @typeName(T)),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.Array => |info| blk: {
|
||||||
|
var array: T = undefined;
|
||||||
|
var i: usize = 0;
|
||||||
|
while (i < info.len) {
|
||||||
|
array[i] = decode(extra, info.child);
|
||||||
|
}
|
||||||
|
break :blk array;
|
||||||
|
},
|
||||||
|
.Struct => |info| {
|
||||||
|
switch (info.layout) {
|
||||||
|
.Packed,
|
||||||
|
.Extern,
|
||||||
|
=> return std.mem.bytesToValue(T, readArray(extra, @sizeOf(T))),
|
||||||
|
.Auto => {
|
||||||
|
var result: T = undefined;
|
||||||
|
inline for (info.fields) |field| {
|
||||||
|
@field(result, field.name) = decode(extra, field.type);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.Optional => |info| blk: {
|
||||||
|
const is_null = decode(extra, bool);
|
||||||
|
if (is_null) {
|
||||||
|
break :blk null;
|
||||||
|
} else {
|
||||||
|
break :blk decode(extra, info.child);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.Enum => |info| @intToEnum(T, decode(extra, info.tag_type)),
|
||||||
|
.Union => @compileError("TODO"),
|
||||||
|
.Vector => |info| decode(extra, [info.len]info.child),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn canEncodeAsBytes(comptime T: type) bool {
|
||||||
|
return switch (@typeInfo(T)) {
|
||||||
|
.Void, .Bool, .Int, .Float, .Enum, .Vector => true,
|
||||||
|
.Array => |info| canEncodeAsBytes(info.child),
|
||||||
|
.Struct => |info| info.layout != .Auto,
|
||||||
|
.Union => |info| info.layout != .Auto,
|
||||||
|
else => false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// forward aligns `extra` until it has the given alignment
|
||||||
|
pub fn alignForward(extra: []const u8, alignment: usize) []const u8 {
|
||||||
|
const unaligned = @ptrToInt(extra.ptr);
|
||||||
|
const offset = std.mem.alignForward(unaligned, alignment) - unaligned;
|
||||||
|
const result = extra[offset..];
|
||||||
|
std.debug.assert(std.mem.isAligned(@ptrToInt(result.ptr), alignment));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn readBytes(extra: *[]const u8, n: usize) []const u8 {
|
||||||
|
defer extra.* = extra.*[n..];
|
||||||
|
return extra.*[0..n];
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn readArray(extra: *[]const u8, comptime n: usize) *const [n]u8 {
|
||||||
|
defer extra.* = extra.*[n..];
|
||||||
|
return extra.*[0..n];
|
||||||
|
}
|
@ -7,6 +7,7 @@ const URI = @import("uri.zig");
|
|||||||
const log = std.log.scoped(.analysis);
|
const log = std.log.scoped(.analysis);
|
||||||
const ast = @import("ast.zig");
|
const ast = @import("ast.zig");
|
||||||
const ComptimeInterpreter = @import("ComptimeInterpreter.zig");
|
const ComptimeInterpreter = @import("ComptimeInterpreter.zig");
|
||||||
|
const InternPool = ComptimeInterpreter.InternPool;
|
||||||
|
|
||||||
var using_trail: std.ArrayList([*]const u8) = undefined;
|
var using_trail: std.ArrayList([*]const u8) = undefined;
|
||||||
var resolve_trail: std.ArrayList(NodeWithHandle) = undefined;
|
var resolve_trail: std.ArrayList(NodeWithHandle) = undefined;
|
||||||
@ -762,41 +763,39 @@ pub fn resolveTypeOfNodeInternal(store: *DocumentStore, node_handle: NodeWithHan
|
|||||||
|
|
||||||
log.info("Invoking interpreter!", .{});
|
log.info("Invoking interpreter!", .{});
|
||||||
|
|
||||||
store.ensureInterpreterExists(handle.uri) catch |err| {
|
const interpreter = store.ensureInterpreterExists(handle.uri) catch |err| {
|
||||||
log.err("Interpreter error: {s}", .{@errorName(err)});
|
log.err("Failed to interpret file: {s}", .{@errorName(err)});
|
||||||
if (@errorReturnTrace()) |trace| {
|
if (@errorReturnTrace()) |trace| {
|
||||||
std.debug.dumpStackTrace(trace.*);
|
std.debug.dumpStackTrace(trace.*);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
var interpreter = handle.interpreter.?;
|
|
||||||
|
const root_namespace = @intToEnum(ComptimeInterpreter.Namespace.Index, 0);
|
||||||
|
|
||||||
// TODO: Start from current/nearest-current scope
|
// TODO: Start from current/nearest-current scope
|
||||||
const result = interpreter.interpret(node, interpreter.root_type.?.getTypeInfo().getScopeOfType().?, .{}) catch |err| {
|
const result = interpreter.interpret(node, root_namespace, .{}) catch |err| {
|
||||||
log.err("Interpreter error: {s}", .{@errorName(err)});
|
log.err("Failed to interpret node: {s}", .{@errorName(err)});
|
||||||
if (@errorReturnTrace()) |trace| {
|
if (@errorReturnTrace()) |trace| {
|
||||||
std.debug.dumpStackTrace(trace.*);
|
std.debug.dumpStackTrace(trace.*);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
const val = result.getValue() catch |err| {
|
const value = result.getValue() catch |err| {
|
||||||
log.err("Interpreter error: {s}", .{@errorName(err)});
|
log.err("interpreter return no result: {s}", .{@errorName(err)});
|
||||||
if (@errorReturnTrace()) |trace| {
|
if (@errorReturnTrace()) |trace| {
|
||||||
std.debug.dumpStackTrace(trace.*);
|
std.debug.dumpStackTrace(trace.*);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const ti = val.type.getTypeInfo();
|
|
||||||
if (ti != .type) {
|
|
||||||
log.err("Not a type: { }", .{interpreter.formatTypeInfo(ti)});
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return TypeWithHandle{
|
return TypeWithHandle{
|
||||||
.type = .{
|
.type = .{
|
||||||
.data = .{ .@"comptime" = .{ .interpreter = interpreter, .type = val.value_data.type } },
|
.data = .{ .@"comptime" = .{
|
||||||
.is_type_val = true,
|
.interpreter = interpreter,
|
||||||
|
.value = value,
|
||||||
|
} },
|
||||||
|
.is_type_val = value.ty == InternPool.Index.type_type,
|
||||||
},
|
},
|
||||||
.handle = node_handle.handle,
|
.handle = node_handle.handle,
|
||||||
};
|
};
|
||||||
@ -1050,7 +1049,7 @@ pub const Type = struct {
|
|||||||
array_index,
|
array_index,
|
||||||
@"comptime": struct {
|
@"comptime": struct {
|
||||||
interpreter: *ComptimeInterpreter,
|
interpreter: *ComptimeInterpreter,
|
||||||
type: ComptimeInterpreter.Type,
|
value: ComptimeInterpreter.Value,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
/// If true, the type `type`, the attached data is the value of the type value.
|
/// If true, the type `type`, the attached data is the value of the type value.
|
||||||
|
@ -14,3 +14,9 @@ pub const URI = @import("uri.zig");
|
|||||||
pub const DocumentStore = @import("DocumentStore.zig");
|
pub const DocumentStore = @import("DocumentStore.zig");
|
||||||
pub const ComptimeInterpreter = @import("ComptimeInterpreter.zig");
|
pub const ComptimeInterpreter = @import("ComptimeInterpreter.zig");
|
||||||
pub const diff = @import("diff.zig");
|
pub const diff = @import("diff.zig");
|
||||||
|
pub const analyser = @import("analyser/analyser.zig");
|
||||||
|
|
||||||
|
comptime {
|
||||||
|
const std = @import("std");
|
||||||
|
std.testing.refAllDecls(@This());
|
||||||
|
}
|
||||||
|
@ -1,96 +1,482 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const zls = @import("zls");
|
const zls = @import("zls");
|
||||||
|
const builtin = @import("builtin");
|
||||||
|
|
||||||
const Ast = std.zig.Ast;
|
const Ast = std.zig.Ast;
|
||||||
|
|
||||||
const ComptimeInterpreter = zls.ComptimeInterpreter;
|
const ComptimeInterpreter = zls.ComptimeInterpreter;
|
||||||
|
const InternPool = zls.analyser.InternPool;
|
||||||
|
const Index = InternPool.Index;
|
||||||
|
const Key = InternPool.Key;
|
||||||
|
const ast = zls.ast;
|
||||||
|
const offsets = zls.offsets;
|
||||||
|
|
||||||
const allocator: std.mem.Allocator = std.testing.allocator;
|
const allocator: std.mem.Allocator = std.testing.allocator;
|
||||||
|
|
||||||
test "ComptimeInterpreter - basic test" {
|
test "ComptimeInterpreter - primitive types" {
|
||||||
var config = zls.Config{};
|
try testExpr("true", .{ .simple_type = .bool }, .{ .simple_value = .bool_true });
|
||||||
var doc_store = zls.DocumentStore{
|
try testExpr("false", .{ .simple_type = .bool }, .{ .simple_value = .bool_false });
|
||||||
.allocator = allocator,
|
try testExpr("5", .{ .simple_type = .comptime_int }, .{ .int_u64_value = 5 });
|
||||||
.config = &config,
|
// TODO try testExpr("-2", .{ .simple_type = .comptime_int }, .{ .int_i64_value = -2 });
|
||||||
};
|
try testExpr("3.0", .{ .simple_type = .comptime_float }, null);
|
||||||
defer doc_store.deinit();
|
|
||||||
|
|
||||||
_ = try doc_store.openDocument("file:///file.zig",
|
try testExpr("null", .{ .simple_type = .null_type }, .{ .simple_value = .null_value });
|
||||||
\\pub fn ReturnMyType(comptime my_arg: bool) type {
|
try testExpr("void", .{ .simple_type = .type }, .{ .simple_type = .void });
|
||||||
\\ var abc = z: {break :z if (!my_arg) 123 else 0;};
|
try testExpr("undefined", .{ .simple_type = .undefined_type }, .{ .simple_value = .undefined_value });
|
||||||
\\ if (abc == 123) return u69;
|
try testExpr("noreturn", .{ .simple_type = .type }, .{ .simple_type = .noreturn });
|
||||||
\\ return u8;
|
|
||||||
\\}
|
|
||||||
);
|
|
||||||
|
|
||||||
var interpreter = ComptimeInterpreter{
|
|
||||||
.allocator = allocator,
|
|
||||||
.document_store = &doc_store,
|
|
||||||
.uri = "file:///file.zig",
|
|
||||||
};
|
|
||||||
defer interpreter.deinit();
|
|
||||||
|
|
||||||
_ = try interpreter.interpret(0, null, .{});
|
|
||||||
|
|
||||||
var bool_type = try interpreter.createType(std.math.maxInt(std.zig.Ast.Node.Index), .{ .bool = {} });
|
|
||||||
var arg_false = ComptimeInterpreter.Value{
|
|
||||||
.interpreter = &interpreter,
|
|
||||||
.node_idx = std.math.maxInt(std.zig.Ast.Node.Index),
|
|
||||||
.type = bool_type,
|
|
||||||
.value_data = try interpreter.createValueData(.{ .bool = false }),
|
|
||||||
};
|
|
||||||
var arg_true = ComptimeInterpreter.Value{
|
|
||||||
.interpreter = &interpreter,
|
|
||||||
.node_idx = std.math.maxInt(std.zig.Ast.Node.Index),
|
|
||||||
.type = bool_type,
|
|
||||||
.value_data = try interpreter.createValueData(.{ .bool = true }),
|
|
||||||
};
|
|
||||||
|
|
||||||
const rmt = interpreter.root_type.?.getTypeInfo().@"struct".scope.declarations.get("ReturnMyType").?;
|
|
||||||
|
|
||||||
const call_with_false = try interpreter.call(null, rmt.node_idx, &.{
|
|
||||||
arg_false,
|
|
||||||
}, .{});
|
|
||||||
defer call_with_false.scope.deinit();
|
|
||||||
const call_with_true = try interpreter.call(null, rmt.node_idx, &.{
|
|
||||||
arg_true,
|
|
||||||
}, .{});
|
|
||||||
defer call_with_true.scope.deinit();
|
|
||||||
|
|
||||||
try std.testing.expectFmt("u69", "{any}", .{interpreter.formatTypeInfo(call_with_false.result.value.value_data.type.getTypeInfo())});
|
|
||||||
try std.testing.expectFmt("u8", "{any}", .{interpreter.formatTypeInfo(call_with_true.result.value.value_data.type.getTypeInfo())});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
test "ComptimeInterpreter - struct" {
|
test "ComptimeInterpreter - expressions" {
|
||||||
var config = zls.Config{};
|
if (true) return error.SkipZigTest; // TODO
|
||||||
var doc_store = zls.DocumentStore{
|
try testExpr("5 + 3", .{ .simple_type = .comptime_int }, .{ .int_u64_value = 8 });
|
||||||
.allocator = allocator,
|
try testExpr("5.2 + 4.2", .{ .simple_type = .comptime_float }, null);
|
||||||
.config = &config,
|
|
||||||
};
|
|
||||||
defer doc_store.deinit();
|
|
||||||
|
|
||||||
_ = try doc_store.openDocument("file:///file.zig",
|
try testExpr("3 == 3", .{ .simple_type = .bool }, .{ .simple_valueclear = .bool_true });
|
||||||
\\pub fn ReturnMyType() type {
|
try testExpr("5.2 == 2.1", .{ .simple_type = .bool }, .{ .simple_value = .bool_false });
|
||||||
|
|
||||||
|
try testExpr("@as(?bool, null) orelse true", .{ .simple_type = .bool }, .{ .simple_value = .bool_true });
|
||||||
|
}
|
||||||
|
|
||||||
|
test "ComptimeInterpreter - builtins" {
|
||||||
|
if (true) return error.SkipZigTest; // TODO
|
||||||
|
try testExpr("@as(bool, true)", .{ .simple_type = .bool }, .{ .simple_value = .bool_true });
|
||||||
|
try testExpr("@as(u32, 3)", .{ .int_type = .{
|
||||||
|
.signedness = .unsigned,
|
||||||
|
.bits = 32,
|
||||||
|
} }, .{ .int_u64_value = 3 });
|
||||||
|
}
|
||||||
|
|
||||||
|
test "ComptimeInterpreter - string literal" {
|
||||||
|
var context = try Context.init(
|
||||||
|
\\const foobarbaz = "hello world!";
|
||||||
|
\\
|
||||||
|
);
|
||||||
|
defer context.deinit();
|
||||||
|
const result = try context.interpret(context.findVar("foobarbaz"));
|
||||||
|
|
||||||
|
try std.testing.expect(result.ty == .pointer_type);
|
||||||
|
|
||||||
|
try std.testing.expectEqualStrings("hello world!", result.val.?.bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "ComptimeInterpreter - labeled block" {
|
||||||
|
try testExpr(
|
||||||
|
\\blk: {
|
||||||
|
\\ break :blk true;
|
||||||
|
\\}
|
||||||
|
, .{ .simple_type = .bool }, .{ .simple_value = .bool_true });
|
||||||
|
try testExpr(
|
||||||
|
\\blk: {
|
||||||
|
\\ break :blk 3;
|
||||||
|
\\}
|
||||||
|
, .{ .simple_type = .comptime_int }, .{ .int_u64_value = 3 });
|
||||||
|
}
|
||||||
|
|
||||||
|
test "ComptimeInterpreter - if" {
|
||||||
|
try testExpr(
|
||||||
|
\\blk: {
|
||||||
|
\\ break :blk if (true) true else false;
|
||||||
|
\\}
|
||||||
|
, .{ .simple_type = .bool }, .{ .simple_value = .bool_true });
|
||||||
|
try testExpr(
|
||||||
|
\\blk: {
|
||||||
|
\\ break :blk if (false) true else false;
|
||||||
|
\\}
|
||||||
|
, .{ .simple_type = .bool }, .{ .simple_value = .bool_false });
|
||||||
|
try testExpr(
|
||||||
|
\\blk: {
|
||||||
|
\\ if (false) break :blk true;
|
||||||
|
\\ break :blk false;
|
||||||
|
\\}
|
||||||
|
, .{ .simple_type = .bool }, .{ .simple_value = .bool_false });
|
||||||
|
// TODO
|
||||||
|
// try testExpr(
|
||||||
|
// \\outer: {
|
||||||
|
// \\ if (:inner {
|
||||||
|
// \\ break :inner true;
|
||||||
|
// \\ }) break :outer true;
|
||||||
|
// \\ break :outer false;
|
||||||
|
// \\}
|
||||||
|
// , .{ .simple_type = .bool }, .{ .simple_value = .bool_true });
|
||||||
|
}
|
||||||
|
|
||||||
|
test "ComptimeInterpreter - variable lookup" {
|
||||||
|
try testExpr(
|
||||||
|
\\blk: {
|
||||||
|
\\ var foo = 42;
|
||||||
|
\\ break :blk foo;
|
||||||
|
\\}
|
||||||
|
, .{ .simple_type = .comptime_int }, .{ .int_u64_value = 42 });
|
||||||
|
try testExpr(
|
||||||
|
\\blk: {
|
||||||
|
\\ var foo = 1;
|
||||||
|
\\ var bar = 2;
|
||||||
|
\\ var baz = 3;
|
||||||
|
\\ break :blk bar;
|
||||||
|
\\}
|
||||||
|
, .{ .simple_type = .comptime_int }, .{ .int_u64_value = 2 });
|
||||||
|
|
||||||
|
var context = try Context.init(
|
||||||
|
\\const bar = foo;
|
||||||
|
\\const foo = 3;
|
||||||
|
);
|
||||||
|
defer context.deinit();
|
||||||
|
|
||||||
|
const result = try context.interpret(context.findVar("bar"));
|
||||||
|
try expectEqualKey(context.interpreter.ip, .{ .simple_type = .comptime_int }, result.ty);
|
||||||
|
try expectEqualKey(context.interpreter.ip, .{ .int_u64_value = 3 }, result.val);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "ComptimeInterpreter - field access" {
|
||||||
|
try testExpr(
|
||||||
|
\\blk: {
|
||||||
|
\\ const foo: struct {alpha: u64, beta: bool} = undefined;
|
||||||
|
\\ break :blk foo.beta;
|
||||||
|
\\}
|
||||||
|
, .{ .simple_type = .bool }, null);
|
||||||
|
try testExpr(
|
||||||
|
\\blk: {
|
||||||
|
\\ const foo: struct {alpha: u64, beta: bool} = undefined;
|
||||||
|
\\ break :blk foo.alpha;
|
||||||
|
\\}
|
||||||
|
, .{ .int_type = .{
|
||||||
|
.signedness = .unsigned,
|
||||||
|
.bits = 64,
|
||||||
|
} }, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "ComptimeInterpreter - optional operations" {
|
||||||
|
if (true) return error.SkipZigTest; // TODO
|
||||||
|
try testExpr(
|
||||||
|
\\blk: {
|
||||||
|
\\ const foo: ?bool = true;
|
||||||
|
\\ break :blk foo.?;
|
||||||
|
\\}
|
||||||
|
, .{ .simple_type = .bool }, .{ .simple_value = .bool_true });
|
||||||
|
try testExpr(
|
||||||
|
\\blk: {
|
||||||
|
\\ const foo: ?bool = true;
|
||||||
|
\\ break :blk foo == null;
|
||||||
|
\\}
|
||||||
|
, .{ .simple_type = .bool }, .{ .simple_value = .bool_false });
|
||||||
|
}
|
||||||
|
|
||||||
|
test "ComptimeInterpreter - pointer operations" {
|
||||||
|
if (true) return error.SkipZigTest; // TODO
|
||||||
|
try testExpr(
|
||||||
|
\\blk: {
|
||||||
|
\\ const foo: []const u8 = "";
|
||||||
|
\\ break :blk foo.len;
|
||||||
|
\\}
|
||||||
|
, .{ .simple_type = .usize }, .{ .bytes = "" });
|
||||||
|
try testExpr(
|
||||||
|
\\blk: {
|
||||||
|
\\ const foo = true;
|
||||||
|
\\ break :blk &foo;
|
||||||
|
\\}
|
||||||
|
, @panic("TODO"), .{ .simple_value = .bool_true });
|
||||||
|
try testExpr(
|
||||||
|
\\blk: {
|
||||||
|
\\ const foo = true;
|
||||||
|
\\ const bar = &foo;
|
||||||
|
\\ break :blk bar.*;
|
||||||
|
\\}
|
||||||
|
, @panic("TODO"), .{ .simple_value = .bool_true });
|
||||||
|
}
|
||||||
|
|
||||||
|
test "ComptimeInterpreter - call return primitive type" {
|
||||||
|
try testCall(
|
||||||
|
\\pub fn Foo() type {
|
||||||
|
\\ return bool;
|
||||||
|
\\}
|
||||||
|
, &.{}, .{ .simple_type = .bool });
|
||||||
|
|
||||||
|
try testCall(
|
||||||
|
\\pub fn Foo() type {
|
||||||
|
\\ return u32;
|
||||||
|
\\}
|
||||||
|
, &.{}, .{ .int_type = .{ .signedness = .unsigned, .bits = 32 } });
|
||||||
|
|
||||||
|
try testCall(
|
||||||
|
\\pub fn Foo() type {
|
||||||
|
\\ return i128;
|
||||||
|
\\}
|
||||||
|
, &.{}, .{ .int_type = .{ .signedness = .signed, .bits = 128 } });
|
||||||
|
|
||||||
|
try testCall(
|
||||||
|
\\pub fn Foo() type {
|
||||||
|
\\ const alpha = i128;
|
||||||
|
\\ return alpha;
|
||||||
|
\\}
|
||||||
|
, &.{}, .{ .int_type = .{ .signedness = .signed, .bits = 128 } });
|
||||||
|
}
|
||||||
|
|
||||||
|
test "ComptimeInterpreter - call return struct" {
|
||||||
|
var context = try Context.init(
|
||||||
|
\\pub fn Foo() type {
|
||||||
\\ return struct {
|
\\ return struct {
|
||||||
\\ slay: bool,
|
\\ slay: bool,
|
||||||
\\ var abc = 123;
|
\\ var abc = 123;
|
||||||
\\ };
|
\\ };
|
||||||
\\}
|
\\}
|
||||||
);
|
);
|
||||||
|
defer context.deinit();
|
||||||
|
const result = try context.call(context.findFn("Foo"), &.{});
|
||||||
|
|
||||||
var interpreter = ComptimeInterpreter{
|
try std.testing.expect(result.ty == .simple_type);
|
||||||
.allocator = allocator,
|
try std.testing.expect(result.ty.simple_type == .type);
|
||||||
.document_store = &doc_store,
|
const struct_info = context.interpreter.ip.getStruct(result.val.?.struct_type);
|
||||||
.uri = "file:///file.zig",
|
try std.testing.expectEqual(Index.none, struct_info.backing_int_ty);
|
||||||
};
|
try std.testing.expectEqual(std.builtin.Type.ContainerLayout.Auto, struct_info.layout);
|
||||||
defer interpreter.deinit();
|
|
||||||
|
|
||||||
_ = try interpreter.interpret(0, null, .{});
|
try std.testing.expectEqual(@as(usize, 1), struct_info.fields.count());
|
||||||
|
try std.testing.expectEqualStrings("slay", struct_info.fields.keys()[0]);
|
||||||
const rmt = interpreter.root_type.?.getTypeInfo().@"struct".scope.declarations.get("ReturnMyType").?;
|
try std.testing.expect(struct_info.fields.values()[0].ty == Index.bool_type);
|
||||||
|
}
|
||||||
const z = try interpreter.call(null, rmt.node_idx, &.{}, .{});
|
|
||||||
defer z.scope.deinit();
|
test "ComptimeInterpreter - call comptime argument" {
|
||||||
|
var context = try Context.init(
|
||||||
try std.testing.expectFmt("struct {slay: bool, var abc: comptime_int = 123, }", "{any}", .{interpreter.formatTypeInfo(z.result.value.value_data.type.getTypeInfo())});
|
\\pub fn Foo(comptime my_arg: bool) type {
|
||||||
|
\\ var abc = z: {break :z if (!my_arg) 123 else 0;};
|
||||||
|
\\ if (abc == 123) return u69;
|
||||||
|
\\ return u8;
|
||||||
|
\\}
|
||||||
|
);
|
||||||
|
defer context.deinit();
|
||||||
|
|
||||||
|
const result1 = try context.call(context.findFn("Foo"), &.{KV{
|
||||||
|
.ty = .{ .simple_type = .bool },
|
||||||
|
.val = .{ .simple_value = .bool_true },
|
||||||
|
}});
|
||||||
|
try std.testing.expect(result1.ty == .simple_type);
|
||||||
|
try std.testing.expect(result1.ty.simple_type == .type);
|
||||||
|
try std.testing.expectEqual(Key{ .int_type = .{ .signedness = .unsigned, .bits = 8 } }, result1.val.?);
|
||||||
|
|
||||||
|
var result2 = try context.call(context.findFn("Foo"), &.{KV{
|
||||||
|
.ty = .{ .simple_type = .bool },
|
||||||
|
.val = .{ .simple_value = .bool_false },
|
||||||
|
}});
|
||||||
|
try std.testing.expect(result2.ty == .simple_type);
|
||||||
|
try std.testing.expect(result2.ty.simple_type == .type);
|
||||||
|
try std.testing.expectEqual(Key{ .int_type = .{ .signedness = .unsigned, .bits = 69 } }, result2.val.?);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "ComptimeInterpreter - call inner function" {
|
||||||
|
try testCall(
|
||||||
|
\\pub fn Inner() type {
|
||||||
|
\\ return bool;
|
||||||
|
\\}
|
||||||
|
\\pub fn Foo() type {
|
||||||
|
\\ return Inner();
|
||||||
|
\\}
|
||||||
|
, &.{}, .{ .simple_type = .bool });
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Helper functions
|
||||||
|
//
|
||||||
|
|
||||||
|
const KV = struct {
|
||||||
|
ty: Key,
|
||||||
|
val: ?Key,
|
||||||
|
};
|
||||||
|
|
||||||
|
const Context = struct {
|
||||||
|
config: *zls.Config,
|
||||||
|
document_store: *zls.DocumentStore,
|
||||||
|
interpreter: *ComptimeInterpreter,
|
||||||
|
|
||||||
|
pub fn init(source: []const u8) !Context {
|
||||||
|
var config = try allocator.create(zls.Config);
|
||||||
|
errdefer allocator.destroy(config);
|
||||||
|
|
||||||
|
var document_store = try allocator.create(zls.DocumentStore);
|
||||||
|
errdefer allocator.destroy(document_store);
|
||||||
|
|
||||||
|
var interpreter = try allocator.create(ComptimeInterpreter);
|
||||||
|
errdefer allocator.destroy(interpreter);
|
||||||
|
|
||||||
|
config.* = .{};
|
||||||
|
document_store.* = .{
|
||||||
|
.allocator = allocator,
|
||||||
|
.config = config,
|
||||||
|
};
|
||||||
|
errdefer document_store.deinit();
|
||||||
|
|
||||||
|
const test_uri: []const u8 = switch (builtin.os.tag) {
|
||||||
|
.windows => "file:///C:\\test.zig",
|
||||||
|
else => "file:///test.zig",
|
||||||
|
};
|
||||||
|
|
||||||
|
const handle = try document_store.openDocument(test_uri, source);
|
||||||
|
|
||||||
|
// TODO handle handle.tree.errors
|
||||||
|
|
||||||
|
interpreter.* = .{
|
||||||
|
.allocator = allocator,
|
||||||
|
.ip = try InternPool.init(allocator),
|
||||||
|
.document_store = document_store,
|
||||||
|
.uri = handle.uri,
|
||||||
|
};
|
||||||
|
errdefer interpreter.deinit();
|
||||||
|
|
||||||
|
_ = try interpretReportErrors(interpreter, 0, .none);
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.config = config,
|
||||||
|
.document_store = document_store,
|
||||||
|
.interpreter = interpreter,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *Context) void {
|
||||||
|
self.interpreter.deinit();
|
||||||
|
self.document_store.deinit();
|
||||||
|
|
||||||
|
allocator.destroy(self.config);
|
||||||
|
allocator.destroy(self.document_store);
|
||||||
|
allocator.destroy(self.interpreter);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn call(self: *Context, func_node: Ast.Node.Index, arguments: []const KV) !KV {
|
||||||
|
var args = try allocator.alloc(ComptimeInterpreter.Value, arguments.len);
|
||||||
|
defer allocator.free(args);
|
||||||
|
|
||||||
|
for (arguments) |argument, i| {
|
||||||
|
args[i] = .{
|
||||||
|
.interpreter = self.interpreter,
|
||||||
|
.node_idx = 0,
|
||||||
|
.ty = try self.interpreter.ip.get(self.interpreter.allocator, argument.ty),
|
||||||
|
.val = if (argument.val) |val| try self.interpreter.ip.get(self.interpreter.allocator, val) else .none,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const namespace = @intToEnum(ComptimeInterpreter.Namespace.Index, 0); // root namespace
|
||||||
|
const result = (try self.interpreter.call(namespace, func_node, args, .{})).result;
|
||||||
|
|
||||||
|
try std.testing.expect(result == .value);
|
||||||
|
try std.testing.expect(result.value.ty != .none);
|
||||||
|
|
||||||
|
return KV{
|
||||||
|
.ty = self.interpreter.ip.indexToKey(result.value.ty),
|
||||||
|
.val = if (result.value.val == .none) null else self.interpreter.ip.indexToKey(result.value.val),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn interpret(self: *Context, node: Ast.Node.Index) !KV {
|
||||||
|
const namespace = @intToEnum(ComptimeInterpreter.Namespace.Index, 0); // root namespace
|
||||||
|
const result = try (try self.interpreter.interpret(node, namespace, .{})).getValue();
|
||||||
|
|
||||||
|
try std.testing.expect(result.ty != .none);
|
||||||
|
|
||||||
|
return KV{
|
||||||
|
.ty = self.interpreter.ip.indexToKey(result.ty),
|
||||||
|
.val = if (result.val == .none) null else self.interpreter.ip.indexToKey(result.val),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn findFn(self: Context, name: []const u8) Ast.Node.Index {
|
||||||
|
const handle = self.interpreter.getHandle();
|
||||||
|
for (handle.tree.nodes.items(.tag)) |tag, i| {
|
||||||
|
if (tag != .fn_decl) continue;
|
||||||
|
const node = @intCast(Ast.Node.Index, i);
|
||||||
|
var buffer: [1]Ast.Node.Index = undefined;
|
||||||
|
const fn_decl = handle.tree.fullFnProto(&buffer, node).?;
|
||||||
|
const fn_name = offsets.tokenToSlice(handle.tree, fn_decl.name_token.?);
|
||||||
|
if (std.mem.eql(u8, fn_name, name)) return node;
|
||||||
|
}
|
||||||
|
std.debug.panic("failed to find function with name '{s}'", .{name});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn findVar(self: Context, name: []const u8) Ast.Node.Index {
|
||||||
|
const handle = self.interpreter.getHandle();
|
||||||
|
var node: Ast.Node.Index = 0;
|
||||||
|
while (node < handle.tree.nodes.len) : (node += 1) {
|
||||||
|
const var_decl = handle.tree.fullVarDecl(node) orelse continue;
|
||||||
|
const name_token = var_decl.ast.mut_token + 1;
|
||||||
|
const var_name = offsets.tokenToSlice(handle.tree, name_token);
|
||||||
|
if (std.mem.eql(u8, var_name, name)) return var_decl.ast.init_node;
|
||||||
|
}
|
||||||
|
std.debug.panic("failed to find var declaration with name '{s}'", .{name});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fn testCall(
|
||||||
|
source: []const u8,
|
||||||
|
arguments: []const KV,
|
||||||
|
expected_ty: Key,
|
||||||
|
) !void {
|
||||||
|
var context = try Context.init(source);
|
||||||
|
defer context.deinit();
|
||||||
|
|
||||||
|
const result = try context.call(context.findFn("Foo"), arguments);
|
||||||
|
|
||||||
|
try expectEqualKey(context.interpreter.ip, Key{ .simple_type = .type }, result.ty);
|
||||||
|
try expectEqualKey(context.interpreter.ip, expected_ty, result.val);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn testExpr(
|
||||||
|
expr: []const u8,
|
||||||
|
expected_ty: Key,
|
||||||
|
expected_val: ?Key,
|
||||||
|
) !void {
|
||||||
|
const source = try std.fmt.allocPrint(allocator,
|
||||||
|
\\const foobarbaz = {s};
|
||||||
|
, .{expr});
|
||||||
|
defer allocator.free(source);
|
||||||
|
|
||||||
|
var context = try Context.init(source);
|
||||||
|
defer context.deinit();
|
||||||
|
|
||||||
|
const result = try context.interpret(context.findVar("foobarbaz"));
|
||||||
|
|
||||||
|
try expectEqualKey(context.interpreter.ip, expected_ty, result.ty);
|
||||||
|
if (expected_val) |expected| {
|
||||||
|
try expectEqualKey(context.interpreter.ip, expected, result.val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// TODO refactor this code
|
||||||
|
fn expectEqualKey(ip: InternPool, expected: Key, actual: ?Key) !void {
|
||||||
|
if (actual) |actual_key| {
|
||||||
|
if (expected.eql(actual_key)) return;
|
||||||
|
|
||||||
|
if (expected.isType() and actual_key.isType()) {
|
||||||
|
std.debug.print("expected type `{}`, found type `{}`\n", .{ expected.fmtType(ip), actual_key.fmtType(ip) });
|
||||||
|
} else if (expected.isType()) {
|
||||||
|
std.debug.print("expected type `{}`, found value ({})\n", .{ expected.fmtType(ip), actual_key });
|
||||||
|
} else if (actual_key.isType()) {
|
||||||
|
std.debug.print("expected value ({}), found type `{}`\n", .{ expected, actual_key.fmtType(ip) });
|
||||||
|
} else {
|
||||||
|
std.debug.print("expected value ({}), found value ({})\n", .{ expected, actual_key }); // TODO print value
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (expected.isType()) {
|
||||||
|
std.debug.print("expected type `{}`, found null\n", .{expected.fmtType(ip)});
|
||||||
|
} else {
|
||||||
|
std.debug.print("expected value ({}), found null\n", .{expected});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return error.TestExpectedEqual;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn interpretReportErrors(
|
||||||
|
interpreter: *ComptimeInterpreter,
|
||||||
|
node_idx: Ast.Node.Index,
|
||||||
|
namespace: InternPool.NamespaceIndex,
|
||||||
|
) !ComptimeInterpreter.InterpretResult {
|
||||||
|
const result = interpreter.interpret(node_idx, namespace, .{});
|
||||||
|
|
||||||
|
// TODO use ErrorBuilder
|
||||||
|
var err_it = interpreter.errors.iterator();
|
||||||
|
if (interpreter.errors.count() != 0) {
|
||||||
|
const handle = interpreter.getHandle();
|
||||||
|
std.debug.print("\n{s}\n", .{handle.text});
|
||||||
|
while (err_it.next()) |entry| {
|
||||||
|
const token = handle.tree.firstToken(entry.key_ptr.*);
|
||||||
|
const position = offsets.tokenToPosition(handle.tree, token, .@"utf-8");
|
||||||
|
std.debug.print("{d}:{d}: {s}\n", .{ position.line, position.character, entry.value_ptr.message });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user