zls/src/ComptimeInterpreter.zig

1093 lines
42 KiB
Zig
Raw Normal View History

2022-10-28 04:59:24 +01:00
//! Hacky comptime interpreter, courtesy of midnight code run fuelled by spite;
//! hope that one day this can use async... <33
// TODO: builtin work!!
// TODO: DODify
// TODO: Work with DocumentStore
const std = @import("std");
const ast = @import("ast.zig");
const zig = std.zig;
const Ast = zig.Ast;
const analysis = @import("analysis.zig");
const DocumentStore = @import("DocumentStore.zig");
const ComptimeInterpreter = @This();
allocator: std.mem.Allocator,
document_store: *DocumentStore,
handle: *const DocumentStore.Handle,
root_type: ?Type = null,
2022-10-28 04:59:24 +01:00
// TODO: Deduplicate typeinfo across different interpreters
2022-10-28 04:59:24 +01:00
type_info: std.ArrayListUnmanaged(TypeInfo) = .{},
type_info_map: std.HashMapUnmanaged(TypeInfo, usize, TypeInfo.Context, std.hash_map.default_max_load_percentage) = .{},
pub fn deinit(interpreter: *ComptimeInterpreter) void {
if (interpreter.root_type) |rt| rt.getTypeInfo().getScopeOfType().?.deinit();
2022-10-28 06:22:03 +01:00
for (interpreter.type_info.items) |*ti| ti.deinit(interpreter.allocator);
2022-10-28 04:59:24 +01:00
interpreter.type_info.deinit(interpreter.allocator);
interpreter.type_info_map.deinit(interpreter.allocator);
}
pub const TypeInfo = union(enum) {
pub const Context = struct {
interpreter: ComptimeInterpreter,
hasher: *std.hash.Wyhash,
pub fn hash(self: @This(), s: TypeInfo) u64 {
TypeInfo.hash(self, s);
return self.hasher.final();
}
pub fn eql(self: @This(), a: TypeInfo, b: TypeInfo) bool {
return TypeInfo.eql(self.interpreter, a, b);
}
};
pub const Signedness = enum { signed, unsigned };
pub const Struct = struct {
/// Declarations contained within
scope: *InterpreterScope,
fields: std.ArrayListUnmanaged(FieldDefinition) = .{},
};
pub const Int = struct {
bits: u16,
signedness: Signedness,
};
pub const Pointer = struct {
size: Size,
is_const: bool,
is_volatile: bool,
child: Type,
is_allowzero: bool,
sentinel: ?ValueData,
pub const Size = enum {
one,
many,
slice,
c,
};
};
pub const Fn = struct {
return_type: ?Type,
/// Index into interpreter.declarations
params: std.ArrayListUnmanaged(usize) = .{},
};
/// Hack to get anytype working; only valid on fnparams
@"anytype",
@"type",
@"bool",
@"struct": Struct,
pointer: Pointer,
@"fn": Fn,
2022-10-28 04:59:24 +01:00
int: Int,
@"comptime_int",
float: u16,
@"comptime_float",
pub fn eql(interpreter: ComptimeInterpreter, a: TypeInfo, b: TypeInfo) bool {
if (std.meta.activeTag(a) != std.meta.activeTag(b)) return false;
return switch (a) {
.@"struct" => false, // Struct declarations can never be equal
.pointer => p: {
const ap = a.pointer;
const bp = b.pointer;
break :p ap.size == bp.size and ap.is_const == bp.is_const and ap.is_volatile == bp.is_volatile and eql(
interpreter,
ap.child.getTypeInfo(),
bp.child.getTypeInfo(),
2022-10-28 04:59:24 +01:00
) and ap.is_allowzero == bp.is_allowzero and ((ap.sentinel == null and bp.sentinel == null) or ((ap.sentinel != null and bp.sentinel != null) and ap.sentinel.?.eql(bp.sentinel.?)));
},
.int => a.int.signedness == b.int.signedness and a.int.bits == b.int.bits,
.float => a.float == b.float,
else => return true,
};
}
pub fn hash(context: TypeInfo.Context, ti: TypeInfo) void {
context.hasher.update(&[_]u8{@enumToInt(ti)});
return switch (ti) {
.@"struct" => |s| {
context.hasher.update(std.mem.sliceAsBytes(s.fields.items));
// TODO: Fix
// context.hasher.update(std.mem.sliceAsBytes(s.declarations.items));
},
.pointer => |p| {
// const ap = a.pointer;
// const bp = b.pointer;
context.hasher.update(&[_]u8{ @enumToInt(p.size), @boolToInt(p.is_const), @boolToInt(p.is_volatile) });
TypeInfo.hash(context, p.child.getTypeInfo());
2022-10-28 04:59:24 +01:00
context.hasher.update(&[_]u8{@boolToInt(p.is_allowzero)});
// TODO: Hash Sentinel
// break :p ap.size == bp.size and ap.is_const == bp.is_const and ap.is_volatile == bp.is_volatile and eql(
// source_unit,
// source_interpreter.type_info.items[ap.child.info_idx],
// source_interpreter.type_info.items[bp.child.info_idx],
// ) and ap.is_allowzero == bp.is_allowzero and ((ap.sentinel == null and bp.sentinel == null) or ((ap.sentinel != null and bp.sentinel != null) and ap.sentinel.?.eql(bp.sentinel.?)));
},
.int => |i| {
// a.int.signedness == b.int.signedness and a.int.bits == b.int.bits;
context.hasher.update(&[_]u8{@enumToInt(i.signedness)});
context.hasher.update(&std.mem.toBytes(i.bits));
},
.float => |f| context.hasher.update(&std.mem.toBytes(f)),
else => {},
};
}
2022-10-28 06:22:03 +01:00
pub fn deinit(ti: *TypeInfo, allocator: std.mem.Allocator) void {
switch (ti.*) {
.@"struct" => |*s| s.fields.deinit(allocator),
else => {},
}
}
pub fn getScopeOfType(ti: TypeInfo) ?*InterpreterScope {
return switch (ti) {
.@"struct" => |s| s.scope,
else => null,
};
}
2022-10-28 04:59:24 +01:00
};
pub const Type = struct {
handle: *const DocumentStore.Handle,
2022-10-28 04:59:24 +01:00
node_idx: Ast.Node.Index,
info_idx: usize,
pub fn getTypeInfo(@"type": Type) TypeInfo {
return @"type".handle.interpreter.?.type_info.items[@"type".info_idx];
}
2022-10-28 04:59:24 +01:00
};
pub const Value = struct {
handle: *const DocumentStore.Handle,
2022-10-28 04:59:24 +01:00
node_idx: Ast.Node.Index,
@"type": Type,
value_data: ValueData,
pub fn eql(value: Value, other_value: Value) bool {
return value.value_data.eql(other_value.value_data);
}
};
pub const ValueData = union(enum) {
// TODO: Support larger ints, floats; bigints?
@"type": Type,
@"bool": bool,
// @"struct": struct {
// },
// one_ptr: *anyopaque,
/// TODO: Optimize this with an ArrayList that uses anyopaque slice
slice_ptr: std.ArrayListUnmanaged(ValueData),
@"comptime_int": std.math.big.int.Managed,
unsigned_int: u64,
signed_int: i64,
float: f64,
@"fn",
runtime,
comptime_undetermined,
2022-10-28 04:59:24 +01:00
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) {
.@"bool" => return data.@"bool" == other_data.@"bool",
.@"comptime_int" => return data.@"comptime_int".eq(other_data.@"comptime_int"),
.unsigned_int => return data.unsigned_int == other_data.unsigned_int,
.signed_int => return data.signed_int == other_data.signed_int,
.float => return data.float == other_data.float,
else => return false,
2022-10-28 04:59:24 +01:00
}
}
};
pub const FieldDefinition = struct {
node_idx: Ast.Node.Index,
/// Store name so tree doesn't need to be used to access field name
name: []const u8,
@"type": Type,
default_value: ?Value,
};
pub const Declaration = struct {
node_idx: Ast.Node.Index,
/// Store name so tree doesn't need to be used to access declaration name
name: []const u8,
@"type": Type,
value: Value,
// TODO: figure this out
// pub const DeclarationKind = enum{variable, function};
// pub fn declarationKind(declaration: Declaration, tree: Ast) DeclarationKind {
// return switch(tree.nodes.items(.tag)[declaration.node_idx]) {
// .fn_proto,
// .fn_proto_one,
// .fn_proto_simple,
// .fn_proto_multi,
// .fn_decl
// }
// }
pub fn isConstant(declaration: Declaration, tree: Ast) bool {
return switch (tree.nodes.items(.tag)[declaration.node_idx]) {
.global_var_decl,
.local_var_decl,
.aligned_var_decl,
.simple_var_decl,
=> {
return tree.tokenSlice(ast.varDecl(tree, declaration.node_idx).?.ast.mut_token).len != 3;
2022-10-28 04:59:24 +01:00
},
else => false,
};
}
};
pub fn createType(interpreter: *ComptimeInterpreter, node_idx: Ast.Node.Index, type_info: TypeInfo) std.mem.Allocator.Error!Type {
// TODO: Figure out dedup
var hasher = std.hash.Wyhash.init(0);
var gpr = try interpreter.type_info_map.getOrPutContext(interpreter.allocator, type_info, .{ .interpreter = interpreter.*, .hasher = &hasher });
if (gpr.found_existing) {
// std.log.info("Deduplicating type {d}", .{interpreter.formatTypeInfo(unit.type_info.items[gpr.value_ptr.*])});
return Type{ .handle = interpreter.handle, .node_idx = node_idx, .info_idx = gpr.value_ptr.* };
2022-10-28 04:59:24 +01:00
} else {
try interpreter.type_info.append(interpreter.allocator, type_info);
const info_idx = interpreter.type_info.items.len - 1;
gpr.value_ptr.* = info_idx;
return Type{ .handle = interpreter.handle, .node_idx = node_idx, .info_idx = info_idx };
2022-10-28 04:59:24 +01:00
}
}
pub const TypeInfoFormatter = struct {
interpreter: *const ComptimeInterpreter,
2022-10-28 04:59:24 +01:00
ti: TypeInfo,
pub fn format(value: TypeInfoFormatter, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
_ = fmt;
_ = options;
return switch (value.ti) {
.int => |ii| switch (ii.signedness) {
.signed => try writer.print("i{d}", .{ii.bits}),
.unsigned => try writer.print("u{d}", .{ii.bits}),
}, // TODO
.float => |f| try writer.print("f{d}", .{f}),
.@"comptime_int" => try writer.writeAll("comptime_int"),
.@"comptime_float" => try writer.writeAll("comptime_float"),
.@"type" => try writer.writeAll("type"),
.@"bool" => try writer.writeAll("bool"),
.@"struct" => |s| {
try writer.writeAll("struct {");
2022-10-28 06:22:03 +01:00
for (s.fields.items) |field| {
try writer.print("{s}: {s}, ", .{ field.name, value.interpreter.formatTypeInfo(field.@"type".getTypeInfo()) });
2022-10-28 06:22:03 +01:00
}
2022-10-28 04:59:24 +01:00
var iterator = s.scope.declarations.iterator();
while (iterator.next()) |di| {
const decl = di.value_ptr.*;
if (decl.isConstant(value.interpreter.handle.tree)) {
try writer.print("const {s}: {any} = TODO_PRINT_VALUES, ", .{ decl.name, value.interpreter.formatTypeInfo(decl.@"type".getTypeInfo()) });
2022-10-28 04:59:24 +01:00
} else {
try writer.print("var {s}: {any}, ", .{ decl.name, value.interpreter.formatTypeInfo(decl.@"type".getTypeInfo()) });
2022-10-28 04:59:24 +01:00
}
}
try writer.writeAll("}");
},
else => try writer.print("UnimplementedTypeInfoPrint", .{}),
};
}
};
pub fn formatTypeInfo(interpreter: *const ComptimeInterpreter, ti: TypeInfo) TypeInfoFormatter {
2022-10-28 04:59:24 +01:00
return TypeInfoFormatter{ .interpreter = interpreter, .ti = ti };
}
pub const InterpreterScope = struct {
interpreter: *ComptimeInterpreter,
parent: ?*InterpreterScope = null,
node_idx: Ast.Node.Index,
declarations: std.StringHashMapUnmanaged(Declaration) = .{},
/// Resizes can modify element pointer locations, so we use a list of pointers
child_scopes: std.ArrayListUnmanaged(*InterpreterScope) = .{},
pub const ScopeKind = enum { container, block, function };
pub fn scopeKind(scope: InterpreterScope, tree: Ast) ScopeKind {
return switch (tree.nodes.items(.tag)[scope.node_idx]) {
.container_decl,
.container_decl_trailing,
.container_decl_arg,
.container_decl_arg_trailing,
.container_decl_two,
.container_decl_two_trailing,
.tagged_union,
.tagged_union_trailing,
.tagged_union_two,
.tagged_union_two_trailing,
.tagged_union_enum_tag,
.tagged_union_enum_tag_trailing,
.root,
.error_set_decl,
=> .container,
else => .block,
};
}
pub fn getLabel(scope: InterpreterScope, tree: Ast) ?Ast.TokenIndex {
const token_tags = tree.tokens.items(.tag);
return switch (scope.scopeKind(tree)) {
.block => z: {
const lbrace = tree.nodes.items(.main_token)[scope.node_idx];
break :z if (token_tags[lbrace - 1] == .colon and token_tags[lbrace - 2] == .identifier)
lbrace - 2
else
null;
},
else => null,
};
}
pub const ParentScopeIterator = struct {
maybe_scope: ?*InterpreterScope,
pub fn next(psi: *ParentScopeIterator) ?*InterpreterScope {
if (psi.maybe_scope) |scope| {
const curr = scope;
psi.maybe_scope = scope.parent;
return curr;
} else return null;
}
};
pub fn parentScopeIterator(scope: *InterpreterScope) ParentScopeIterator {
return ParentScopeIterator{ .maybe_scope = scope };
}
pub fn deinit(scope: *InterpreterScope) void {
const allocator = scope.interpreter.allocator;
scope.declarations.deinit(allocator);
2022-10-28 04:59:24 +01:00
for (scope.child_scopes.items) |child| child.deinit();
scope.child_scopes.deinit(allocator);
2022-10-28 04:59:24 +01:00
allocator.destroy(scope);
2022-10-28 04:59:24 +01:00
}
};
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.* = .{
.interpreter = interpreter,
.parent = maybe_parent,
.node_idx = node_idx,
};
return ls;
}
pub const InterpretResult = union(enum) {
@"break": ?[]const u8,
break_with_value: struct {
label: ?[]const u8,
value: Value,
},
value: Value,
@"return",
return_with_value: Value,
nothing,
pub fn maybeGetValue(result: InterpretResult) ?Value {
return switch (result) {
.break_with_value => |v| v.value,
.value => |v| v,
.return_with_value => |v| v,
2022-10-28 04:59:24 +01:00
else => null,
};
}
2022-10-28 06:22:03 +01:00
pub fn getValue(result: InterpretResult) error{ExpectedValue}!Value {
return result.maybeGetValue() orelse error.ExpectedValue;
2022-10-28 04:59:24 +01:00
}
};
fn getDeclCount(tree: Ast, node_idx: Ast.Node.Index) usize {
var buffer: [2]Ast.Node.Index = undefined;
const members = ast.declMembers(tree, node_idx, &buffer);
var count: usize = 0;
for (members) |member| {
switch (tree.nodes.items(.tag)[member]) {
.global_var_decl,
.local_var_decl,
.aligned_var_decl,
.simple_var_decl,
=> count += 1,
else => {},
}
}
return count;
}
pub fn huntItDown(
interpreter: *ComptimeInterpreter,
scope: *InterpreterScope,
decl_name: []const u8,
options: InterpretOptions,
) InterpretError!Declaration {
const tree = interpreter.handle.tree;
const tags = tree.nodes.items(.tag);
var psi = scope.parentScopeIterator();
while (psi.next()) |pscope| {
const known_decl = pscope.declarations.get(decl_name);
if (pscope.scopeKind(tree) == .container and
known_decl == null and
pscope.declarations.count() != getDeclCount(tree, pscope.node_idx))
{
std.log.info("Order-independent evaluating {s}...", .{decl_name});
var buffer: [2]Ast.Node.Index = undefined;
const members = ast.declMembers(tree, pscope.node_idx, &buffer);
for (members) |member| {
switch (tags[member]) {
.global_var_decl,
.local_var_decl,
.aligned_var_decl,
.simple_var_decl,
=> {
if (std.mem.eql(u8, analysis.getDeclName(tree, member).?, decl_name)) {
_ = try interpreter.interpret(member, pscope, options);
return pscope.declarations.get(decl_name).?;
}
},
else => {},
}
}
}
return known_decl orelse continue;
}
std.log.err("Identifier not found: {s}", .{decl_name});
return error.IdentifierNotFound;
}
2022-10-28 04:59:24 +01:00
// Might be useful in the future
pub const InterpretOptions = struct {};
2022-10-28 06:22:03 +01:00
pub const InterpretError = std.mem.Allocator.Error || std.fmt.ParseIntError || std.fmt.ParseFloatError || error{
InvalidCharacter,
InvalidBase,
ExpectedValue,
InvalidOperation,
CriticalAstFailure,
InvalidBuiltin,
IdentifierNotFound,
MissingArguments,
ImportFailure,
2022-10-28 06:22:03 +01:00
};
2022-10-28 04:59:24 +01:00
pub fn interpret(
interpreter: *ComptimeInterpreter,
node_idx: Ast.Node.Index,
scope: ?*InterpreterScope,
options: InterpretOptions,
) InterpretError!InterpretResult {
// _ = unit;
// _ = node;
// _ = observe_values;
const tree = interpreter.handle.tree;
2022-10-28 04:59:24 +01:00
const tags = tree.nodes.items(.tag);
const data = tree.nodes.items(.data);
const main_tokens = tree.nodes.items(.main_token);
std.log.info("{s}: {any}", .{ interpreter.handle.uri, tags[node_idx] });
2022-10-28 04:59:24 +01:00
switch (tags[node_idx]) {
.container_decl,
.container_decl_trailing,
.container_decl_arg,
.container_decl_arg_trailing,
.container_decl_two,
.container_decl_two_trailing,
// .tagged_union, // TODO: Fix these
// .tagged_union_trailing,
// .tagged_union_two,
// .tagged_union_two_trailing,
// .tagged_union_enum_tag,
// .tagged_union_enum_tag_trailing,
2022-10-28 04:59:24 +01:00
.root,
.error_set_decl,
=> {
var container_scope = try interpreter.newScope(scope, node_idx);
var type_info = TypeInfo{
.@"struct" = .{
.scope = container_scope,
},
};
var cont_type = try interpreter.createType(node_idx, type_info);
2022-10-28 04:59:24 +01:00
if (node_idx == 0) interpreter.root_type = cont_type;
2022-10-28 04:59:24 +01:00
var buffer: [2]Ast.Node.Index = undefined;
const members = ast.declMembers(tree, node_idx, &buffer);
for (members) |member| {
const maybe_container_field: ?zig.Ast.full.ContainerField = switch (tags[member]) {
.container_field => tree.containerField(member),
.container_field_align => tree.containerFieldAlign(member),
.container_field_init => tree.containerFieldInit(member),
else => null,
};
if (maybe_container_field) |field_info| {
var init_type = try interpreter.interpret(field_info.ast.type_expr, container_scope, .{});
var default_value = if (field_info.ast.value_expr == 0)
null
else
2022-10-28 06:22:03 +01:00
try (try interpreter.interpret(field_info.ast.value_expr, container_scope, .{})).getValue();
2022-10-28 04:59:24 +01:00
const name = tree.tokenSlice(field_info.ast.name_token);
const field = FieldDefinition{
.node_idx = member,
.name = name,
2022-10-28 06:22:03 +01:00
.@"type" = (try init_type.getValue()).value_data.@"type",
2022-10-28 04:59:24 +01:00
.default_value = default_value,
// TODO: Default values
// .@"type" = T: {
// var value = (try interpreter.interpret(field_info.ast.type_expr, scope_idx, true)).?.value;
// break :T @ptrCast(*Type, @alignCast(@alignOf(*Type), value)).*;
// },
// .value = null,
};
try type_info.@"struct".fields.append(interpreter.allocator, field);
} else {
_ = try interpreter.interpret(member, container_scope, options);
}
}
return InterpretResult{ .value = Value{
.handle = interpreter.handle,
2022-10-28 04:59:24 +01:00
.node_idx = node_idx,
.@"type" = try interpreter.createType(node_idx, .{ .@"type" = .{} }),
.value_data = .{ .@"type" = cont_type },
2022-10-28 04:59:24 +01:00
} };
},
.global_var_decl,
.local_var_decl,
.aligned_var_decl,
.simple_var_decl,
=> {
// TODO: Add 0 check
const name = analysis.getDeclName(tree, node_idx).?;
if (scope.?.declarations.contains(name))
return InterpretResult{ .nothing = .{} };
2022-10-28 04:59:24 +01:00
const decl = ast.varDecl(tree, node_idx).?;
if (decl.ast.init_node == 0)
return InterpretResult{ .nothing = .{} };
// We should have a value when a var is defined
// var value = try (try interpreter.interpret(decl.ast.init_node, scope, options)).getValue();
var value = (try interpreter.interpret(decl.ast.init_node, scope, options)).maybeGetValue() orelse return InterpretResult{ .nothing = .{} };
// Is this redundant? im too afraid to change it rn tbh
2022-10-28 04:59:24 +01:00
var @"type" = if (decl.ast.type_node == 0) Value{
.handle = interpreter.handle,
2022-10-28 04:59:24 +01:00
.node_idx = std.math.maxInt(Ast.Node.Index),
.@"type" = try interpreter.createType(node_idx, .{ .@"type" = .{} }),
.value_data = .{ .@"type" = value.@"type" },
2022-10-28 06:22:03 +01:00
} else try (try interpreter.interpret(decl.ast.type_node, scope, options)).getValue();
2022-10-28 04:59:24 +01:00
try scope.?.declarations.put(interpreter.allocator, name, .{
.node_idx = node_idx,
.name = name,
.@"type" = @"type".value_data.@"type",
.@"value" = value,
});
return InterpretResult{ .nothing = .{} };
},
.block,
.block_semicolon,
.block_two,
.block_two_semicolon,
=> {
// try interpreter.scopes.append(interpreter.allocator, .{
// .node_idx = node_idx,
// .parent_scope = parent_scope_idx orelse std.math.maxInt(usize),
// });
// const scope_idx = interpreter.scopes.items.len - 1;
var block_scope = try interpreter.newScope(scope, node_idx);
var buffer: [2]Ast.Node.Index = undefined;
const statements = ast.blockStatements(tree, node_idx, &buffer).?;
for (statements) |idx| {
const ret = try interpreter.interpret(idx, block_scope, options);
switch (ret) {
.@"break" => |lllll| {
const maybe_block_label_string = if (scope.?.getLabel(tree)) |i| tree.tokenSlice(i) else null;
if (lllll) |l| {
if (maybe_block_label_string) |ls| {
if (std.mem.eql(u8, l, ls)) {
return InterpretResult{ .nothing = .{} };
} else return ret;
} else return ret;
} else {
return InterpretResult{ .nothing = .{} };
}
},
.break_with_value => |bwv| {
const maybe_block_label_string = if (scope.?.getLabel(tree)) |i| tree.tokenSlice(i) else null;
if (bwv.label) |l| {
if (maybe_block_label_string) |ls| {
if (std.mem.eql(u8, l, ls)) {
return InterpretResult{ .value = bwv.value };
} else return ret;
} else return ret;
} else {
return InterpretResult{ .value = bwv.value };
}
},
.@"return", .return_with_value => return ret,
else => {},
}
}
return InterpretResult{ .nothing = .{} };
},
.identifier => {
var value = tree.getNodeSource(node_idx);
2022-10-28 06:22:03 +01:00
if (std.mem.eql(u8, "bool", value)) return InterpretResult{ .value = Value{
.handle = interpreter.handle,
2022-10-28 06:22:03 +01:00
.node_idx = node_idx,
.@"type" = try interpreter.createType(node_idx, .{ .@"type" = .{} }),
.value_data = .{ .@"type" = try interpreter.createType(node_idx, .{ .@"bool" = .{} }) },
} };
if (std.mem.eql(u8, "true", value)) return InterpretResult{ .value = Value{
.handle = interpreter.handle,
2022-10-28 06:22:03 +01:00
.node_idx = node_idx,
.@"type" = try interpreter.createType(node_idx, .{ .@"bool" = .{} }),
.value_data = .{ .@"bool" = true },
} };
if (std.mem.eql(u8, "false", value)) return InterpretResult{ .value = Value{
.handle = interpreter.handle,
2022-10-28 06:22:03 +01:00
.node_idx = node_idx,
.@"type" = try interpreter.createType(node_idx, .{ .@"bool" = .{} }),
.value_data = .{ .@"bool" = false },
} };
2022-10-28 04:59:24 +01:00
if (std.mem.eql(u8, "type", value)) {
return InterpretResult{ .value = Value{
.handle = interpreter.handle,
2022-10-28 04:59:24 +01:00
.node_idx = node_idx,
.@"type" = try interpreter.createType(node_idx, .{ .@"type" = .{} }),
.value_data = .{ .@"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,
2022-10-28 04:59:24 +01:00
.node_idx = node_idx,
.@"type" = try interpreter.createType(node_idx, .{ .@"type" = .{} }),
.value_data = .{ .@"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,
},
}) },
} };
}
2022-10-28 06:22:03 +01:00
// TODO: Floats
2022-10-28 04:59:24 +01:00
// Logic to find identifiers in accessible scopes
return InterpretResult{ .value = (try interpreter.huntItDown(scope.?, value, options)).value };
2022-10-28 04:59:24 +01:00
},
.field_access => {
if (data[node_idx].rhs == 0) return error.CriticalAstFailure;
const rhs_str = ast.tokenSlice(tree, data[node_idx].rhs) catch return error.CriticalAstFailure;
var ir = try interpreter.interpret(data[node_idx].lhs, scope, options);
var irv = try ir.getValue();
var sub_scope = irv.value_data.@"type".getTypeInfo().getScopeOfType() orelse return error.IdentifierNotFound;
// var scope_sub_decl = sub_scope.declarations.get(rhs_str) orelse return error.IdentifierNotFound;
var scope_sub_decl = try irv.value_data.@"type".handle.interpreter.?.huntItDown(sub_scope, rhs_str, options);
return InterpretResult{
.value = scope_sub_decl.value,
};
},
2022-10-28 04:59:24 +01:00
.grouped_expression => {
return try interpreter.interpret(data[node_idx].lhs, scope, options);
},
.@"break" => {
const label = if (data[node_idx].lhs == 0) null else tree.tokenSlice(data[node_idx].lhs);
return if (data[node_idx].rhs == 0)
InterpretResult{ .@"break" = label }
else
2022-10-28 06:22:03 +01:00
InterpretResult{ .break_with_value = .{ .label = label, .value = try (try interpreter.interpret(data[node_idx].rhs, scope, options)).getValue() } };
2022-10-28 04:59:24 +01:00
},
.@"return" => {
return if (data[node_idx].lhs == 0)
InterpretResult{ .@"return" = {} }
else
2022-10-28 06:22:03 +01:00
InterpretResult{ .return_with_value = try (try interpreter.interpret(data[node_idx].lhs, scope, options)).getValue() };
2022-10-28 04:59:24 +01:00
},
.@"if", .if_simple => {
const iff = ast.ifFull(tree, node_idx);
// TODO: Don't evaluate runtime ifs
// if (options.observe_values) {
const ir = try interpreter.interpret(iff.ast.cond_expr, scope, options);
2022-10-28 06:22:03 +01:00
if ((try ir.getValue()).value_data.@"bool") {
2022-10-28 04:59:24 +01:00
return try interpreter.interpret(iff.ast.then_expr, scope, options);
} else {
if (iff.ast.else_expr != 0) {
return try interpreter.interpret(iff.ast.else_expr, scope, options);
} else return InterpretResult{ .nothing = .{} };
}
},
.equal_equal => {
var a = try interpreter.interpret(data[node_idx].lhs, scope, options);
var b = try interpreter.interpret(data[node_idx].rhs, scope, options);
return InterpretResult{ .value = Value{
.handle = interpreter.handle,
2022-10-28 04:59:24 +01:00
.node_idx = node_idx,
.@"type" = try interpreter.createType(node_idx, .{ .@"bool" = .{} }),
2022-10-28 06:22:03 +01:00
.value_data = .{ .@"bool" = (try a.getValue()).eql(try b.getValue()) },
2022-10-28 04:59:24 +01:00
} };
// a.getValue().eql(b.getValue())
},
.number_literal => {
const s = tree.getNodeSource(node_idx);
const nl = std.zig.parseNumberLiteral(s);
// if (nl == .failure) ;
return InterpretResult{ .value = Value{
.handle = interpreter.handle,
2022-10-28 04:59:24 +01:00
.node_idx = node_idx,
.@"type" = try interpreter.createType(node_idx, .{ .@"comptime_int" = .{} }),
.value_data = 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: {
var bi = try std.math.big.int.Managed.init(interpreter.allocator);
try bi.setString(@enumToInt(bii), s[if (bii != .decimal) @as(usize, 2) else @as(usize, 0)..]);
break :ppp .{ .@"comptime_int" = bi };
},
2022-10-28 06:22:03 +01:00
.failure => return error.CriticalAstFailure,
2022-10-28 04:59:24 +01:00
},
} };
},
.assign,
.assign_bit_and,
.assign_bit_or,
.assign_shl,
.assign_shr,
.assign_bit_xor,
.assign_div,
.assign_sub,
.assign_sub_wrap,
.assign_mod,
.assign_add,
.assign_add_wrap,
.assign_mul,
.assign_mul_wrap,
=> {
// TODO: Make this work with non identifiers
// TODO: Actually consider operators
const value = tree.getNodeSource(data[node_idx].lhs);
var psi = scope.?.parentScopeIterator();
while (psi.next()) |pscope| {
if (pscope.declarations.getEntry(value)) |decl|
2022-10-28 06:22:03 +01:00
decl.value_ptr.value = try (try interpreter.interpret(data[node_idx].rhs, scope.?, options)).getValue();
2022-10-28 04:59:24 +01:00
}
return InterpretResult{ .nothing = .{} };
},
// .@"switch",
// .switch_comma,
// => {
// const cond = data[node_idx].lhs;
// const extra = tree.extraData(data[node_idx].rhs, Ast.Node.SubRange);
// const cases = tree.extra_data[extra.start..extra.end];
// for (cases) |case| {
// const switch_case: Ast.full.SwitchCase = switch (tags[case]) {
// .switch_case => tree.switchCase(case),
// .switch_case_one => tree.switchCaseOne(case),
// else => continue,
// };
// }
// },
.builtin_call,
.builtin_call_comma,
.builtin_call_two,
.builtin_call_two_comma,
=> {
var buffer: [2]Ast.Node.Index = undefined;
const params = ast.builtinCallParams(tree, node_idx, &buffer).?;
const call_name = tree.tokenSlice(main_tokens[node_idx]);
if (std.mem.eql(u8, call_name, "@compileLog")) {
return InterpretResult{ .nothing = .{} };
}
if (std.mem.eql(u8, call_name, "@compileError")) {
return InterpretResult{ .@"return" = .{} };
}
if (std.mem.eql(u8, call_name, "@import")) {
if (params.len == 0) return error.InvalidBuiltin;
const import_param = params[0];
if (tags[import_param] != .string_literal) return error.InvalidBuiltin;
const import_str = tree.tokenSlice(main_tokens[import_param]);
std.log.info("Resolving {s} from {s}", .{ import_str[1 .. import_str.len - 1], interpreter.handle.uri });
var import_uri = (try interpreter.document_store.uriFromImportStr(interpreter.allocator, interpreter.handle.*, import_str[1 .. import_str.len - 1])) orelse return error.ImportFailure;
defer interpreter.allocator.free(import_uri);
var handle = interpreter.document_store.getOrLoadHandle(import_uri) orelse return error.ImportFailure;
try interpreter.document_store.ensureInterpreterExists(handle.uri);
return InterpretResult{ .value = Value{
.handle = interpreter.handle,
.node_idx = node_idx,
.@"type" = try interpreter.createType(node_idx, .{ .@"type" = .{} }),
.value_data = .{ .@"type" = handle.interpreter.?.root_type.? },
} };
}
2022-10-28 04:59:24 +01:00
std.log.info("Builtin not implemented: {s}", .{call_name});
@panic("Builtin not implemented");
2022-10-28 06:22:03 +01:00
// return error.InvalidBuiltin;
2022-10-28 04:59:24 +01:00
},
.string_literal => {
const value = tree.getNodeSource(node_idx)[1 .. tree.getNodeSource(node_idx).len - 1];
var val = Value{
.handle = interpreter.handle,
2022-10-28 04:59:24 +01:00
.node_idx = node_idx,
.@"type" = try interpreter.createType(node_idx, .{
.pointer = .{
.size = .slice,
.is_const = true,
.is_volatile = false,
.child = try interpreter.createType(0, .{ .int = .{
.bits = 8,
.signedness = .unsigned,
} }),
.is_allowzero = false,
.sentinel = .{ .unsigned_int = 0 },
},
}),
.value_data = .{ .slice_ptr = .{} },
};
for (value) |z| {
try val.value_data.slice_ptr.append(interpreter.allocator, .{ .unsigned_int = z });
}
try val.value_data.slice_ptr.append(interpreter.allocator, .{ .unsigned_int = 0 });
return InterpretResult{ .value = val };
},
// TODO: Add comptime autodetection; e.g. const MyArrayList = std.ArrayList(u8)
.@"comptime" => {
return try interpreter.interpret(data[node_idx].lhs, scope, .{});
},
// .fn_proto,
// .fn_proto_multi,
// .fn_proto_one,
// .fn_proto_simple,
.fn_decl => {
// var buf: [1]Ast.Node.Index = undefined;
// const func = ast.fnProto(tree, node_idx, &buf).?;
// TODO: Add params
var type_info = TypeInfo{
.@"fn" = .{
.return_type = null,
},
};
2022-10-28 04:59:24 +01:00
// var it = func.iterate(&tree);
// while (ast.nextFnParam(&it)) |param| {
// // Add parameter decls
// if (param.name_token) |name_token| {
// // TODO: Think of new method for functions
// if ((try interpreter.interpret(param.type_expr, func_scope_idx, .{ .observe_values = true, .is_comptime = true })).maybeGetValue()) |value| {
// try interpreter.addDeclaration(func_scope_idx, value.value_data.@"type");
// try fnd.params.append(interpreter.allocator, interpreter.declarations.items.len - 1);
// } else {
// try interpreter.addDeclaration(parent_scope_idx.?, .{
// .node_idx = node_idx,
// .name = tree.tokenSlice(name_token),
// .scope_idx = func_scope_idx, // orelse std.math.maxInt(usize),
// .@"value" = undefined,
// .@"type" = interpreter.createType(0, .{ .@"anytype" = .{} }),
// });
// try fnd.params.append(interpreter.allocator, interpreter.declarations.items.len - 1);
// }
// }
// }
// if ((try interpreter.interpret(func.ast.return_type, func_scope_idx, .{ .observe_values = true, .is_comptime = true })).maybeGetValue()) |value|
// fnd.return_type = value.value_data.@"type";
var value = Value{
.handle = interpreter.handle,
.node_idx = node_idx,
.@"type" = try interpreter.createType(node_idx, type_info),
.value_data = .{ .@"fn" = .{} },
};
2022-10-28 04:59:24 +01:00
const name = analysis.getDeclName(tree, node_idx).?;
try scope.?.declarations.put(interpreter.allocator, name, .{
.node_idx = node_idx,
.name = name,
.@"type" = value.@"type",
.@"value" = value,
});
2022-10-28 04:59:24 +01:00
return InterpretResult{ .nothing = .{} };
},
.call,
.call_comma,
.async_call,
.async_call_comma,
.call_one,
.call_one_comma,
.async_call_one,
.async_call_one_comma,
=> {
var params: [1]Ast.Node.Index = undefined;
const call_full = ast.callFull(tree, node_idx, &params) orelse unreachable;
2022-10-28 04:59:24 +01:00
var args = try std.ArrayListUnmanaged(Value).initCapacity(interpreter.allocator, call_full.ast.params.len);
defer args.deinit(interpreter.allocator);
2022-10-28 04:59:24 +01:00
for (call_full.ast.params) |param| {
try args.append(interpreter.allocator, try (try interpreter.interpret(param, scope, .{})).getValue());
}
2022-10-28 04:59:24 +01:00
std.log.err("AEWEWEWE: {s}", .{tree.getNodeSource(call_full.ast.fn_expr)});
const func_id_result = try interpreter.interpret(call_full.ast.fn_expr, interpreter.root_type.?.getTypeInfo().getScopeOfType().?, .{});
const func_id_val = try func_id_result.getValue();
2022-10-28 04:59:24 +01:00
const call_res = try interpreter.call(interpreter.root_type.?.getTypeInfo().getScopeOfType().?, func_id_val.node_idx, args.items, options);
// defer call_res.scope.deinit();
// TODO: Figure out call result memory model; this is actually fine because newScope
// makes this a child of the decl scope which is freed on refresh... in theory
return switch (call_res.result) {
.value => |v| .{ .value = v },
.nothing => .{ .nothing = {} },
};
2022-10-28 04:59:24 +01:00
},
2022-10-28 06:22:03 +01:00
.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;
return InterpretResult{
.value = .{
.handle = interpreter.handle,
2022-10-28 06:22:03 +01:00
.node_idx = node_idx,
.@"type" = value.@"type",
.value_data = .{ .@"bool" = !value.value_data.@"bool" },
},
};
},
2022-10-28 04:59:24 +01:00
else => {
std.log.err("Unhandled {any}", .{tags[node_idx]});
return InterpretResult{ .nothing = .{} };
},
}
}
pub const CallResult = struct {
scope: *InterpreterScope,
result: union(enum) {
value: Value,
nothing,
},
};
pub fn call(
interpreter: *ComptimeInterpreter,
scope: ?*InterpreterScope,
2022-10-28 04:59:24 +01:00
func_node_idx: Ast.Node.Index,
arguments: []const Value,
options: InterpretOptions,
) InterpretError!CallResult {
_ = options;
// TODO: type check args
2022-10-28 04:59:24 +01:00
const tree = interpreter.handle.tree;
2022-10-28 04:59:24 +01:00
const tags = tree.nodes.items(.tag);
std.debug.assert(tags[func_node_idx] == .fn_decl);
var fn_scope = try interpreter.newScope(scope, func_node_idx);
2022-10-28 04:59:24 +01:00
var buf: [1]Ast.Node.Index = undefined;
var proto = ast.fnProto(tree, func_node_idx, &buf).?;
var arg_it = proto.iterate(&tree);
var arg_index: usize = 0;
while (ast.nextFnParam(&arg_it)) |param| {
if (arg_index >= arguments.len) return error.MissingArguments;
if (param.name_token) |nt| {
const decl = Declaration{
.node_idx = param.type_expr,
.name = tree.tokenSlice(nt),
.@"type" = arguments[arg_index].@"type",
.value = arguments[arg_index],
};
try fn_scope.declarations.put(interpreter.allocator, tree.tokenSlice(nt), decl);
arg_index += 1;
}
}
2022-10-28 04:59:24 +01:00
const body = tree.nodes.items(.data)[func_node_idx].rhs;
const result = try interpreter.interpret(body, fn_scope, .{});
// TODO: Defers
return CallResult{
.scope = fn_scope,
.result = switch (result) {
.@"return" => .{ .nothing = {} },
.@"return_with_value" => |v| .{ .value = v },
else => @panic("bruh"),
},
};
}