Casts++, compileLog, pointers

This commit is contained in:
Auguste Rame 2022-10-31 16:00:02 -04:00
parent e6b691e447
commit c6ab7e8a0f
No known key found for this signature in database
GPG Key ID: 3A5E3F90DF2AAEFE
2 changed files with 167 additions and 40 deletions

View File

@ -134,7 +134,7 @@ pub fn build(b: *std.build.Builder) !void {
});
}
tests.use_stage1 = true;
// tests.use_stage1 = true;
tests.addPackage(.{ .name = "zls", .source = .{ .path = "src/zls.zig" }, .dependencies = exe.packages.items });
tests.setBuildMode(.Debug);
tests.setTarget(target);

View File

@ -13,6 +13,8 @@ const analysis = @import("analysis.zig");
const DocumentStore = @import("DocumentStore.zig");
const ComptimeInterpreter = @This();
// TODO: Investigate arena
allocator: std.mem.Allocator,
document_store: *DocumentStore,
handle: *const DocumentStore.Handle,
@ -25,11 +27,15 @@ errors: std.AutoArrayHashMapUnmanaged(Ast.Node.Index, InterpreterError) = .{},
type_info: std.ArrayListUnmanaged(TypeInfo) = .{},
type_info_map: std.HashMapUnmanaged(TypeInfo, usize, TypeInfo.Context, std.hash_map.default_max_load_percentage) = .{},
// TODO: Use DOD
value_data_list: std.ArrayListUnmanaged(*ValueData) = .{},
pub const InterpreterError = struct {
code: []const u8,
message: []const u8,
};
/// `message` must be allocated with interpreter allocator
pub fn recordError(interpreter: *ComptimeInterpreter, node_idx: Ast.Node.Index, code: []const u8, message: []const u8) error{OutOfMemory}!void {
try interpreter.errors.put(interpreter.allocator, node_idx, .{
.code = code,
@ -38,10 +44,17 @@ pub fn recordError(interpreter: *ComptimeInterpreter, node_idx: Ast.Node.Index,
}
pub fn deinit(interpreter: *ComptimeInterpreter) void {
var err_it = interpreter.errors.iterator();
while (err_it.next()) |entry| interpreter.allocator.free(entry.value_ptr.message);
if (interpreter.root_type) |rt| rt.getTypeInfo().getScopeOfType().?.deinit();
for (interpreter.type_info.items) |*ti| ti.deinit(interpreter.allocator);
for (interpreter.value_data_list.items) |ti| interpreter.allocator.destroy(ti);
interpreter.errors.deinit(interpreter.allocator);
interpreter.type_info.deinit(interpreter.allocator);
interpreter.type_info_map.deinit(interpreter.allocator);
interpreter.value_data_list.deinit(interpreter.allocator);
}
pub const TypeInfo = union(enum) {
@ -78,7 +91,7 @@ pub const TypeInfo = union(enum) {
child: Type,
is_allowzero: bool,
sentinel: ?ValueData,
sentinel: ?*ValueData,
pub const Size = enum {
one,
@ -94,6 +107,13 @@ pub const TypeInfo = union(enum) {
params: std.ArrayListUnmanaged(usize) = .{},
};
pub const Array = struct {
len: usize,
child: Type,
sentinel: ?*ValueData,
};
/// Hack to get anytype working; only valid on fnparams
@"anytype",
@"type",
@ -108,6 +128,8 @@ pub const TypeInfo = union(enum) {
float: u16,
@"comptime_float",
array: Array,
pub fn eql(interpreter: ComptimeInterpreter, a: TypeInfo, b: TypeInfo) bool {
if (std.meta.activeTag(a) != std.meta.activeTag(b)) return false;
return switch (a) {
@ -196,7 +218,7 @@ pub const Value = struct {
node_idx: Ast.Node.Index,
@"type": Type,
value_data: ValueData,
value_data: *ValueData,
pub fn eql(value: Value, other_value: Value) bool {
return value.value_data.eql(other_value.value_data);
@ -231,11 +253,11 @@ pub const ValueData = union(enum) {
runtime,
comptime_undetermined,
pub fn eql(data: ValueData, other_data: ValueData) bool {
if (std.meta.activeTag(data) != std.meta.activeTag(other_data)) return false;
pub fn eql(data: *ValueData, other_data: *ValueData) bool {
if (std.meta.activeTag(data.*) != std.meta.activeTag(other_data.*)) return false;
// std.enums.
// std.meta.activeTag(u: anytype)
switch (data) {
switch (data.*) {
.@"bool" => return data.@"bool" == other_data.@"bool",
.big_int => return data.big_int.eq(other_data.big_int),
.unsigned_int => return data.unsigned_int == other_data.unsigned_int,
@ -250,7 +272,7 @@ pub const ValueData = union(enum) {
pub fn bitCount(data: ValueData) ?u16 {
return switch (data) {
// TODO: Implement for signed ints
.unsigned_int => |i| std.math.log2_int(@TypeOf(i), i),
.unsigned_int => |i| if (i == 0) 0 else std.math.log2_int(@TypeOf(i), i),
.big_int => |bi| @intCast(u16, bi.bitCountAbs()),
else => null,
};
@ -314,6 +336,13 @@ pub fn createType(interpreter: *ComptimeInterpreter, node_idx: Ast.Node.Index, t
}
}
pub fn createValueData(interpreter: *ComptimeInterpreter, data: ValueData) error{OutOfMemory}!*ValueData {
var vd = try interpreter.allocator.create(ValueData);
try interpreter.value_data_list.append(interpreter.allocator, vd);
vd.* = data;
return vd;
}
pub const TypeInfoFormatter = struct {
interpreter: *const ComptimeInterpreter,
ti: TypeInfo,
@ -358,9 +387,39 @@ pub fn formatTypeInfo(interpreter: *const ComptimeInterpreter, ti: TypeInfo) Typ
return TypeInfoFormatter{ .interpreter = interpreter, .ti = ti };
}
pub const ValueFormatter = struct {
interpreter: *const ComptimeInterpreter,
val: Value,
pub fn format(form: ValueFormatter, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
_ = fmt;
_ = options;
var value = form.val;
var ti = value.@"type".getTypeInfo();
return switch (ti) {
.int, .@"comptime_int" => switch (value.value_data.*) {
inline .unsigned_int, .signed_int, .big_int => |a| try writer.print("{d}", .{a}),
else => unreachable,
},
else => try writer.print("UnimplementedValuePrint", .{}),
};
}
};
pub fn formatValue(interpreter: *const ComptimeInterpreter, value: Value) ValueFormatter {
return ValueFormatter{ .interpreter = interpreter, .val = value };
}
// pub const Comptimeness = enum { @"comptime", runtime };
pub const InterpreterScope = struct {
interpreter: *ComptimeInterpreter,
// TODO: Actually use this value
// comptimeness: Comptimeness,
parent: ?*InterpreterScope = null,
node_idx: Ast.Node.Index,
declarations: std.StringHashMapUnmanaged(Declaration) = .{},
@ -431,7 +490,11 @@ pub const InterpreterScope = struct {
}
};
pub fn newScope(interpreter: *ComptimeInterpreter, maybe_parent: ?*InterpreterScope, node_idx: Ast.Node.Index) std.mem.Allocator.Error!*InterpreterScope {
pub fn newScope(
interpreter: *ComptimeInterpreter,
maybe_parent: ?*InterpreterScope,
node_idx: Ast.Node.Index,
) std.mem.Allocator.Error!*InterpreterScope {
var ls = try interpreter.allocator.create(InterpreterScope);
if (maybe_parent) |parent| try parent.child_scopes.append(interpreter.allocator, ls);
ls.* = .{
@ -546,7 +609,7 @@ pub fn cast(
.@"comptime_int" => switch (to_type_info) {
.int => {
if (value_data.bitCount().? > to_type_info.int.bits) {
switch (value_data) {
switch (value_data.*) {
inline .unsigned_int, .signed_int, .big_int => |bi| {
try interpreter.recordError(node_idx, "invalid_cast", try std.fmt.allocPrint(interpreter.allocator, "integer value {d} cannot be coerced to type '{s}'", .{ bi, interpreter.formatTypeInfo(to_type_info) }));
},
@ -561,7 +624,7 @@ pub fn cast(
};
err catch |e| {
try interpreter.recordError(node_idx, "invalid_cast", "invalid cast");
try interpreter.recordError(node_idx, "invalid_cast", try std.fmt.allocPrint(interpreter.allocator, "invalid cast from '{s}' to '{s}'", .{ interpreter.formatTypeInfo(from_type_info), interpreter.formatTypeInfo(to_type_info) }));
return e;
};
@ -654,7 +717,7 @@ pub fn interpret(
try interpreter.recordError(
field_info.ast.type_expr,
"invalid_field_type_value",
"Field type should be a type!",
try std.fmt.allocPrint(interpreter.allocator, "expected type 'type', found '{s}'", .{interpreter.formatTypeInfo(init_type_value.@"type".getTypeInfo())}),
);
continue;
}
@ -685,7 +748,7 @@ pub fn interpret(
.handle = interpreter.handle,
.node_idx = node_idx,
.@"type" = try interpreter.createType(node_idx, .{ .@"type" = {} }),
.value_data = .{ .@"type" = cont_type },
.value_data = try interpreter.createValueData(.{ .@"type" = cont_type }),
} };
},
.global_var_decl,
@ -711,7 +774,7 @@ pub fn interpret(
.handle = interpreter.handle,
.node_idx = std.math.maxInt(Ast.Node.Index),
.@"type" = try interpreter.createType(node_idx, .{ .@"type" = {} }),
.value_data = .{ .@"type" = value.@"type" },
.value_data = try interpreter.createValueData(.{ .@"type" = value.@"type" }),
} else try (try interpreter.interpret(decl.ast.type_node, scope, options)).getValue();
try scope.?.declarations.put(interpreter.allocator, name, .{
@ -781,19 +844,19 @@ pub fn interpret(
.handle = interpreter.handle,
.node_idx = node_idx,
.@"type" = try interpreter.createType(node_idx, .{ .@"type" = {} }),
.value_data = .{ .@"type" = try interpreter.createType(node_idx, .{ .@"bool" = {} }) },
.value_data = try interpreter.createValueData(.{ .@"type" = try interpreter.createType(node_idx, .{ .@"bool" = {} }) }),
} };
if (std.mem.eql(u8, "true", value)) return InterpretResult{ .value = Value{
.handle = interpreter.handle,
.node_idx = node_idx,
.@"type" = try interpreter.createType(node_idx, .{ .@"bool" = {} }),
.value_data = .{ .@"bool" = true },
.value_data = try interpreter.createValueData(.{ .@"bool" = true }),
} };
if (std.mem.eql(u8, "false", value)) return InterpretResult{ .value = Value{
.handle = interpreter.handle,
.node_idx = node_idx,
.@"type" = try interpreter.createType(node_idx, .{ .@"bool" = {} }),
.value_data = .{ .@"bool" = false },
.value_data = try interpreter.createValueData(.{ .@"bool" = false }),
} };
if (value.len == 5 and (value[0] == 'u' or value[0] == 'i') and std.mem.eql(u8, "size", value[1..])) return InterpretResult{
@ -801,14 +864,14 @@ pub fn interpret(
.handle = interpreter.handle,
.node_idx = node_idx,
.@"type" = try interpreter.createType(node_idx, .{ .@"type" = {} }),
.value_data = .{
.value_data = try interpreter.createValueData(.{
.@"type" = try interpreter.createType(node_idx, .{
.int = .{
.signedness = if (value[0] == 'u') .unsigned else .signed,
.bits = 64, // TODO: Platform specific
},
}),
},
}),
},
};
@ -817,19 +880,19 @@ pub fn interpret(
.handle = interpreter.handle,
.node_idx = node_idx,
.@"type" = try interpreter.createType(node_idx, .{ .@"type" = {} }),
.value_data = .{ .@"type" = try interpreter.createType(node_idx, .{ .@"type" = {} }) },
.value_data = try interpreter.createValueData(.{ .@"type" = try interpreter.createType(node_idx, .{ .@"type" = {} }) }),
} };
} else if (value.len >= 2 and (value[0] == 'u' or value[0] == 'i')) int: {
return InterpretResult{ .value = Value{
.handle = interpreter.handle,
.node_idx = node_idx,
.@"type" = try interpreter.createType(node_idx, .{ .@"type" = {} }),
.value_data = .{ .@"type" = try interpreter.createType(node_idx, .{
.value_data = try interpreter.createValueData(.{ .@"type" = try interpreter.createType(node_idx, .{
.int = .{
.signedness = if (value[0] == 'u') .unsigned else .signed,
.bits = std.fmt.parseInt(u16, value[1..], 10) catch break :int,
},
}) },
}) }),
} };
}
@ -903,7 +966,7 @@ pub fn interpret(
.handle = interpreter.handle,
.node_idx = node_idx,
.@"type" = try interpreter.createType(node_idx, .{ .@"bool" = {} }),
.value_data = .{ .@"bool" = (try a.getValue()).eql(try b.getValue()) },
.value_data = try interpreter.createValueData(.{ .@"bool" = (try a.getValue()).eql(try b.getValue()) }),
} };
// a.getValue().eql(b.getValue())
},
@ -916,7 +979,7 @@ pub fn interpret(
.handle = interpreter.handle,
.node_idx = node_idx,
.@"type" = try interpreter.createType(node_idx, .{ .@"comptime_int" = {} }),
.value_data = switch (nl) {
.value_data = try interpreter.createValueData(switch (nl) {
.float => .{ .float = try std.fmt.parseFloat(f64, s) },
.int => if (s[0] == '-') ValueData{ .signed_int = try std.fmt.parseInt(i64, s, 0) } else ValueData{ .unsigned_int = try std.fmt.parseInt(u64, s, 0) },
.big_int => |bii| ppp: {
@ -925,7 +988,7 @@ pub fn interpret(
break :ppp .{ .big_int = bi };
},
.failure => return error.CriticalAstFailure,
},
}),
},
};
},
@ -944,16 +1007,15 @@ pub fn interpret(
.assign_mul,
.assign_mul_wrap,
=> {
// TODO: Make this work with non identifiers
// TODO: Actually consider operators
const value = tree.getNodeSource(data[node_idx].lhs);
if (std.mem.eql(u8, tree.getNodeSource(data[node_idx].lhs), "_")) return InterpretResult{ .nothing = {} };
var psi = scope.?.parentScopeIterator();
while (psi.next()) |pscope| {
if (pscope.declarations.getEntry(value)) |decl|
decl.value_ptr.value = try (try interpreter.interpret(data[node_idx].rhs, scope.?, options)).getValue();
}
var ir = try interpreter.interpret(data[node_idx].lhs, scope, options);
var to_value = try ir.getValue();
var from_value = (try (try interpreter.interpret(data[node_idx].rhs, scope.?, options)).getValue());
to_value.value_data.* = (try interpreter.cast(node_idx, to_value.@"type", from_value)).value_data.*;
return InterpretResult{ .nothing = {} };
},
@ -982,10 +1044,27 @@ pub fn interpret(
const call_name = tree.tokenSlice(main_tokens[node_idx]);
if (std.mem.eql(u8, call_name, "@compileLog")) {
var final = std.ArrayList(u8).init(interpreter.allocator);
var writer = final.writer();
try writer.writeAll("log: ");
for (params) |param, index| {
var value = (try interpreter.interpret(param, scope, options)).maybeGetValue() orelse {
try writer.writeAll("indeterminate");
continue;
};
try writer.print("@as({s}, {s})", .{ interpreter.formatTypeInfo(value.@"type".getTypeInfo()), interpreter.formatValue(value) });
if (index != params.len - 1)
try writer.writeAll(", ");
}
try interpreter.recordError(node_idx, "compile_log", final.toOwnedSlice());
return InterpretResult{ .nothing = {} };
}
if (std.mem.eql(u8, call_name, "@compileError")) {
// TODO: Add message
try interpreter.recordError(node_idx, "compile_error", try std.fmt.allocPrint(interpreter.allocator, "compile error", .{}));
return InterpretResult{ .@"return" = {} };
}
@ -1007,7 +1086,7 @@ pub fn interpret(
.handle = interpreter.handle,
.node_idx = node_idx,
.@"type" = try interpreter.createType(node_idx, .{ .@"type" = {} }),
.value_data = .{ .@"type" = handle.interpreter.?.root_type.? },
.value_data = try interpreter.createValueData(.{ .@"type" = handle.interpreter.?.root_type.? }),
} };
}
@ -1019,7 +1098,7 @@ pub fn interpret(
.handle = interpreter.handle,
.node_idx = node_idx,
.@"type" = try interpreter.createType(node_idx, .{ .@"type" = {} }),
.value_data = .{ .@"type" = value.@"type" },
.value_data = try interpreter.createValueData(.{ .@"type" = value.@"type" }),
} };
}
@ -1039,7 +1118,7 @@ pub fn interpret(
.handle = interpreter.handle,
.node_idx = node_idx,
.@"type" = try interpreter.createType(node_idx, .{ .@"bool" = {} }),
.value_data = .{ .@"bool" = ti.getScopeOfType().?.declarations.contains(field_name.value_data.slice_of_const_u8) },
.value_data = try interpreter.createValueData(.{ .@"bool" = ti.getScopeOfType().?.declarations.contains(field_name.value_data.slice_of_const_u8) }),
} };
}
@ -1068,7 +1147,7 @@ pub fn interpret(
// to a fixed size value in the data(?) section (when we're compilign zig code)
.@"type" = try interpreter.createType(node_idx, .{
.pointer = .{
.size = .slice,
.size = .one,
.is_const = true,
.is_volatile = false,
.child = try interpreter.createType(0, .{ .int = .{
@ -1077,10 +1156,10 @@ pub fn interpret(
} }),
.is_allowzero = false,
.sentinel = .{ .unsigned_int = 0 },
.sentinel = null,
},
}),
.value_data = .{ .slice_of_const_u8 = value },
.value_data = try interpreter.createValueData(.{ .slice_of_const_u8 = value }),
};
// TODO: Add type casting, sentinel
@ -1137,7 +1216,7 @@ pub fn interpret(
.handle = interpreter.handle,
.node_idx = node_idx,
.@"type" = try interpreter.createType(node_idx, type_info),
.value_data = .{ .@"fn" = {} },
.value_data = try interpreter.createValueData(.{ .@"fn" = {} }),
};
const name = analysis.getDeclName(tree, node_idx).?;
@ -1187,13 +1266,61 @@ pub fn interpret(
.bool_not => {
const result = try interpreter.interpret(data[node_idx].lhs, scope, .{});
const value = (try result.getValue());
if (value.value_data != .@"bool") return error.InvalidOperation;
if (value.value_data.* != .@"bool") return error.InvalidOperation;
return InterpretResult{
.value = .{
.handle = interpreter.handle,
.node_idx = node_idx,
.@"type" = value.@"type",
.value_data = .{ .@"bool" = !value.value_data.@"bool" },
.value_data = try interpreter.createValueData(.{ .@"bool" = !value.value_data.@"bool" }),
},
};
},
.address_of => {
// TODO: Make const pointers if we're drawing from a const;
// variables are the only non-const(?)
const result = try interpreter.interpret(data[node_idx].lhs, scope, .{});
const value = (try result.getValue());
return InterpretResult{
.value = .{
.handle = interpreter.handle,
.node_idx = node_idx,
.@"type" = try interpreter.createType(node_idx, .{
.pointer = .{
.size = .one,
.is_const = false,
.is_volatile = false,
.child = value.@"type",
.is_allowzero = false,
.sentinel = null,
},
}),
.value_data = try interpreter.createValueData(.{ .@"one_ptr" = value.value_data }),
},
};
},
.deref => {
const result = try interpreter.interpret(data[node_idx].lhs, scope, .{});
const value = (try result.getValue());
const ti = value.@"type".getTypeInfo();
if (ti != .pointer) {
try interpreter.recordError(node_idx, "invalid_deref", try std.fmt.allocPrint(interpreter.allocator, "cannot deference non-pointer", .{}));
return error.InvalidOperation;
}
// TODO: Check if this is a one_ptr or not
return InterpretResult{
.value = .{
.handle = interpreter.handle,
.node_idx = node_idx,
.@"type" = ti.pointer.child,
.value_data = value.value_data.one_ptr,
},
};
},